This is a sample application demonstrating some aspects of Akka.NET. I use this application for demos during talks I give on Building Actor Model systems using Akka.NET (download the slides).
This repo contains 3 folders:
Folder | Description |
---|---|
Start | The base demo with all actors running in 1 single process. |
Remoting | The demo with the sales actor running in a remote process (demonstrating Akka.Remoting). |
Persistence | The demo with the sales actor persisting state in Azure Table storage (demonstrating Akka.Persistence). |
This application simulates a central warehouse system with multiple stores that allow customers to scan products in order to purchase them. The system consists of the following parts:
- Inventory - keeps track of the amount of products in stock.
- Sales - keeps track of how many money the company has made on sales.
- Back-order - keeps track of products which need to be back-ordered (because of insufficient stock).
- Store - keeps tracks of scanners being used by customers in the store.
- Scanners - represent bar-code scanners used by customers to purchase products (by scanning them and entering an amount).
The following Actor hierarchy is used in the system:
For every store a Store actor is created. The store actor creates a Customer actor per customer. Every customer actor creates a Scanner actor to represent a bar-code scanner. In this demo, customers with a scanner are simulated by letting the customer actor repeatedly send a scheduled message to Self at random intervals.
The Sales actor accumulates all sales. Only products actually sold are added - back-ordered products will not show up in the sales report.
The BackOrder actor tracks the list of products that must be back-ordered because of insufficient stock.
Because multiple customers could order the same product at the same time, I needed to serialize access to the products' stock-amount. I solved this problem by letting the Inventory actor create a Product actor per available product in the inventory. Every product actor has as its state the amount of products in stock.
In my initial design, I tried to solve the concurrency problem by letting a single inventory actor handle this. While this solution worked, it introduced a performance bottle-neck at the inventory Actor. Its mailbox filled up quite rapidly which decreased the performance of the system.
This demonstrates that it's important that you experiment with several solutions for your problem or partition your problem in a different way in order to solve it efficiently using an Actor Model approach. My advice would be to always start drawing out the actor hierarchy on a whiteboard (or do some Event Storming) to validate your design before implementing it.
The Start solution contains the initial version of the demo application. All actors run in a single process. This process is the Warehouse console application. When this project is started, the application initializes and waits for a keypress before starting the simulation.
By default, only 1 store with 1 customer is simulated. You can increase this by setting the following variables:
- numberOfStores in Program.cs in the Warehouse project.
- _numberOfCustomers in StoreActor.cs in the Actors project.
Throughout the simulation, information about what's going on is printed to the console. I've used different colors to identify which actor prints the message:
Color | Emitted by |
---|---|
White | Store actor |
Yellow | Scanner actor |
Green | Product actor |
Cyan | Sales actor |
Red | BackOrder actor |
Make sure you browse the code of the start solution first to see how I've used Akka.NET to build this system. Make sure you're familiar with the structure of this solution before looking at the other sample solutions in this repo.
The Remoting solution demonstrates location transparency using Akka.Remote (see the documentation). It contains an additional project Sales which is a console application. In this solution, the Sales actor will be created in the process of this second console application. Make sure you set both the Warehouse project and the Sales project as start-up projects.
The code in this solution is almost identical to the code in the Start solution. The magic is primarily in the App.config of the console applications.
In the Warehouse project, remoting is configured:
actor {
provider = "Akka.Remote.RemoteActorRefProvider, Akka.Remote"
deployment {
/sales { remote = "akka.tcp://sales@localhost:9999" }
}
}
remote {
helios.tcp {
port = 0 # bound to a dynamic port assigned by the OS
hostname = localhost
}
}
Notice that the actor named /sales will be created at address: akka.tcp://sales@localhost:9999.
The app.config of the Sales project also contains remoting configuration:
actor {
provider = "Akka.Remote.RemoteActorRefProvider, Akka.Remote"
}
remote {
helios.tcp {
port = 9999
hostname = localhost
}
}
Notice that this process will be configured to listen for remoting messages on port 9999.
The Sales actor is changed slightly in this solution to make sure it dumps its sales report to the console after every sale.
The Persistence solution demonstrates Actor state persistence using Akka.Persistence (see the documentation) to enable an actor to store its state and survive restarts. By default, event-sourcing is used to store the state of the actor. This means that updating the state will always be done by handling events.
For this demo I've chosen to make the Sales actor persistent. I could've also chosen the Product actor - which would made more sense in real-life. But because the Sales actor was already living in a seperate process, I selected that one.
Akka.Persistence comes with several pluggable storage-providers. For this sample, I've chosen the Azure Table Storage provider. This means that you need to have the Azure Storage SDK with the local Storage Emulator installed on your machine in order to run this demo. Another option would be to configure a different storage provider (as described here).
The structure for this solution is basically the same as the Remoting solution with the following changes:
- In the app.config of the Sales project Akka.Persistence is configured.
- The Sales actor derives from ReceivePersistentActor to make it persistent.
In the constructor of the Sales actor you can see how I've used the Command method from the base-class to specify how the actor handles incoming commands by:
- creating an event based on the command,
- persist this event and
- update the state of the actor and handle the event using the Persist method from the base-class.
Also the constructor shows how the actor handles recovering from restarts using the Recover method from the base-class. In this sample I've used a generic JObject to deserialize the events from storage.
Notice how I use the convenient IsRecovering property from the base-class to determine whether I'm handling a new event as a result of command that came in (false) or I'm replaying a historical event (true).
In the solution I've included a Powershell script preparepersistence.ps1. This script does 2 things:
- Start the local Azure Storage Emulator.
- Delete any existing tables in development storage.
You can use this script to initialize the environment before running the sample.
This is sample code and should be treated as such. I've demonstrated only a small portion of the Akka.NET framework and possibilities. Also I've cut a lot of corners which I wouldn't ever do in production code.
Check out the Akka.NET documentation to get started with Akka.Net.
If you have some time, I would also advice you to do the Akka.NET Bootcamp.