Akka in .Net Core (with DI and Supervision)

Distributed and concurrent systems are norms rather than the exception these days. And there is no other better framework to build a distributed system than Akka. In this blog post, I am going to discuss building applications with Akka in .Net Core.

Akka is an implementation of the Actor model. Akka encapsulates all the complex implementation for managing concurrency, fault-tolerance, and performance needed for building a distributed system.

Akka.Net is the .Net implementation of the Akka framework which is originally built for Java/Scala.

In this blog, I have going to cover the following aspects of Akka.Net in .Net Core:

  • Creating an actor
  • Registering and using actor through .Net Core Dependency Injection
  • Creating a child actor and relaying message to child actor
  • Akka.Net Supervision
Akka.Net with .Net Core

What is Actor Model

The actor model is a model of concurrent computation. In an Actor model, the state of an actor can be modified by the actor itself. But actors can affect each other through messages. Actors are autonomous entities.

Below are the main characteristics of Actors:

  • The Actors communicate through asynchronous messages only
  • Actors manages their own state
  • Actors decide how they respond to the message, and if it should create a child actor to manage the request

Why use the Actor Model

Consider the challenges faced in building a concurrent distributed system

  • When you communicate within multiple components, how to make them fault-tolerant
  • When a component failed to execute a command in a different thread, how to communicate it back to the main thread
  • Managing locks when multiple threads access a shared resource within a component

The actor implementation of Akka solves these challenges without us worrying about managing all the code ourselves.

Creating a .Net Core Application

In this blog, I will be working on creating a distributed system using Akka.Net. But I will first start with a basic .Net Core application with Akka.Net. And then introduce different features of Akka.

I will open Visual Studio 2019 and create a new .Net Core Console application named Akka.Demo. I will click on Create a new project and in the next page selection, I will select the Console App (.NET Core). Finally, on the next page, I will create Akka.Demo project.

After the new project is created, I will open the Nuget package manager by right-clicking on the project and clicking on “Manage Nuget Packages”. This will open the Nuget package manager window. Once the window is opened, I will search for the Akka package.

akka.net nuget package

Finally, I will click Install to install the 1.3.17 version of the Akka package.

Creating the first Actor class for Akka in .Net Core

First of all, I will start by creating an Actor class. A basic implementation of Actor. I will create a class named NotificationActor. I will rename the class later with a more appropriate name.

using Akka.Actor;
using System;

namespace Akka.Demo
{
    class NotificationActor : UntypedActor
    {
        protected override void OnReceive(object message)
        {
            Console.WriteLine($"Message received {message}");
        }
    }
}

Here, I created my first actor deriving from UntypedActor class. The UntypedActor class belongs to the Akka.Actor namespace. The UntypedActor is an abstract class, hence I will override the abstract OnReceive method. This method will provide a message sent from a different actor. For the time being, I will just print out the message.

After that, I will update the NotificationActor class to implement two other methods PreStart and PostStop.

PreStart will be called by the Akka framework when the actor is getting started. The PostStop will be called after the actor is stopped.

using Akka.Actor;
using System;

namespace Akka.Demo
{
    class NotificationActor : UntypedActor
    {
        protected override void OnReceive(object message)
        {
            Console.WriteLine($"Message received {message}");
        }

        protected override void PreStart() => Console.WriteLine("Actor started");

        protected override void PostStop() => Console.WriteLine("Actor stopped");
    }
}

Creating and sending a message to an actor

After the actor class is created and ready, I will now create an instance of the actor class and send a message.

First of all, I will have to create an actor system. And for that, I will use the ActorSystem.Create method in the Main function of Program class. The Create method takes a name for the new actor system.

var actorSystem = ActorSystem.Create("test-actor-system");

After I get a handle on the actor system, it’s time to create my actor. But, the actor class cannot be directly instantiated. Instead, we will have to use the ActorOf method of the ActorSystem class and Props.Create to create an instance of the actor.

var actor = actorSystem.ActorOf(Props.Create<NotificationActor>(), "first-actor");
using Akka.Actor;

namespace Akka.Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            var actorSystem = ActorSystem.Create("test-actor-system");
            actorSystem.ActorOf(Props.Create<NotificationActor>(), "first-actor");
        }
    }
}

And finally, I will use the Tell method on the Actor to send the message to it.

using Akka.Actor;
using System;

namespace Akka.Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            var actorSystem = ActorSystem.Create("test-actor-system");
            var actor = actorSystem.ActorOf(Props.Create<NotificationActor>(), "first-actor");
            actor.Tell("Hello there!");
            Console.ReadLine();
        }
    }
}

Now I will run the application and I can see two messages printed out in the console. The “Actor started” which is printed from the PreStart method. And the “Message received Hello there!”, which is printed from the OnReceive method of the actor.

actor output

Stopping an Actor

For stopping an actor, we can either do it from inside of the actor based on some message. Or use the ActorSystem.Stop to stop the actor.

using Akka.Actor;
using System;

namespace Akka.Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            var actorSystem = ActorSystem.Create("test-actor-system");
            var actor = actorSystem.ActorOf(Props.Create<NotificationActor>(), "first-actor");
            actor.Tell("Hello there!");
            actorSystem.Stop(actor);
            Console.ReadLine();
        }
    }
}

This time when I run the application, I will also see the “Actor stopped” message in the end.

actor stopped

Dependency Injection and Akka in .Net Core

Now that we have created the actor system and able to create an actor as well as send a message. Now it is time to configure dependency injection.

To do that I will install the following three Nuget packages:

  • Microsoft.Extensions.DependencyInjection
  • Akka.DI.Core
  • Akka.DI.Extensions.DependencyInjection

Introduction of EmailNotification

I will introduce a new call EmailNotification. This class will be responsible for sending email notifications. This class will implement the interface IEmailNotification.

It will have a single method Send, which will accept a string message parameter.

In the current implementation, I will just write the message into the console.

namespace Akka.Demo
{
    public interface IEmailNotification
    {
        void Send(string message);
    }
}
using System;

namespace Akka.Demo
{
    public class EmailNotification : IEmailNotification
    {
        public void Send(string message)
        {
            Console.WriteLine($"Email sent with message {message}");
        }
    }
}

Once the EmailNotification class is complete, I will update NotificationActor and inject IEmailNotification to the constructor.

using Akka.Actor;
using System;

namespace Akka.Demo
{
    class NotificationActor : UntypedActor
    {
        private readonly IEmailNotification emailNotification;

        public NotificationActor(IEmailNotification emailNotification)
        {
            this.emailNotification = emailNotification;
        }

        protected override void OnReceive(object message)
        {
            Console.WriteLine($"Message received {message}");
            emailNotification.Send(message.ToString());
        }

        protected override void PreStart() => Console.WriteLine("Actor started");

        protected override void PostStop() => Console.WriteLine("Actor stopped");
    }
}

Configure Dependency Injection

Now I will update the Main method to introduce Dependency Injection. For that, I will create an instance of ServiceCollection. And register the EmailNotification and the NotificationActor into the dependency injection container.

var collection = new ServiceCollection();
collection.AddSingleton<IEmailNotification, EmailNotification>();
collection.AddSingleton<NotificationActor>();

After that, I will call BuildServiceProvider on the ServiceCollection instance. Which will return an instance of ServiceProvider.

var provider = collection.BuildServiceProvider();

Once I get an instance of the ServiceProvider, I will create the ActorSystem. And then call the extension method UseServiceProvider on the ActorSystem to add the ServiceProvider into the ActorSystem.

using var actorSystem = ActorSystem.Create("test-actor-system");
actorSystem.UseServiceProvider(provider);

And finally, instead of creating an instance of NotificationActor directly from Prop, I will use DI function of the ActorSystem for that.

using Akka.Actor;
using Akka.DI.Core;
using Akka.DI.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System;

namespace Akka.Demo
{
    class Program
    {
        static void Main(string[] args)
        {

            var collection = new ServiceCollection();
            collection.AddSingleton<IEmailNotification, EmailNotification>();
            collection.AddSingleton<NotificationActor>();
            var provider = collection.BuildServiceProvider();

            using var actorSystem = ActorSystem.Create("test-actor-system");
            actorSystem.UseServiceProvider(provider);

            var actor = actorSystem.ActorOf(actorSystem.DI().Props<NotificationActor>(), "first-actor");
            actor.Tell("Hello there!");
            Console.ReadLine();
            actorSystem.Stop(actor);
            Console.Read();
        }
    }
}

Now I will run the application and see the expected output in the console.

Creating Child Actors

Child actors are actors managed by a supervisor. Technically every actor we create is managed by the root actor. But we are going to discuss child actors we will create as a part of our project.

The child actors are similar to any other actor, meaning they also can derive from UntypedActor or ReceiveActor.

I will create a new actor named TextNotificationActor deriving from UntypedActor. This actor will be responsible for sending Text messages. For the time being, I am just going to write a message to the console.

using Akka.Actor;
using System;

namespace Akka.Demo
{
    class TextNotificationActor : UntypedActor
    {
        protected override void PreStart() =>
            Console.WriteLine("TextNotification child started");

        protected override void PostStop() => 
            Console.WriteLine("TextNotification child stopped");

        protected override void OnReceive(object message)
        {
            Console.WriteLine($"Sending text message {message}");
        }
    }
}

And just like the NotificationActor class, I will override the PreStart and PostStop method to print out in console when these events are fired.

Update in NotificationActor

Once I am done with creating the new child actor, It is time to update the NotificationActor to create the new child actor.

Hence, in the constructor of the NotificationActor, I will create the new Child actor through DI method.

public NotificationActor(IEmailNotification emailNotification)
{
    this.emailNotification = emailNotification;
    this.childActor = Context.ActorOf(Context.System.DI().Props<TextNotificationActor>());
}

Next, I will update the OnReceive method to call the Tell method of the child actor once the supervisor actor receives a message.

protected override void OnReceive(object message)
{
    Console.WriteLine($"Message received: {message}");
    emailNotification.Send(message?.ToString());
    childActor.Tell(message);
}

Update to Main method

Finally, I will update the Main method of the Program class. I will register the new TextNotificationActor class to the dependency injection container.

using Akka.Actor;
using Akka.DI.Core;
using Akka.DI.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System;

namespace Akka.Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            serviceCollection.AddSingleton<IEmailNotification, EmailNotification>();
            serviceCollection.AddSingleton<NotificationActor>();
            serviceCollection.AddSingleton<TextNotificationActor>();
            var serviceProvider = serviceCollection.BuildServiceProvider();

            using var actorSystem = ActorSystem.Create("test-actor-system");
            actorSystem.UseServiceProvider(serviceProvider);
            
            var actor = actorSystem.ActorOf(actorSystem.DI().Props<NotificationActor>());
            actor.Tell("Hello there!");
            
            Console.ReadLine();
            actorSystem.Stop(actor);
        }
    }
}

Now if I run the application, I will see the “Hello there!” message is printed by the new child actor as well.

child response for akka.net in .net core

Child Actor Supervision in Akka.Net in .Net Core

You might ask, when is supervision necessary? If we are implementing a highly concurrent distributed system, the biggest challenge is managing exception in components and how to recover from that. Usually its a lot of custom code and potential bugs. Also, there are challenges around talking across different threads.

How does Akka helps here

First, all the tasks for a component can be distributed across multiple child actors by the supervisor actor. And the child actor themselves also can delegate the work further into another set of child actors and so on.

Second, in case of an error, the supervisor can decide to either resume, stop, restart or escalate it further to one level up. Restarting an actor will reset the states, whereas resuming the actor will keep the accumulated states. In some cases, a restart might be a better option to start clean.

Supervision Strategy for Akka.Net in .Net Core

There are two supervision strategy available:

  1. OneForOneStrategy: As the name suggests, it supervision is one to one, which means only one failed child will get the instruction. This is the default strategy.
  2. AllForOneStrategy: Here all the child actors will get a single instruction, which means even if a child is not impacted by an exception, the strategy will affect all.

Implementation of supervision

Now its time to implement the strategy in the supervisor. In our case, it is the NotificationActor class. Here, I will override the SupervisorStrategy method and implement retries as well as strategy to resume, restart and stop.

using Akka.Actor;
using Akka.DI.Core;
using System;

namespace Akka.Demo
{
    public class NotificationActor : UntypedActor
    {
        private readonly IEmailNotification emailNotification;
        private readonly IActorRef childActor;

        public NotificationActor(IEmailNotification emailNotification)
        {
            this.emailNotification = emailNotification;
            this.childActor = Context.ActorOf(Context.System.DI().Props<TextNotificationActor>());
        }

        protected override void OnReceive(object message)
        {
            Console.WriteLine($"Message received: {message}");
            emailNotification.Send(message?.ToString());
            childActor.Tell(message);
        }

        protected override void PreStart() => Console.WriteLine("Actor started");

        protected override void PostStop() => Console.WriteLine("Actor stopped");

        protected override SupervisorStrategy SupervisorStrategy()
        {
            return new OneForOneStrategy(
                maxNrOfRetries: 10,
                withinTimeRange: TimeSpan.FromMinutes(1),
                localOnlyDecider: ex =>
                {
                    return ex switch
                    {
                        ArgumentException ae => Directive.Resume,
                        NullReferenceException ne => Directive.Restart,
                        _ => Directive.Stop
                    };
                }
                );
        }
    }
}

In the above code, I configured retries to 10 times within a minute. This is the number of times the child actor is allowed to restart within the time window specified. The negative value means no limit.

And for the exception directive, for ArgumentException the actor will resume as if nothing happened. For NullReferenceException it will restart the actor and move on. And for any other unknown exception, it will stop the actor.

Update to TextNotificationActor

Now I will update TextNotificationActor to throw exceptions based on different messages.

using Akka.Actor;
using System;

namespace Akka.Demo
{
    public class TextNotificationActor : UntypedActor
    {
        protected override void PreStart() =>
            Console.WriteLine("TextNotification child started");

        protected override void PostStop() => 
            Console.WriteLine("TextNotification child stopped");

        protected override void OnReceive(object message)
        {
            if (message.ToString() == "n")
                throw new NullReferenceException();
            if (message.ToString() == "e")
                throw new ArgumentException();
            if (string.IsNullOrEmpty(message?.ToString()))
                throw new Exception();
            Console.WriteLine($"Sending text message {message}");
        }
    }
}

The code is self-explanatory, it will throw a NullReferenceException when the message is "n", ArgumentException when the message is "e". And it will throw Exception when an empty message is passed.

Update to Main method

Next, I will make a couple of changes to the Main method.

  • Change the dependency injection registrations from Singleton instances to Scoped instances. This is necessary since we will be restarting the actor based on error. Restart creates a scope change, hence to get a new instance after the restart we need to use a Scoped instance in dependency injection container.
  • Instead of sending a single message, change the implementation to a while loop and get a message from the console input, through Console.ReadLine().
using Akka.Actor;
using Akka.DI.Core;
using Akka.DI.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System;

namespace Akka.Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            serviceCollection.AddScoped<IEmailNotification, EmailNotification>();
            serviceCollection.AddScoped<NotificationActor>();
            serviceCollection.AddScoped<TextNotificationActor>();
            var serviceProvider = serviceCollection.BuildServiceProvider();

            using var actorSystem = ActorSystem.Create("test-actor-system");
            actorSystem.UseServiceProvider(serviceProvider);
            
            var actor = actorSystem.ActorOf(actorSystem.DI().Props<NotificationActor>());
            
            Console.WriteLine("Enter message");
            while(true){
                var message = Console.ReadLine();
                if (message == "q") break;
                actor.Tell(message); 
            };
            Console.ReadLine();
            actorSystem.Stop(actor);
        }
    }
}

Running the application

Now, let us first run the application and send a message “Hello Akka.Net!” and see the response.

As expected, the message will be received by the supervisor. Which will then pass it to the child.

Next, I will send the message “n” to test the null reference exception case.

akka in .net core actor stop

And as expected the supervisor will print the message, but the message will not be printed by the child, since it will throw the error. And based on the supervision strategy the child actor will be restarted.

Next, I will send a message “e” to test the argument exception case.

akka in .Net Core actor resume

Here, since the strategy for ArgumentException was to resume, there is no effect on the child actor. It will continue functioning as usual.

Finally, I will send an empty message to test the general exception scenario.

akka in .net core actor stop

And here as you can see, based on the strategy the child actor will be stopped. And after that, if we send a message, the message for the child actor will go to the dead letter queue.

Conclusion

This is just getting the feet wet with the Akka in .Net Core. In this blog post, first, we have created a simple actor, configured it through dependency injection. And afterward created a child actor and implemented Akka.Net Supervision for managing exception cases.

I am going to cover all the features of Akka.Net that are relevant for the distributed system programming in this series.

Github URL for the code: https://github.com/choudhurynirjhar/akka-demo

YouTube URL: https://youtu.be/aUY8tu6j7wM