/ AKKA.NET, ASP.NET CORE

How to integrate Akka.NET and ASP.NET Core

Integrating Akka.NET and ASP.NET Core can be quite tricky. In this blog post, I would like to demonstrate how to make these two technologies work together smoothly.

I have been working with Akka.NET for some time, and I have to admit that it is an amazing piece of technology. It truly simplifies building scalable, concurrent, distributed, and fault-tolerant applications. Thanks to its implementation of the actor model, Akka.NET can help you with designing critical sections of your applications in a non-locking manner. Unfortunately, while it gives you the freedom from having to deal with complex matters like explicit locking or thread management, at the same time, it makes simple things not so obvious. For instance: how to expose your actor based system to the world? You can try to do so by leveraging Akka.Remote package with its location transparency feature, but it would imply that all your clients use Akka.NET as well. It’s one hell of an assumption in this day and age when microservices rule the world. So we go for REST, then. And this is precisely where ASP.NET Core comes to play.

Understanding the example application

You can find the source code of the example application on GitHub. It is a simple bookstore inventory management service that exposes functionalities like:

  • adding new books
  • finding books by id
  • listing all the books available in the system

alt text

To meet these extremely sophisticated business requirements, you will use the following actor:

public class BooksManagerActor : ReceiveActor
{
private readonly Dictionary<Guid, Book> _books = new Dictionary<Guid, Book>();

    public BooksManagerActor()
    {
        Receive<CreateBook>(command =>
        {
            var newBook = new Book
            {
                Id = Guid.NewGuid(),
                Title = command.Title,
                Author = command.Author,
                Cost = command.Cost,
                InventoryAmount = command.InventoryAmount,
            };

            _books.Add(newBook.Id, newBook);
        });

        Receive<GetBookById>(query =>
        {
            if (_books.TryGetValue(query.Id, out var book))
                Sender.Tell(GetBookDto(book));
            else
                Sender.Tell(NotFound.Instance);
        });

        Receive<GetBooks>(query =>
            Sender.Tell(_books.Select(x => GetBookDto(x.Value)).ToList()));
    }

    private static BookDto GetBookDto(Book book) => new BookDto
    {
        Id = book.Id,
        Title = book.Title,
        Author = book.Author,
        Cost = book.Cost,
        InventoryAmount = book.InventoryAmount
    };

}

This little fellow listens to 3 types of messages: CreateBook, GetBookById, and GetBooks. Our job is to find a way to route these messages to it. But before we can do so, we will need an actor system to be set up and configured to run within our application.

One ActorSystem to rule them all

As you probably know already, there should be only one ActorSystem deployed per application. It’s vital because ActorSystems are really heavy objects. It’s an expensive operation to create one. Moreover, as a reference to the underlying Akka.NET framework, the actor system defines the context within all your actors live. In other words, it’s a container for all actor instances. If you decide to kill the actor system, all actors will die with it, and the messages pending in their mailboxes will be lost as well.

The best way to restrict the initialization of a certain class to precisely one object in ASP.NET Core based applications is to register it as a singleton in the IoC container. It’s the right way to go for our actor system, and the following code should do the trick:

public void ConfigureServices(IServiceCollection services)
{    
    /*...*/
    services.AddSingleton(_ => ActorSystem.Create("bookstore", ConfigurationLoader.Load()));
}

The crucial thing to note here is to use AddSingleton overload which takes a function as an argument. It’s important because the last thing we want is to create the actor system during container configuration. There is a much better place to do so.

IApplicationLifetime

Interface IApplicationLifetime provides a neat way to hook into ASP.NET Core application lifecycle events. It was designed to allow developers to perform clean-up during a graceful shutdown of the application. A quick look at its definition reveals that nothing stands against using it to do some initial work after the application has fully started as well.

public interface IApplicationLifetime
{  
    CancellationToken ApplicationStarted { get; }
    CancellationToken ApplicationStopping { get; }
    CancellationToken ApplicationStopped { get; }
    void StopApplication();
}

It looks exactly like what we are after. Now what we have to is to add IApplicationLifetime as a parameter to Configure method in Startup file. As a result, a new dependency will be injected at runtime, and we can subscribe to ApplicationStarted and ApplicationStopping events to start and terminate the actor system respectively.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime)
{
    /*...*/

    lifetime.ApplicationStarted.Register(() =>
    {
        app.ApplicationServices.GetService<ActorSystem>(); // start Akka.NET

    });
    lifetime.ApplicationStopping.Register(() =>
    {
        app.ApplicationServices.GetService<ActorSystem>().Terminate().Wait();
    });

}

The code above is rather self-explanatory. The only tricky thing here may be the need to call Wait method just after we requested our actor system’s termination. It results from the nature of Terminate method which is asynchronous. You want to wait until Akka performs all the necessary clean-up work before the shutdown procedure can be safely resumed.

Hollywood

Where are actors born?

Now that you know how to fit the ActorSystem into your ASP.NET Core application, it’s time to think about actually using it to create some actors and send them to work.

In our demo application, there is only one actor - BooksManagerActor. Bringing it to life is as simple as:

IActorRef booksManagerActor = actorSystem.ActorOf(Props.Create(() => new BooksManagerActor()));

The problem, however, is how to make it available to the rest of the application, and controllers in particular. As we are in ASP.NET Core world probably the first thing that comes to your mind is to register it in IoC container as we already did with ActorSystem. Unfortunately, it cannot be done that easily. All because of the concept of Actor References.

When you create a new actor, Akka.NET doesn’t give you the reference to the actual instance of the actor you created. All you get back is so-called actor reference. An actor reference (or an actor handle) is a form of proxy, whose purpose is to support sending messages to the actor it represents. You can hate it, or you can love it, but it’s one the most fundamental aspects of how Akka.NET is implemented. This is what makes Akka location transparency feature possible. In our case, however, it simply boils down to the fact that every actor you create will be represented by IActorRef interface.

It would be fine if you had only one top-level actor is your system (like we do in our demo app), but it’s hardly a case in real-world applications. What’s the problem, then? The problem is you cannot register your actors in IoC container as they are of the same type.

Since I first started learning Akka, I’ve come across several articles which try to address the problem of managing actor references. Most of them suggested to use static classes to cache and expose permanent references to ActorSystem and top-level actors. While I highly respect most of the authors, especially brilliant guys from Petabridge team, this solution is a big no-no for me.

The solution to this problem I am going to show here is really simple. The idea is to introduce a delegate which will represent a function that takes no arguments and returns IActorRef.

public delegate IActorRef BooksManagerActorProvider();

This delegate can then be registered in IoC container with function that returns corresponding actor reference. The container will guarantee that the given actor will be created only once, as it already does for ActorSystem.

services.AddSingleton<BooksManagerActorProvider>(provider =>
{
    var actorSystem = provider.GetService<ActorSystem>();
    var booksManagerActor = actorSystem.ActorOf(Props.Create(() => new BooksManagerActor()));
    return () => booksManagerActor;
});

This actor provider delegate can be injected wherever you like to use a specific actor, for example inside a controller. By invoking it, you will obtain IActorRef pointing to the appropriate actor instance. To register another actor just define next delegate and proceed as above.

For most cases, I like to unwrap my actor inside the constructor and then assign it as an instance field. It feels more natural to me than invoking provider function every time I want to send a message to the actor.

[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
    private readonly IActorRef _booksManagerActor;

    public BooksController(BooksManagerActorProvider booksManagerActorProvider)
    {
        _booksManagerActor = booksManagerActorProvider();
    }
}

If you want to query an actor you can do it by using the Ask pattern. Ask return a Task, which means it can be awaited, making your method fully asynchronous.

[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var books = await _booksManagerActor.Ask<IEnumerable<BookDto>>(GetBooks.Instance);
        return Ok(books);
    }
    
    [HttpGet("{id}")]
    public async Task<IActionResult> Get(Guid id)
    {
        var result = await _booksManagerActor.Ask(new GetBookById(id));
        switch (result)
        {
            case BookDto book:
                return Ok(book);
            default:
                return BadRequest();
        }
    }    
}

While using Ask for communication between actors is considered to be an anti-pattern, it is perfectly fine to do so from the controller action. You can try to avoid it as you may, but what you will finally end up is reimplementing Ask functionality all by yourself. Ask me how I know. ;)

If you, on the other hand, want to request some operation to be performed by your actor, and:

  • you do not care about the response
  • the response may be delivered in some unknown future
  • there won’t be any response

you should just use Tell.

[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{    
    [HttpPost]
    public IActionResult Post([FromBody] CreateBook command)
    {        
        _booksManagerActor.Tell(command);
        return Accepted();
    }
}

It is considered to be a good practice for this kind of fire and forget asynchronous operations to return 202 (Accepted) HTTP status code from your action methods. This way you are giving your clients a better sense of what’s going on in the system.

Summary

In this blog post, you learned how Akka.NET can be combined with ASP.NET Core. If you have any questions or some points are not clear to you, please leave me a comment below!