Skip to content
thesarfo

Concept

Akka & the Actor Model

What the actor model is, why it beats blocking OOP method calls, and how to wire up Akka.NET actors in an ASP.NET Core API.

views 0

Useful reading: How the actor model works, by example

What is an actor

The fundamental unit of communication that covers all 3 fundamental units of computation:

  • processing
  • storage
  • communication

It represents an entity that can send/receive messages to/from other actors via a mailbox mechanism. The actor is independent from the other parts of the system and can perform operations concurrently in a native way.

Better than OOP because actors communicate asynchronously instead of through method calls like OOP.

OOP method calls mean you have to wait for a response, which is blocking, and at any point during the method execution something might throw an exception or crash the program entirely. The traditional OOP approach isn’t self-healing on its own; you’d need to wrap it in something that is self-healing, in this case, an Actor.

An actor has an address to receive a message. When it receives a message, it can:

  • create other actors,
  • send a message to other actors it knows,
  • decide what to do with the message it receives.

To use the actor model, you need an Actor System per deployable unit. The Actor System is like a container that houses all your actors.

After you have the Actor System, you can create actors in that system using the actorOf method. This actor can then send messages using the Tell method.

For a class to become an actor, it needs to inherit from a base class based on the type of actor you want to create. For an actor that receives a message, it inherits from the ReceiveActor class.

Akka.NET in a nutshell

Akka.NET is a C# port of the Akka actor model from Scala. It’s about building highly concurrent, distributed, and fault-tolerant systems using the actor model. Actors are objects that:

  • Receive messages
  • Process one message at a time
  • Maintain their own private state safely without locks

Basic pieces

ConceptWhat it is
ActorSystemA container for actors; manages their lifecycle.
ActorA lightweight object that processes messages one-by-one.
PropsInstructions for creating an actor.
IActorRefA “handle” to interact with an actor (send it messages).
Tell (!)Fire-and-forget sending a message to an actor.
Ask (?)Send a message and expect a response (Task-based).
ReceiveAsyncAsync version of handling incoming messages inside an actor.
Actor MessagesPlain C# classes used as strongly-typed messages.

Example: setting up an Akka.NET actor system in a C# API

Imagine a .NET Web API that uses Akka.NET actors to do some background job (e.g., order processing).

1. Install the NuGet packages

Terminal window
dotnet add package Akka
dotnet add package Akka.DependencyInjection

2. Define messages, plain immutable objects (record, class, etc.):

public record ProcessOrder(int OrderId);
public record OrderProcessed(int OrderId);

3. Create an actor

using Akka.Actor;
using System.Threading.Tasks;
public class OrderProcessorActor : ReceiveActor
{
public OrderProcessorActor()
{
ReceiveAsync<ProcessOrder>(async order =>
{
// simulate work
await Task.Delay(500);
Console.WriteLine($"Processed order {order.OrderId}");
Sender.Tell(new OrderProcessed(order.OrderId));
});
}
}

ReceiveAsync<T> lets the actor asynchronously handle a message of type T. Sender is a reference to who sent the message. Tell sends a reply.

4. Create the ActorSystem and spawn actors (e.g. in Program.cs):

using Akka.Actor;
using Akka.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// setup Akka
var serviceProvider = builder.Services.BuildServiceProvider();
var bootstrap = BootstrapSetup.Create();
var diSetup = DependencyResolverSetup.Create(serviceProvider);
var actorSystemSetup = bootstrap.And(diSetup);
var actorSystem = ActorSystem.Create("MyActorSystem", actorSystemSetup);
// create actor (Props + ActorOf)
var orderProcessor = actorSystem.ActorOf(Props.Create(() => new OrderProcessorActor()), "orderProcessor");

ActorOf creates an actor instance from Props.

5. Send messages to actors, from a controller or service, using Tell and Ask:

// Fire-and-forget (Tell)
orderProcessor.Tell(new ProcessOrder(123));
// Request-response (Ask)
var response = await orderProcessor.Ask<OrderProcessed>(new ProcessOrder(456), TimeSpan.FromSeconds(5));
Console.WriteLine($"Got confirmation for order {response.OrderId}");

Full quickflow

  1. Create ActorSystem
  2. Define actor classes (subclass ReceiveActor)
  3. Use ReceiveAsync<T> inside to handle specific message types
  4. Create actors via ActorSystem.ActorOf(Props)
  5. Send messages via Tell (no reply) or Ask (with reply)
  6. Messages are plain classes/records, preferably immutable

Practical integration with a .NET API

If you’re in ASP.NET Core, you usually:

  • Register ActorSystem as a singleton service (like a background service)
  • Register actor IActorRef instances with IServiceCollection
  • Inject and use actor refs inside controllers
builder.Services.AddSingleton(provider =>
{
var actorSystem = ActorSystem.Create("MyActorSystem");
var orderProcessor = actorSystem.ActorOf(Props.Create(() => new OrderProcessorActor()), "orderProcessor");
return orderProcessor;
});

Then inject into your controller:

public class OrdersController : ControllerBase
{
private readonly IActorRef _orderProcessor;
public OrdersController(IActorRef orderProcessor)
{
_orderProcessor = orderProcessor;
}
[HttpPost("/process")]
public async Task<IActionResult> ProcessOrder([FromBody] int orderId)
{
var result = await _orderProcessor.Ask<OrderProcessed>(new ProcessOrder(orderId), TimeSpan.FromSeconds(5));
return Ok(result);
}
}

Cheat sheet

What you doCode example
Create actor systemActorSystem.Create("name")
Define actorclass MyActor : ReceiveActor {}
Create actor instancesystem.ActorOf(Props.Create(() => new MyActor()))
Fire-and-forget messageactorRef.Tell(new MyMessage())
Request-response messageawait actorRef.Ask<MyReply>(new MyRequest(), timeout)
Inside actor, receive messagesReceiveAsync<MyMessage>(async msg => { /* handle */ })