/ AKKA.NET, ASP.NET CORE

How to integrate Akka.NET and ASP.NET Core

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

I’ve been working with Akka.NET for some time, and I have to admit it’s 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 design critical sections of your applications in a non-locking manner. Unfortunately, while it frees you from dealing with complex issues like explicit locking or thread management, it can make simple things less obvious. For instance, how do you expose your actor-based system to the world? You could try using the Akka.Remote package with its location transparency feature, but that would imply all your clients also use Akka.NET. That’s a big assumption in this day and age when microservices dominate. So, we turn to REST. That’s precisely where ASP.NET Core comes into 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 offers functionalities such as:

  • 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 do is add IApplicationLifetime as a parameter to the Configure method in the Startup file. As a result, a new dependency will be injected at runtime, and we can subscribe to the 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 potentially tricky part might be the need to call the Wait method immediately after requesting our actor system’s termination. This stems from the asynchronous nature of the Terminate method. You’ll want to wait until Akka completes all the necessary clean-up tasks before safely resuming the shutdown procedure.

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 challenge, however, is making it available to the rest of the application, especially the controllers. Given that we’re in the ASP.NET Core environment, the first thing that probably comes to mind is registering it in the IoC container, just as we did with ActorSystem. Unfortunately, it’s not that straightforward due to the concept of Actor References.

When you create a new actor in Akka.NET, you don’t receive a reference to the actual instance of the actor you’ve created. Instead, what you get is a so-called actor reference. This actor reference, or actor handle, acts as a proxy whose primary purpose is to support message-sending to the actor it represents. You may appreciate this design or dislike it, but it’s one of the most fundamental aspects of Akka.NET’s implementation. This approach enables Akka’s location transparency feature. In our context, it simply means that every actor you create will be represented by the IActorRef interface.

It would be fine if you had only one top-level actor in your system (as we do in our demo app), but this is hardly the case in real-world applications. So, what’s the problem? The issue arises because you cannot register your actors in the IoC container as they are all 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 I’m about to present for this problem is quite straightforward. The idea involves introducing a delegate that represents a function. This function will take no arguments and will return an IActorRef.

public delegate IActorRef BooksManagerActorProvider();

This delegate can then be registered in the IoC container with a function that returns the corresponding actor reference. The container will ensure that the given actor is created only once, just as it 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 wish to use a specific actor, for example, inside a controller. By invoking it, you will obtain an IActorRef pointing to the appropriate actor instance. To register another actor, simply define the next delegate and proceed as described 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 use the Ask pattern. Ask returns a Task, which means it can be awaited, allowing your method to be 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, on the other hand, you wish to request an operation to be performed by your actor, and:

  • You don’t need a response,
  • The response might be delivered at an unspecified future time, or
  • There is no response expected,

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();
    }
}

For these types of ‘fire and forget’ asynchronous operations, it’s considered good practice to return a 202 (Accepted) HTTP status code from your action methods. This approach provides your clients with a clearer understanding of the system’s processes.

Summary

In this blog post, we’ve explored how to integrate Akka.NET with ASP.NET Core. If anything is unclear or if you have any questions, please feel free to leave a comment below!