Implementing Actor Model in Azure Service Fabric

Introduction

In this article, we are going to learn about a completely new programming paradigm called Actors. First I will give you a brief intro to Actor Model theory and then we will start implementing Actor Model using Service Fabric. We will also see how we can manage the state in the Actor Model.

Road Map

  1. A brief intro to Actor Model theory
  2. Service Fabric Actors Implementation
  3. Creating a User Service
  4. Calling to Actors
  5. Test Using Postman
  6. Use Cases

*Pre-requisite* This article uses base code from the previous article “Creating Services With Azure Service Fabric”

Brief Intro to Actor Model Theory

There are a lot of problems regarding multithreading including sharing of data between different threads. Since then, there have been many attempts to simplify multithreaded programming, and the Actor model is one of the most popular ones these days. And what exactly is an actor? An actor is an isolated entity and is defined as a fundamental unit of computation, which embodies three essential parts.

  • Processing,
  • Storage,
  • Communications

The actor model says that everything is an actor, meaning that you model the system as a set of actors. But what is an actor? An actor is an abstract entity that contains.

  • Code to execute.
  • A persistent state. The state can only be accessed by the actor and no one else.
  • A mailbox, which means it's discoverable by other actors or different parts of the application like services.

The main thing you should remember is that multiple actors can run in parallel, but one actor processes messages sequentially. The underlying actor runtime implementation makes sure it executes actors as effectively as possible so you don't have to worry about parallelism.

Service Fabric Actors Implementation

a. Let's create an actor project in Visual Studio. As usual, we'll right-click the ECommerce project, choose Add, New Service Fabric Service

Service Fabric Actors

b. Now select Actor Service, and let's call it UserActor.

UserActor

A user service in the form of an actor. After that's done, you'll see that Visual Studio generates two new projects, UserActor and UserActor.Interfaces.

Visual Studio generates

Now let's have a look at the class generated in the UserActor project UserActor.cs, which is an entry point to the actor instance. The first thing worth noticing is that it derives from the Actor class. It also implements the IUserActor interface, which itself comes from the interface's project.

The Code below is automatically generated by the project, you don’t have to write it.

Code

When we open it, we can see it implements the IActor interface, which contains a few methods Visual Studio generated for us as an example. These are methods visible to public callers in actor's clients.

The Code below is automatically generated by the project, you don’t have to write it.

IActor interface

There will be an actor implementation class, in our case UserActor.cs, which implements frameworks Actor class, and also a public interface visible to actor's clients, in our case IUserActor.cs, which itself is contained in a separate project. And the last thing I'd like to show you before we move on is UserActor's constructor. It passes two parameters that are of use to us. The first one, ActorService, just provides context information like actor settings and how it's configured by the system, but the second one, ActorId, is of utter importance and needs more explanation.

The Code below is automatically generated by the project, you don’t have to write it.

Actor implementation class

In Service Fabric, actor projects are called actor-type projects. This is merely a way to map theory to code, and here is what it means. The actor Type contains code that implements actor behavior. For example, UserActor can have functionality to store user profiles and manage user baskets. However, there are millions of users existing on the system, and according to the actor theory, each user should be represented as a separate actor.

Building a User Service

Now we need to decide what our actor is going to do, right? For this, we will modify the IUserActor interface. The first method is designated to add a product to the basket where we specify productId and quantity. Note that we are not going to store the product information as there is no need for this. ID is enough. The second method to get the basket, a method to delete all products from the basket. We'll delete the older generated methods and comments because we don't need them.

a. But before that, create a new class BasketItem in UserActor.Interfaces project.

public class BasketItem
{
    public Guid ProductId { get; set; }
    public int Quantity { get; set; }
}

b. Now define the following methods in the IUserActor interface.

public interface IUserActor : IActor
{
    Task AddToBasket(Guid productId, int quantity);
    Task<BasketItem[]> GetBasket();
    Task ClearBasket();
}

All you want to know for now is that actor state management is very accessible and developer-friendly. The way to access the actor state is via the actor's base property called StateManager. StateManager is a dictionary-like structure that allows you to add values by keys, and remove, or update them. Therefore, to add a product to the basket, I will write the following code. It uses StateManager's method AddOrUpdateStateAsync, which accepts three parameters: Key, which we set to productId. Value, and for the value, we will use the product quantity. And because this method adds a new value or updates it, we need to provide a method to resolve conflicts if the key already exists.

c. Implement the method AddToBasket as follows.

public async Task AddToBasket(Guid productId, int quantity)
{
    await StateManager.AddOrUpdateAsync(productId.ToString(), quantity, (id, oldQuantity) => oldQuantity + quantity);
}

ClearBasket method will simply clear all the keys from the actor. So first it enumerates the keys, or sometimes they are called states in Service Fabric terminology, and removes each key's state.

d. Implement ClearBasket as follows,

public async Task ClearBasket()
{
    IEnumerable<string> productIDs = await StateManager.GetStateNamesAsync();
    foreach (string productId in productIDs)
    {
        await StateManager.RemoveStateAsync(productId);
    }
}

GetBasket, similar to ClearBasket, enumerates all the states, then gets the value for each state or key and puts it in a result BasketItem list, as simple as that.

e. Finally implement the GetBasket method.

public async Task<BasketItem[]> GetBasket()
{
    var result = new List<BasketItem>();
    IEnumerable<string> productIDs = await StateManager.GetStateNameAsync();
    foreach (string productId in productIDs)
    {
        int quantity = await StateManager.GetStateAsync<int>(productId);
        result.Add(new BasketItem { ProductId = new Guid(productId), Quantity = quantity });
    }
    return result.ToArray();
}

Calling to Actors

We've created the actor and implemented the basket methods, but it's all private to our cluster so far. It's not that useful to have private, inaccessible, and untested code that serves no purpose for users; therefore, the natural next step would be to expose the basket API via our web server REST API.

a. First of all right-click the Controllers folder and add a new class called BasketController.

The Code below is automatically generated by the project, you don’t have to write it.

Now, I'd like to have a method to get the user basket. Okay, our UserActor does return the basket; however, a map of GUI to integer is not that nice looking in a JSON world; therefore, I'll create a specially crafted entity. We will return to the callers of our API in the Model folder as we did before and call it ApiBasket.

b. Create a class ApiBasket in the Model folder of Api and add the following code.

public class ApiBasket
{
    [JsonProperty("userid")]
    public string UserId { get; set; }
    [JsonProperty("item")]
    public ApiBasketItem[] Items { get; set; }
}

public class ApiBasketItem
{
    [JsonProperty("productid")]
    public string ProductId { get; set; }
    [JsonProperty("quantity")]
    public int Quantity { get; set; }
}

Back to our controller class, BasketController, I'd like to have a method that returns this basket. It allows me to fetch the basket object by the following URI.

http://host:port/api/basket/userid

c. Create a Get Method in the BasketController class, we will add the code later.

public async Task<ApiBasket> GetAsync(string userId)
{
}

ApiBasketAddRequest is another class I've declared that contains productId and quantity, which allows me to bind it to the body of the request.

d. Create a class ApiBasketAddRequest in the Model folder of API with this code.

public class ApiBasketAddRequest {
    [JsonProperty("productid")]
    public Guid ProductId { get; set; }
    [JsonProperty("quantity")]
    public int Quantity { get; set; }
}

I'd like to declare a nice-looking REST POST request to add product quantity to the user's basket. It will be accessible via the same URL containing the product ID and quantity in the body.

e. Declare a Post method as follows.

[HttpPost("{userId}")]
public async Task AddAsync(string userId, [FromBody] ApiBasketAddRequest request)
{
}

And the delete method is really simple. All we need is userId, and the method will be callable with the Delete HTTP method with the same URL as before.

f. Declare Delete Method as Follows.

[HttpDelete("{userId}")]
public async Task DeleteAsync(string userId)
{
}

The methods are in place. They still don't call our actor, and that's what we'll do now. Just like with services, in order to call an actor, we need to create an actor proxy; therefore, I'll add a helper method to get our UserActor interface to our controller, which accepts userId as a parameter because we chose to bind user ID to actor ID, so this parameter is definitely useful.

g. Add a helper method GetActor in BasketController as follows.

private IUserActor getActor(string userId)
{
    return ActorProxy.Create<IUserActor>(
        new ActorId(userId), new Uri("fabric:/Ecommerce/UserActorService"));
}

Moving on. Having this in place, it's easy to forward API calls to the actor.

h. Change the HttpPost and HttpDelete methods as follows.

[HttpPost("{userId}")]
public async Task AddAsync(string userId, [FromBody] ApiBasketAddRequest request)
{
    IUserActor actor = getActor(userId);
    await actor.AddToBasket(request.ProductId, request.Quantity);
}

[HttpDelete("{userId}")]
public async Task DeleteAsync(string userId)
{
    IUserActor actor = getActor(userId);
    await actor.ClearBasket();
}

i. Add the following code to the HttpGet method.

public async Task<ApiBasket> GetAsync(string userId)
{
    IUserActor actor = GetActor(userId);
    BasketItem[] products = await actor.GetBasket();
    return new ApiBasket()
    {
        UserId = userId,
        Items = products.Select(p => new ApiBasketItem
        {
            ProductId = p.ProductId.ToString(),
            Quantity = p.Quantity
        }).ToArray()
    };
}

Test with Postman

We are now ready to launch this application and make the call.

Postman

And it's successful, so problem solved.

Should try to get back the basket for the user, here we go, and we get it back.

Basket

All our APIs are working just perfectly. Congratulations! You've made it.

Use Cases

  • The complex system that involves dependencies and coordinating shared state
  • When you want to avoid using explicit locks to protect shared state
  • Classic synchronization problems like dining philosophers and the sleeping barber's problem
  • Highly available services
  • Scalable services


Similar Articles