C# Actors: Reliable & Robust software

Jason Field
6 min readMar 25, 2021
Photo by Ryunosuke Kikuno on Unsplash

Do you want server-side software that is engineered to be reliable and robust? Delivers exceptional value to your customer (or to the business where you work), and designed to scale from demands that start small, right up to the large? If so, then this series is for you.

I’ve been working with C# and .NET since its early days (.NET 2.0). Most of the time since then has been spent on server-side solutions for industries such as aviation, e-commerce, 24/7/365 manufacturing.

The common theme you’re working to with these types of systems, is that the system must work. All the time. It needs to handle failure (either internally or around it) gracefully and just recover — it just keeps going. Oh, and it needs to support significant performance requirements as well (that have a spiky load signature).

So you design your software to be robust. You design and engineer it to be reliable. You handle those scaling requirements.

The problem is, as software professionals, we normally try solving these requirements with one hand tied behind our back. Or more appropriately, our thinking tied to an old programming model.

Put simply — designing and building elegant, clean, and robust software that is multi-threaded and multi-core aware is Hard.

To write safe code includes mastering mutexes, semaphores, threads, critical sections and locks. You need to understand race conditions across multiple threads and be able to see how it all hangs together, before you even start writing the code. These are just the basics. You’ll also need to master writing code that is compliant with SOLID principles. Then you need to figure out how to test it in a way that mimics production loads.

Even if you are the genius who can, those that follow after you to maintain your code need to be as good as you, in order to understand it all. Therein lies the problem:

How can we write code that is simple, clean, consistent, and robust? Easy to read and maintain? And have it run across multiple threads and cores to meet those demanding performance requirements.

Simple. What would happen if we used a different programming model — one that is simple, clean and consistent; one that was still able to leverage the power of multi-threading and multi-core.

How? Enter the Actor model.

The Actor Model

The Actor model, first introduced in 1973, was motivated by the prospect of highly parallel computing across tens, hundreds, or thousands of machines and processors.

Does this sound familiar to what we’re seeing today? Our desktop computers have more multi-core CPUs inside than we have monitors; we have hyper threading, and water cooling thrown in for good measure. To top it all off, the Cloud is only accelerating this trend.

Trying to support the new normal of how we write software in this brave new world requires a different way of thinking. We need the Actor model. It was designed to solve this problem — without all of the razor sharp attachments that come from our default, programming models.

There are plenty or articles on what the actor model is. I’m not going to rehash those. What I’m going to talk about is how to leverage the Actor model in C#, to build reliable and robust server side software.

I’m going to show you how to do this using a production ready framework, called Akka.NET.

My goal is to help you shorten the learning curve required to be proficient with the actor model (both in thinking patterns and in practice)- and deliver high quality, robust server-side software when you design and develop with discipline.

Outline

What this series will start to unpack is based on the journey I’ve made so far, and the lessons I’ve learned over 25 years, to build reliable and robust server side software. I’ll show you how to leverage the Actor model to build that software with Akka.NET.

Along with way we’ll also cover some of the practices that will make your software easier to support, maintain, and understand. What I am going to do is to show you how to:

  • Understand the use cases where using actors is best.
  • Define Facades. What they are good for and how to get started.
  • Define bounded contexts and service boundaries.
  • Understand commands and notifications. Messages with purpose.
  • Use Supervision. Fault tolerance and workloads.
  • Master state management. Different messages for different states.
  • Use dependency management. When to use it, and how.
  • Define and use routers. Workload management.
  • Define and use simulators. Real traffic, sanitised data.

I’m going to show you some of the code and I’ll explain the reasons why I’ve taken the approach that I recommend, as we progress. This article lays the foundations for where we would use it, and how to identify where to start with an existing system, when introducing Akka.NET.

The Basics

Let’s start with the basics. What are the use cases where I’ve found Akka.NET to be well suited? These are applications or systems that are based around:

  • Event based architectures
  • Background processing (server side)
  • High volume throughput scenarios (100 — 10K requests per minute)
  • Integration to external APIs
  • Business problems where parallel processing has significant advantages
  • Integration to external hardware (devices with state)

For example, integrating your in-house e-commerce inventory system to external warehouses, to export sales orders for fulfilment. Each sales order is an atomic piece of data, it doesn’t need someone to action each item, and the faster you export means the faster it will be fulfilled (assuming you have the inventory at the warehouse of course). You could literally throw thousands or tens of thousands of sales orders at such a system and have it just chug away.

Generally, if you’re processing data in an unattended manner, and that data can be transformed, manipulated, processed, or distributed 24x7, these are sound starting points for selection. Oh and add to this, if this system were to go down it would have significant impact on the business.

Actor based systems can manage application integration risks really well and with greater simplicity. When there is significant risk to the business due to technology, there is significant benefit in reducing that risk. When you solve that issue, the business wins — and so will you (and your team if they are supporting the system).

What situations is it not well suited to? In my experience, whenever there is a request and immediate reply required; or put another way — pulling a response from a system (rather than pushing a response).

Akka.NET does support the request/reply pattern. However when you follow this pattern it tends to break your ability to write software that can really scale (via parallel processing). The framework (and the actor model in general) is designed around parallel processing and supervision (more on this later in the series).

Starters

It’s usually going to be the case that you won’t be starting from a clean slate. That’s the journey I’ve had to date. i.e. A software system already exists — its already been built, it’s evolved over time, and you’ll need to solve an issue with it. Most likely a reliability or performance issue — or even better, a reliability issue that only happens when parts of the system is under significant load or stress.

You aren’t going to be able to introduce actors into this software system wholesale. You’ll need laser focus - identify a well defined, smallish (in terms of functionality) part of the overall system that has the problem(s). Then identify a clear boundary — and define a facade pattern to wrap this boundary. This is what will become the starting point for our journey.

A clear boundary means clear responsibilities and acceptance criteria. It also means that code external to the facade will be still working to the old way of doing things. Inside will be a different story.

What I mean by this is that you’ll be defining a normal set of C# contracts that follow a request (or in limited cases a request/reply) set of methods on your facade. Inside we’ll be converting these calls into messages that the actors will process. We’ll cover this in detail in the next post.

As a benefit you’ll also have a clear way to test your new component, in isolation from the main system, unencumbered with all of its dependencies and quirks.

Summary

Hopefully this has provided you with the basics of where and how to start your own journey. Find and identify that problem area that’s giving you or the team consistent reliability or performance issues. Identify a way to encapsulate it using a facade pattern.

In the next post, we’ll start with a concrete example, and step by step work through how to build this sample subsystem.

I hope you’ll join me on this journey as we unpack how to simplify writing highly robust software, for high-performance problems, with C# and Akka.NET.

--

--

Jason Field

“Software engineering isn’t rocket science. Its just basic disciple. Every day.” (even if you do build rockets).