Actors and Messages: The Building Blocks of Akka.NET

The Actor Model is programming paradigm that is well suited to working in the world of asynchronous, distributed applications. As I described in a previous article, the Actor Model is message driven architecture in which every entity within the framework is an actor. Work gets done by message passing between actors. An actor receives a message and reacts to it by doing something or by passing another message onto another actor to do work the first actor can't or won't do.

Akka.NET, an open source technology created by the folks over at Petabridge.com, provides a way for .NET programmers to code using the Actor Model at the enterprise level. Akka.NET is the .NET port of Akka. Akka is a Java/Scala implementation of the Actor Model.

Akka.NET is a very large framework and takes a bit of time to master. However, there are two essential concepts you need to understand in order to get started. These essential concepts are actors and messages. Understanding actors and messages in an operational manner under Akka.NET is the purpose of this article.

In this article I am going to show you how I used Akka.NET and the Actor Model to create a demonstration program that emulates a stock trade. The demonstration program illustrates the basics of Message communication between Actors.

What do I need to know?

To get full benefit from reading the article, you would do well to have a good understanding of the Task object and the Task Parallel Library (TPL). We're not going to delve into Task extensively. But, Akka.NET is built on top of TPL and we will refer to Task when we cover message communication via Actor.Ask().

Actors and Messages

Trading stock on a real world stock exchange involves a number of actors that operate in an asynchronous manner. The actors communicate with each other via messages. For example, in the real world, a Stock Broker sends a message to a Floor Trader to buy 100 shares of IBM. This message might be sent as a telephone call, an email, or within a trading system. Should the message come as a phone call to the Floor Trader, the Floor Trader takes the Trade order (the message) and hangs up. Then, the Floor Trader makes the trade and calls the Stock Broker back, reporting that the Trade is done. The trade transaction is asynchronous and distributed. Who knows where the Floor Trader is? And, the Stock Broker is not kept on the phone while the Trade is taking place, thus the asynchronicity (see Figure 1).

Figure 1: In a message driven system, Actors communicate asynchronously with each other via messages.

Understanding the AkkaNetDemo Trade Scenario

The AkkaNetDemo program, the demonstration program for this article, emulates a trade scenario as follows:

A customer (console) declares a trade that he or she wants to make. The trade is defined as a comma delimited string, with the structure:

IBM,100,B

Where

IBM is the ticker symbol

100 is the number of shares

B is a symbol indicating Buy. (S indicates Sell)

The trade declaration is submitted to an object, TradingSystem, via TradingSystem.Trade(), (1, in Figure 2). The TradingSystem converts the trade declaration into a Trade message (2). The TradingSystem passes the message to the StockBroker as an "ask". The StockBroker determines whether to create a BuyFloorTrader, an instance of the FloorTrader class or a SellFloorTrader, depending on whether the Trade message declares TradeType.Buy or TradeType.Sell. Then, the StockBroker forwards the message onto the identified FloorTrader (3). The FloorTrader determines whether the number of shares in the Trade is over or under the trade limit. If under the trade limit, the FloorTrader makes the trade, and then creates a new Trade message indicating success. However, if the number of shares in the trade exceeds the trade limit, the FloorTrader creates a new Trade message, setting Trade.TradeStatus to TradeStatus.Fail (4).

The new Trade message is sent back to the StockBroker. The TradingSystem, which is waiting on the StockBroker to return a message, passes the response Trade message back to the console, thus completing the transaction. (5)

Figure 2: The AkkaNetDemo application emulates a trading session between a Stock Broker and Floor Trader within a Trading System

Understanding the Actors in the Demonstration Program

Before we go into the details of the messaging interaction between actors in the demonstration program, you need to understand the actors that are in the program. Table 1 describes these actors. Please review the table. Having an awareness of TradingSystem, StockBroker, and FloorTrader is important as we go into the details of the actors' function.

Actor

Description

TradingSystem

The root entity that creates a StockBroker based on trade type. A BUY trade creates a Buy Stock Broker; the SELL trade creates a Sell Stock Broker. The Trading System sends messages to the StockBroker

StockBroker

Creates a FloorTrader to do the trade. StockBroker forward messages to the Floor Trader.

FloorTrader

Executes the trade. HOWEVER, if the number of shares indicated in the trade exceeds the trade limit, the FloorTrader creates a Trade message with a Trade.TradeStatus = TradeStatus.Fail.

Table 1: The Actors of the AkkaNetDemo program

Figure 3 shows a formal object model diagram of the three Actor in play the AkkaNetDemo program.

Figure 3: The Actors in the AkkaNetDemo program

Understanding the System Hierarchy

Akket.NET supports a hierarchical relationship model. At the top of the relationship is the Akka.NET object, ActorSystem. In the AkkaNetDemo, the TradingSystem object is the place where the root ActorSystem is created.

Actors created by ActorSystem are top level actors. Actors are objects that inherit from Akka.NET's UntypedActor. Top level Actors create children Actors. In our little Stock Trading example, the StockBroker actor is a top level actor of the TradingSystem. The FloorTrader is a child of the StockBroker actor. Actors supervise their children. Supervision strategies are an important part of the Akka.NET Actor Model and will be discussed in future articles. For now, the important thing to understand is that once an Actor creates another Actor, that subsequent Actor is a child.

Communicating with Messages

Now that we've covered ActorSystem and Actors, let's take a look at the concept of a message as it applies to Akka.NET. In Akka.NET, a message is a Plain Old CSharp Object (POCO), a class. (The strong type nature of a Message is an essential concept of Akka.NET.) For example, the POCO shown in Listing 1 can be a message:

public namespace Messages
{
public class AlarmSet
{
//Make a constructor so time can be set
//only when the object is created
public AlarmSet(DateTime ringTime)
{
RingTime = ringTime
}
public DateTime RingTime{get; private set:}
}
}

Listing 1: You define an Akka.NET message using C# classes

AlarmSet is a trivial message, but one can well imagine what to do when receiving such a message. "Oh yeah, I need to set my clock to go off at the value of RingTime." Unlike full-blown, complex C# classes that do work, a class that defines a message does nothing more than contain information, similar to a structure.

The Importance of Immutability

Please notice that the AlarmSet message is immutable. The only way the property, RingTime, can be set is when the message is created. Nobody can come along later down the line and twiddle with things. Being immutable is a very important best practice of Akka.NET. When the message gets passed about to who knows where, being immutable means that nobody can alter the message contents when the message is in transit.

The AkkaNetDemo application has one message, Trade, shown in Figure 4. Trade inherits from the abstract class, AbstractTrade. This inheritance model is of my own doing. It has nothing to do with Akka.NET.

Figure 4: The Trade message describes a stock trade and its state.

The message, Trade, has properties by which an actor will determine behavior. A Trade message will be sent among Actors to perform work. Let's take a look at details of sending messages.

Sending Messages

As we mentioned above, an Actor sends a message to another Actor to get something done. One of the nice things about Akka.NET is that the framework uses intuative, nature language to describe sending a message. You use Actor.Tell(message) to send a message to an actor on a fire-and-forget basis. You use Actor.Ask(message) when you want to get to the underlying Task object in which the message is sent.

Under the Hood: Task Parallel Library

Akka.NET uses the .NET Task Parallel Library to do its thread management. As a result, whenever you Actor.Ask(message) you will get the underlying Task asking the return object. Also, you use Actor.Ask<MyReturnMessageType>(message) to get back the underlying message the Actor receives in response to the ask. (You'll see an Ask() scenario later in the sample code.)

Also, you use Actor.Forward(message) when you want an Actor to forward a received message on to another Actor. When you Forward() a message, information about the originating sender is passed along too.

So, let's review. There are three ways to pass a message from one Actor to another:

Actor.Send(message)

Task task = Actor.Ask<MyReturnMessageType>(message)

Actor.Forward(message)

Now that we know how to send a message, let's talk about processing messages in an Actor.

OnReceive(): An Actor's Message Clearing House

Actors inherit from an abstract class, UntypedActor. UntypedActor defines an abstract method, OnReceive(message. OnReceive(message) needs to be implemented by the class that inherits from UntypedActor. The internals of Akka.NET are smart enough to do all the automagic required for an Actor to receive a message and the pass it on to OnReceive(). OnReceive() is the place where message processing takes place.

When an Actor receives a message in OnReceive(message), the Actor can analyze the message and react to the analysis. That analysis might be to do some processing and then respond to the sender with another message. The Actor might decompose the message and create one or more new messages to send on to other existing actors or child actors it creates. Or, the Actor can simply forward the message onto another Actor.

The important thing to remember is that the OnReceive(message) is the place where processing will happen.

Understanding Context

Akka.NET is designed to be a loosely coupled, distributed, asynchronous system. A lot of the magic of the system is hidden at the programming level. When you send a message, using Tell(message), for example, it might seem as if you are doing nothing more than sending a simple POCO. This is not the case. When you invoke Actor.Tell(message), information about the Sender of the message tags along (no pun intended). Also, within a given Actor, there is an implicit property, Context. You can use Context to determine the parent of the Actor, using Context.Parent. You use Context.System to access the root ActorSystem, under which the Actor resides. You can get a reference to the Actor itself using Context.Self. As you begin to program with Akka.NET, you'll find yourself working with Context a lot.

Okay, so up to this point we've explored a lot of concepts. We've covered Actors, Messages, Tell(), Ask(), Forward(), and OnReceive(), as well as taken a glimpse at Context. And, we looked at the specific program flow and actors that are part of the AkkaNetDemo program. Now, let's move beyond concept into actually programming.

Coding the AkkaNetDemo Program

As mentioned at the beginning of this article, when we introduced the program flow of the AkkaNetDemo, the AkkaNetDemo console application decomposes a comma delimited string that describes a trade. The comma delimited string is sent to the console by the user. The string is split into ticker, share, and B (or S) values. These three values are sent to the TradingSystem by way of the method, TradingSystem.Trade(ticker, share, tradeType). Then, TradingSystem creates a StockBroker object that it will Ask() to do the trade. Listing 2 shows the code:

The reason we are having the TradingSystem ask the broker to send the Trade message is that we want to wait until the trade is complete before moving on to anything else. Remember that Actor.Ask<MyResponseType>(message) returns the underlying Task object. We'll invoke Task.Wait() to block the task, waiting for it to complete before allowing program control flow to move forward.

The way that a Task completes is when the Actor doing the "ask" gets a message back. That response message is reflected in the Task.Result.

The broker receives the Trade message and creates either a Buy Or Sell FloorTrader object. Then, the broker calls FloorTrader.Forward(message), passing the message provided by the TradingSystem (see Listing 3).

The FloorTrader inspects the message. If Trade.Shares is more than the TradeLimit, the FloorTrader creates a new Trade message, setting the Trade.TradeStatus = TradeStatus.Failure. Otherwise, the FloorTrader creates the new Trade message, setting the Trade.TradeStatus = TradeStatus.Success.

The FloorTrader uses the Sender object, implicit in the Actor object, to send the new Trade message back up the hierarchy (see Listing 4).

The console converts the Trade message to JSON and dumps the output to the screen.

//Call the static object, Trading System. TradingSystem will
//return the response message of the Trade.
Trade responseTradeMessage = TradingSystem.Trade(ticker,
shares, tradeType);
Console.WriteLine("The following is the result of your trade:\n");
//Convert the response Trade message into a JSON string and show it
var startColor = Console.ForegroundColor;
//Make the console text RED upon failure
if (responseTradeMessage.TradeStatus.Equals(TradeStatus.Fail))
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(JsonConvert.SerializeObject
(responseTradeMessage,Newtonsoft.Json.Formatting.Indented));
Console.ForegroundColor = startColor;

Listing 5: The AkkaNetDemo application console displays the returned Trade message as JSON.

Console output is shown below in Figures 5 and 6.

Figure 5: The AkkaNetDemo program converts the response Trade message to JSON when rendering on the console.

Figure 6: Failed trades are displayed in red

Putting It All Together

We've done a lot in this article. I showed you the very basics of using Actors and Messages to do a simple transactional scenario under Akka.NET. I showed you how to create a root Actor by using ActorSystem. I showed you how to create child actors by using Context. Also, we've discussed the three ways of sending a message: Actor.Tell(), Actor.Ask(), and Ask.Forward().

However, as much as we've done, we've only scratched the surface. Akka.NET is very big framework. Remember, Akka.NET is meant to be run on very big, dynamic, distributed systems in a fault tolerant manner. Actor supervision, monitoring, and routing are but a few of the topics we'll cover later one in subsequent articles. In the meantime, you might want take a look the Akka.NET bootcamps and video documentation on the GetAkka.Net site. Also, there is an active chat site that has a very helpful community.

As you can see, the Actor Model is well suited to modern distributed computing. Beginning to take the journey to master Akka.NET is a good first step toward being able to do modern programming in the world of dynamic, distributed computing.

About the Author

Robert Reselman

Bob Reselman is well known technology writer and software developer. He as written numerous books and articles about software and the making of software. Also, Bob is part of the technology cartooning duo, ROELBOB. You can view their work at: www.TheWorldInWhichWeLive.com. Bob lives in Los Angeles, CA with his wife, the musician Arlo Zoos and his pet, Itchy the Dog.