The life of a consultant is an exciting one! We are not only learning from past technologies and implementing current state of the art technologies. Occasionally we dabble with future (sometimes visionary) technologies and paradigms. Akka.Net is one of those technologies that can be found in all those time periods starting from the theoretical paper in 1973 to well into the foreseeable future.

Contents

What is Akka.Net

As a participant of the CC Foundation, a get together for people interested in architecture and security, we talked about some design patterns, architectural ideas and technologies. The one that peaked my interest was Akka.Net. The only caveat being my absolute lack of knowledge about the topic. The first thing I did was open my browser and use my trusty friend Google and opened the official website of Akka.Net.

The official website of Akka.Net (http://getakka.net) states that “Akka.NET is a toolkit and runtime for building highly concurrent, distributed, and fault tolerant event-driven applications on .NET & Mono.” So, a lot of fancy words… But is it all they claim it to be? To be frank, it is, and much more!

Akka.Net was build asynchronously and distributed from the ground up. All interactions are based on passing messages between different Actors. An actor can be described as a high-level abstraction of well… about anything. Its responsibility is to handle incoming messages or more explicitly as you program them to do. Because of the event driven nature and the possibility to create finite state machines, you can create resilient, self-healing applications more easily.

This blog post was created to show you the ease of setting up a basic project with Akka.Net. The focus of this post will be on the practicality of the base setup and where to go from there.

Let us dive in

Setting up the ActorSystem

First things first, let us create a new project. For this example, we will be creating a console application. We all get nostalgic and miss those days staring at a black screen with white or green text. I will be using .Net Core but full .Net will as well. I will be naming this application IntroductionToAkka

Next we will be adding the Akka.Net NuGet package either through the NuGet package manager or by executing following command in the package manager console:

Install-Package Akka

This package contains the core functionality to handle messages, create mailboxes (queues) and configure Actors. Next, we will create an Actor system. Open up Program.cs and add the following statements:

using Akka.Actor;
using IntroductionToAkka.Helpers;
using System;

namespace IntroductionToAkka
{
    class Program
    {
        private static ActorSystem StreamingActorSystem;

        static void Main(string[] args)
        {
            // Create the actor system with the name StreamingActorSystem
            StreamingActorSystem = ActorSystem.Create(nameof(StreamingActorSystem));
            ConsoleHelper.WriteColoredLine("Actor system created.");

            // Terminate the actor system
            (StreamingActorSystem.Terminate()).GetAwaiter().GetResult();
            Console.WriteLine("Actor system terminated");
            Console.ReadLine();
        }
    }
}

The previous code will create a root ActorSystem that will manage the lifetime of our Actors and will deal with the communication between them. We also added the command for terminating the ActorSystem, although this is not necessary, it is considered best practice. Akka.Net has the possibility to persist state/messages on shutdown or let Actors finish their current queue before terminating. This behavior can be configured by changing the Actors configuration. Triggering the Terminate command handles all things properly without giving it a second thought.

We also used a ConsoleHelper, this helper is responsible for visualizing our different type of messages. We will implement this helper in a moment. First, add these folders to the root of the application

  • Actors
  • Helpers
  • Messages

Next, add the code listed below:

using System;

namespace IntroductionToAkka.Helpers
{
    public class ConsoleHelper
    {
        public static void WriteColoredLine(string message, ConsoleColor color = ConsoleColor.White)
        {
            // Save the previous color
            var previousColor = Console.ForegroundColor;
            // Change the current console text color
            Console.ForegroundColor = color;
            Console.WriteLine(message);
            // Reset the color back to the previous
            Console.ForegroundColor = previousColor;
        }
    }
}

Adding your first Actor

To further our journey we will create a new PlaybackActor class in the Actors folder. Replace the contents with the snippet below:

using Akka.Actor;
using IntroductionToAkka.Helpers;
using IntroductionToAkka.Messages;
using System;

namespace IntroductionToAkka.Actors
{
    public class PlaybackActor : UntypedActor
    {
        private string _currentlyStreaming;

        public PlaybackActor()
        {

        }

        protected override void OnReceive(object message)
        {
            switch (message)
            {
                case PlayMessage m:
                    Play(m);
                    break;
                case StopMessage m:
                    Stop(m);
                    break;
                default:
                    Unhandled(message);
                    break;
            }
        }

        private void Play(PlayMessage item)
        {
            if (String.IsNullOrWhiteSpace(_currentlyStreaming))
            {
                ConsoleHelper.WriteColoredLine($"PlayMessage received, playing movie: {item.Title}", ConsoleColor.DarkGreen);
                this._currentlyStreaming = item.Title;
            }
            else
            {
                ConsoleHelper.WriteColoredLine($"ERROR: cannot start playing movie while another is playing", ConsoleColor.DarkGreen);
            }
        }


        private void Stop(StopMessage item)
        {
            if (String.IsNullOrWhiteSpace(_currentlyStreaming))
            {
                ConsoleHelper.WriteColoredLine($"ERROR: there was no movie playing", ConsoleColor.DarkGreen);
            }
            else
            {
                ConsoleHelper.WriteColoredLine($"StopMessage received, stopping movie", ConsoleColor.DarkGreen);
                this._currentlyStreaming = String.Empty;
            }
        }
    }
}

You have probably noticed is that our class is inheriting from UntypedActor. Because our class is inheriting from UntypedActor we also need to implement the OnReceive method. When a message is received it will be handled by the OnReceive method and handled accordingly. Congratulations, we now have our first Actor set up!

The incoming message parameter is of type object, this makes it ideal for pattern matching as introduced in C# 7.0. In our switch statement we can handle different types of messages and trigger custom actions or use Akka’s built in Unhandled() method. By executing Unhandled we can configure Akka to put these messages on the Actor’s event stream and transform these messages to debug messages.

Communicating through messages

We have already implemented the code for the Actor and defined actions based on 2 types of messages, the PlayMessage and the StopMessage. In the next steps of our example we will create classes for both message types and add them to the Messages folder.

PlayMessage.cs

namespace IntroductionToAkka.Messages
{
    public class PlayMessage
    {
        public string Title { get; private set; }
        public string Username { get; private set; }

        public PlayMessage(string title, string username)
        {
            Title = title;
            Username = username;
        }
    }
}

StopMessage.cs

namespace IntroductionToAkka.Messages
{
    public class StopMessage
    {
        public string Username { get; private set; }

        public StopMessage(string username)
        {
            Username = username;
        }
    }
}

Now let us talk

We have configured our Actor and created our messages, now it is time to communicate with our Actor. To accomplish this, we actually need to create the actor and tell him what actions to execute. Open up Program.cs and paste in this content:

using Akka.Actor;
using IntroductionToAkka.Actors;
using IntroductionToAkka.Helpers;
using IntroductionToAkka.Messages;
using System;

namespace IntroductionToAkka
{
    class Program
    {
        private static ActorSystem StreamingActorSystem;

        static void Main(string[] args)
        {
            ...
            ConsoleHelper.WriteColoredLine("Actor system created.");

            // Create the playbackActor
            Props playbackActorProps = Props.Create<PlaybackActor>();
            IActorRef playbackActorRef = StreamingActorSystem.ActorOf(playbackActorProps, nameof(PlaybackActor));

            var user = "Timmy";
            var movie1 = "North Park - The Movie";
            var movie2 = "Eastworld";

            // Start playing movie1
            ConsoleHelper.WriteColoredLine($"Sending PlayMessage for {movie1}", ConsoleColor.Cyan);
            playbackActorRef.Tell(new PlayMessage(movie1, user));
            Console.ReadLine();

            // Try playing movie2 while movie1 is playing
            ConsoleHelper.WriteColoredLine($"Sending PlayMessage for {movie2}", ConsoleColor.Cyan);
            playbackActorRef.Tell(new PlayMessage(movie2, user));
            Console.ReadLine();

            // Stop the current movie
            ConsoleHelper.WriteColoredLine($"Sending StopMessage for {user}", ConsoleColor.Red);
            playbackActorRef.Tell(new StopMessage(user));
            Console.ReadLine();

            // Try stopping the movie when no movie is playing
            ConsoleHelper.WriteColoredLine($"Sending StopMessage for {user}", ConsoleColor.Red);
            playbackActorRef.Tell(new StopMessage(user));
            Console.ReadLine();

            // Start playing movie1 again
            ConsoleHelper.WriteColoredLine($"Sending PlayMessage for {movie1}", ConsoleColor.Cyan);
            playbackActorRef.Tell(new PlayMessage(movie1, user));
            Console.ReadLine();

            // Kill the playbackActor
            ConsoleHelper.WriteColoredLine("Killing the UserActor", ConsoleColor.Cyan);
            playbackActorRef.Tell(PoisonPill.Instance);
            Console.ReadLine();

            // Try sending message after killing actor
            ConsoleHelper.WriteColoredLine($"Sending PlayMessage for {movie1}", ConsoleColor.Cyan);
            playbackActorRef.Tell(new PlayMessage(movie1, user));
            Console.ReadLine();

            // Terminate the actor system
            ...  
        }
    }
}

The previous code adds a whole scenario to our program. Instead of just adding 1 message, we will be sending a lot of messages. Because let us face it, this is what the Actor Model is all about.

The first thing we did, was creating the actor and adding it to our ActorSystem.

// Create the playbackActor
Props playbackActorProps = Props.Create<PlaybackActor>();
IActorRef playbackActorRef = StreamingActorSystem.ActorOf(playbackActorProps, nameof(PlaybackActor));

Props.Create is an Akka.Net method for creating the Actor ‘recipe’ and optionally adding additional configuration to it. A recipe in Akka.Net can be compared to an ordinary recipe with different ingredients and procedures. The combination of these ingredients being added and handled by these procedures will create a finished product or Actor in this case. We told the ActorSystem to use its factory method ActorOf to create an Actor based on this recipe by handing over the recipe and choosing a reference name.

We scripted our scenario to start watching a movie. Afterwards, we try to play another movie without stopping the previous one, stop the movie that is playing. Finally, we try to stop a movie while there is none playing. These actions will be executed by creating messages and passing them to our Actor reference by using the Tell method. Example below:

playbackActorRef.Tell(new PlayMessage(movie1, user));

But what if we want to stop our Actor from receiving messages? Well it sounds like something out of a crime scene… a poison pill.

playbackActorRef.Tell(PoisonPill.Instance);

If everything was setup correctly, build the project and start our application.

Do not be a star, share the stage

Time to spice things up a bit and add another actor that will keep count of how many times a movie was played.

Create a new IncrementPlayCountMessage.cs file in the Messages folder:

namespace IntroductionToAkka.Messages
{
    public class IncrementPlayCountMessage
    {
        public string Title { get; private set; }

        public IncrementPlayCountMessage(string title)
        {
            Title = title;
        }
    }
}

Add this code to Program.cs:

...
ConsoleHelper.WriteColoredLine("Actor system created.");

// Create the counterActor
Props counterActorProps = Props.Create<CounterActor>();
IActorRef counterActorRef = StreamingActorSystem.ActorOf(counterActorProps, nameof(CounterActor));

// Create the playbackActor
...

Create a new CounterActor.cs file in the Actors folder:

using Akka.Actor;
using IntroductionToAkka.Helpers;
using IntroductionToAkka.Messages;
using System;
using System.Collections.Generic;

namespace IntroductionToAkka.Actors
{
    public class CounterActor : ReceiveActor
    {
        private readonly Dictionary _playCounts;

        public CounterActor()
        {
            _playCounts = new Dictionary();

            Receive<IncrementPlayCountMessage>(msg => HandleIncrementMessage(msg));
        }

        public void HandleIncrementMessage(IncrementPlayCountMessage msg)
        {
            if(_playCounts.ContainsKey(msg.Title))
            {
                _playCounts[msg.Title]++;
            }
            else
            {
                _playCounts.Add(msg.Title, 1);
            }

            ConsoleHelper.WriteColoredLine($"Movie {msg.Title} was played {_playCounts[msg.Title]} time(s)", ConsoleColor.Gray);
        }
    }
}

What happened here? Instead of inheriting from UntypedActor we inherited from ReceiveActor. Now we do not need to implement the OnReceive method and we get message typing out of the box! In our constructor we defined that we want to receive messages from the IncrementPlayCountMessage type. We also specified which method to call when a message of this type has been received. By inheriting from ReceiveActor we also do not need to specify the Unhandled method anymore, Akka.Net will take care of this. It is a lot cleaner to use the ReceiveActor, it also gives us a lot more options as you can find out by reading the documentation on the Akka.Net website.

Open up PlaybackActor.cs and change the code as stated below:

...
private void Play(PlayMessage item)
{
    if (String.IsNullOrWhiteSpace(_currentlyStreaming))
    {
        ConsoleHelper.WriteColoredLine($"PlayMessage received, playing movie: {item.Title}", ConsoleColor.DarkGreen);
        this._currentlyStreaming = item.Title;

        Context.ActorSelection("/user/CounterActor")
            .Tell(new IncrementPlayCountMessage(item.Title));
    }
    else
    ...
}
...

If PlaybackActor receives a message of the type PlayMessage and there is no movie currently playing, there will be a message sent to the CounterActor by executing:

Context.ActorSelection("/user/CounterActor")
    .Tell(new IncrementPlayCountMessage(item.Title));

This command will tell the current ActorySystem to look for an Actor found under the root (/user) that is named CounterActor and send a message to his mailbox. Now, tart the project to see if our finished code does not contain any mistakes and works as expected.

Conclusion

As you have seen, starting with Akka.Net is really easy. It shields us developers from dealing with things like scaling and concurrency if we want to, or gives us control when we need to. There are a lot more things to explore in Akka.Net and a lot of powerful features to discover. With the popularity of Microservices and distributed computing, Akka.Net really shines and shows us that things neither have to be hard or time consuming.

Marc is a Software Engineer at Ordina Belgium. At Ordina he is a full stack .Net developer working with both front-end and back-end technologies. Passionate about finding the right "cure" he is always looking for new ways to develop his skills. Marc is constantly searching to improve quality and efficiency for both enterprise applications and himself.