With the advent of .NET 6, C# 10 and Orleans 4.0 (preview) it seems like a great time to take a fresh look at Microsoft Orleans (which is now included in the official Microsoft docs!).
What is Orleans?
Orleans is an open source framework for building distributed systems in .NET.
This makes it easier for a developer to build software which will scale from a single computer to running across a cluster of hundreds of machines cooperating to deliver a single service.
Orleans works well for situations where fast response times are important, by keeping data in memory, or for concurrent problems where Orleans’ turn-based concurrency model can make it easy to reason about mutating state and thread safety.
Getting Started
Let’s by building a very simple Orleans sample, just to get up and running.
The only prerequisite you’ll need is .NET 6.0.
https://dotnet.microsoft.com/download/dotnet/6.0
You can then create a new console application project using the dotnet command line tool.
This will create a basic “Hello World!” console application.
We can add a references to the Orleans Nuget packages we’ll need.
A super-simple program that starts up an Orleans ‘Silo’ and calls a grain looks like this:
Breaking it down
There’s a lot going on in the above code, let’s break it down into the individual parts and explain the concepts.
Grain Interfaces
Let’s start with the IHelloWorld
interface:
Orleans’ great strength is that it allows you to connect together lots of machines into a cluster allowing you to run your code across these machines as if they were one big computer.
Just as you would create objects in a normal C# program, Orleans will create objects for you, on demand, and will distribute them across the computers in your cluster. We call these objects ‘Grains’.
You can then start sending messages to these objects and Orleans will automatically create (or activate) them somewhere in the cluster when required.
The client doesn’t need to know if the grains exist in memory or not, from the perspective of the client they are always available.
You need an interface to define the messages that a grain can receive. We create methods on the interface to represent each message the grain can receive.
In this case our IHelloGrain
has a single message (Hello
) which a single argument (name
). It doesn’t return any data, but
these methods must be asynchronous and therefore return a Task
. This interface inherits IGrainWithStringKey
, which means that it uses a string as it’s
identity, or primary key.
Grain Classes
We can implement the IHelloGrain
interface with the HelloGrain
class. With Orleans version 4 we can have POCO Grains. In previous versions it was necessary to
inherit a Gain
base class. You can still inherit from this class if you prefer.
Our method doesn’t do much, it’s intentionally simple, but in theory this is the
place to add “business logic”. The code in here
will always be executed in a turn-based concurrency model. This means that if we send multiple Hello
messages to the same grain at the same time in parallel,
the method will be executed in a serial way, taking turns, one after the other.
This greatly simplifies the programming model, removing the need for locks or thread-safe data structures, and reduces the chance of having race conditions and other unwanted emergent behaviors when the system is under load.
Building the Host
The Silo is the runtime that hosts grains. A silo is configured using the
HostBuilder
class, and then calling a .UseOrleans()
extension method.
In previous version of Orleans you would start by creating a
SiloHostBuilder
, but this has now been retired in favour of theHostBuilder
.
In this case we just create a local silo for development (using the .UseLocalHostClustering()
). The following line starts the Silo.
Sending Messages to a Grain
To send a message to a grain, first you need to create reference to it using the client. The HostBuilder creates
a client for us and registers it as a service, so we can use GetRequiredService
on the host’s services to get the client.
The client can then be used to get a reference to the grain.
The reference combines the interface for the grain (IHelloGrain
) and the identity, which in this case can be any string, and is hard-coded to "grain-key"
.
We finish by sending a message to the grain by calling the Hello
method:
Wrapping Up
It’s fun that we can create a ‘Hello World’ in a single file. Of course, this is not how we would build a production application, but it’s a great way of playing with Orleans, and getting a feel for how the framework works.
It’s interesting to compare this with the 2016 edition and seeing how both C# and Orleans have reduced boiler-plate and become slicker.