Using Akka.NET from your ASP.NET core application

ASP.NET Core is a great web framework. Now that RC2 is out I can recommend you take a look at it. The tooling is much more solid now and the API is looking stable as well.

One of the things I've been meaning to try for weeks now is to use actors in my ASP.NET Core application. So here it goes. <!-- more -->

What is Akka.NET?

Akka.NET offers a way to build your application using actors. The framework solves a typical concurrency problem.

In a regular application if you want concurrency you're have to use things like locks and signals. These constructs allow you to synchronize parts of the application that access shared resources.

Concurrency is hard to get right. It opens the way to all sorts of weird problems that are hard to solve.

Akka.NET tries to solve this problem by using Actors. Actors are isolated components that you talk to by sending a message to its mailbox. Actors can talk to other actors by sending messages to their mailboxes. They can't and shouldn't access code in other actors directly.

The inside of an actor is single threaded, but the message delivery is not. Akka.NET takes care of message delivery so you can build your components as if there's no concurrency. Pretty cool stuff since this makes concurrency a lot easier.

As you can imagine the concurrency isn't completely gone. You still have to think about which message goes where and what messages you need in order to continue performing a task. But this is much simpler to think about than trying to work with mutex objects directly.

So what does an actor look like? It is a component that has a Receive handler. In this receive handler you define which type of message you handle and how you want to handle it.

1
2
3
4
5
6
7
public class MyActor: ReceiveActor
{
public MyActor()
{
Receive<string>(msg => Sender.Tell(msg));
}
}

In the sample I've written an actor that receives a message and sends back a reply immediately. The basic hello world stuff. In normal circumstances you would do complex processing in here. So a good tip is to invoke a method from within your receive handler to let it do the actual processing. This makes the actor code much more readable.

Running actors in your ASP.NET core application

To use the actor we just built you need to add an actor system to your ASP.NET core application.

If you've worked with ASP.NET core before you know that it has a startup class. In this class you define services and middleware for your application.

Akka.NET uses an actor system to run the actor instances. This actor system needs to be available in your web application. So first we need to create a new actor system for our actors to run in. After that we register it with the service collection.

1
2
3
4
5
6
7
8
9
10
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
var actorSystem = ActorSystem.Create("my-actor-system");
services.AddSingleton(typeof(ActorSystem), (serviceProvider) => actorSystem);
}
// ... The rest of your startup class
}

The actor lifecycle in a web application

Once you have an actor system you can start a new actor by asking for a specific actor based on a set of properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HomeController: Controller
{
private ActorSystem _actorSystem;
public HomeController(ActorSystem actorSystem)
{
_actorSystem = actorSystem;
}
[HttpGet("api/greet")]
public async Task<object> GetData(string name)
{
var actorRef = _actorSystem.ActorOf(Props.Create<MyActor>());
return await actorRef.Ask<string>(name);
}
}

In the sample above I've loaded the actor system in my controller and create a new actor instance every time I receive a request.

This means you get a new actor per request. It is simple, but can be quite memory intensive depending on what your actor does.

To make the most of Akka.NET it is best to use its routing and scaling capabilities.

Instead of creating a new actor every time you receive a request it is best to create a single actor at the start of the application. You can scale this at a later time if the need arises.

Now there's one little problem with this. In ASP.NET core there's no way to ask for named dependencies. So when you register your actor with the service collection it will overwrite any existing actors.

You can fix this by writing a custom class that wraps the actor reference. This custom class is then registered as singleton so that I have a single actor wrapped in it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface IMyActorInstance
{
Task<string> GreetAsync(string name);
}
public class MyActorInstance: IMyActorInstance
{
private IActorRef _actor;
public MyActorInstance(ActorSystem actorSystem)
{
_actor = actorSystem.ActorOf(Props.Create<MyActor>(), "my-actor");
}
public async Task<string> GreetAsync(string name)
{
return await _actor.Ask<string>(name);
}
}

Now that you have a proper wrapper you can register that wrapper with the service collection and use that instead. One other advantage over using actor references directly is that you can manage the message interface much better.

To use the wrapper reference it in the controller and call the provided methods to send a message to your actor.

Scaling the actor

Now that you have a proper wrapper for the actor you can scale it by modifying the construction logic a bit.

1
2
3
4
var actorProps = Props.Create<MyActor>()
.WithRouter(new RoundRobinPool(5));
_actor = actorSystem.ActorOf(actorProps,"my-actor");

This code tells Akka.NET we want a custom router with the actor. The RoundRobinPool router uses a round robin distribution algorithm to distribute the messages over the actor instances.

The round robin pool router isn't the only router available to you. You can also use the Broadcast router for scenarios where you need many actors to respond to a message. There's also routers that allow you to pin a series of messages to the same actor instance.

You can use the router setup in the sample as a means to scale. But you that is not the only scenario that routers in Akka.NET support. So I suggest you spend a little time to explore routing when you start to look at Akka.NET.

Final thoughts

The combination Akka.NET with ASP.NET core is a good one if you want your logic to scale easily. I personally feel that the Akka.NET API is easier to use than regular tasks with async/await. So when you have to scale logic in your application across multiple CPU cores I suggest you take a look at Akka.NET.

Give it a shot and let me know what you think!

avatar

Willem Meints

AI Fanatic, Technical Evangelist, Microsoft MVP