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
To meet these extremely sophisticated business requirements, you will use the following actor:
This little fellow listens to 3 types of messages:
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.
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:
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 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.
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
ApplicationStopping events to start and terminate the actor system, respectively.
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.
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:
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
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
ActorSystemand 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
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
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.
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.
Askfor 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
Askfunctionality 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
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.
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!
Subscribe to Havret on Software
Get the latest posts delivered right to your inbox