Learn the Basics of Microsoft Orleans


Building scalable and maintainable solutions can be challenging, especially when dealing with distributed systems. While microservices are a common approach, they aren't the only option for achieving scalability. In this post, I’ll introduce you to Microsoft Orleans, a powerful framework designed to simplify the process of building scalable, distributed applications. Whether you’re running on a single server or scaling across thousands of instances, Orleans offers a flexible and developer-friendly solution that might be exactly what your .NET application needs.
Overview of Microsoft Orleans
For those who have never heard about Microsoft Orleans, Microsoft Orleans is a cross-platform framework for building scalable, distributed apps that can easily scale to thousands of distributed instances but also function perfectly on just a single instance.
This framework is especially built with .NET developers in mind. It extends familiar concepts that .NET developers know (and hopefully love) from a single server environment to a multi-server environment. This all while also simplifying the complexities that come with a multi-server environment.
Core Concepts of Microsoft Orleans
To understand how this framework works, it's important to know it's based on the actor model. The actor model is a programming model where each actor is a small, concurrent, immutable object that encapsulates its own behavior and state. These actors can only communicate with each other through asynchronous messages.
Virtual Actors
Microsoft Orleans invented the idea of virtual actors. A virtual actor is an abstraction that provides a straightforward approach to building distributed applications. Within Microsoft Orleans, these are represented by Grains.
Microsoft also wrote about Virtual actors which you can read here if you want to learn more.
Grains
So grains are the centerpiece of the Orleans framework. Grains represent the (virtual) actors within the framework. Grains define the state, behavior and identity of an entity. An example could be an invoice, or a customer. Grains are identified with an user-defined key. This means that a grain can be accessed by other grains and clients by the key. To better show it, here is an visual representation of what a grain is:
Source: Microsoft Learn
To further understand grains, let’s implement one within .NET. To do this, we simply create a normal class that inherits the grain base class like this:
using System.Threading.Tasks;
using Orleans;
namespace MyOrleansApp.Grains
{
public interface IMyGrain : IGrainWithIntegerKey
{
Task<string> SayHello(string greeting);
}
public class MyGrain : Grain, IMyGrain
{
public Task<string> SayHello(string greeting)
{
return Task.FromResult($"Hello, {greeting}!");
}
}
}
In our example, we created two things, an interface and a class. The interface is needed since the Orleans framework forces us to use abstractions when talking to the grain. This is good since it helps us separating implementations and making the code easier to test. On this interface we implemented the IGrainWithIntegerKey
. This implementation tells Orleans our grain is identifiable with an integer. Not only does Orleans support integers as keys, it supports the following types of keys:
IGrainWithGuidKey
IGrainWithIntegerKey
IGrainWithStringKey
IGrainWithGuidCompoundKey
IGrainWithIntegerCompoundKey
The state of grains can be volatile and/or persistent. To accommodate and enable automatic scalability and failure recovery, a grain state is kept in memory while the grain is active. This also gives us lower latency and less load on the data stores.
The state of a grain can reside in four different phases:
Persisted
Activating
Active in memory
Deactivating
To understand, how this works, Microsoft made a visual representation:
Source: Microsoft Learn
Instantiation of grains is automatically performed on demand by the Orleans runtime. This happens when a client calls for the specific Grain. Grains that aren't used for a while are automatically removed from memory to free up resources. This is possible because of the stable identity a grain has, which enables developers to call the grain without knowing if it's already in memory or not. The Orleans runtime handles the responsibility of activating and deactivating, as well as placing and locating grains as needed.
Silo
When we are talking about Grains, these are separate entities. To actually scale and manage them, Microsoft Orleans put them in something called a 'Silo'. A Silo can be seen as an instance of the application. When a client makes a call for a specific Grain, the Orleans Runtime checks if the Grain already exists in a Silo and if not, it activates the Grain in a Silo. There are different ways we can configure the logic that is used to assign a Grain to a Silo, though these won't be covered in this post.
Cluster
So to manage all the Grains active in one or more Silos we need something to coordinate the work between the Silos. This is where the cluster comes in play. A cluster can be seen as a Group of one or more Silos. The cluster will coordinate all work between the silo. This enables us to use Grains as if we are using a single process. This also means we do not have to worry about knowing in which Silo a Grain is active, the Cluster manages it for us. To visualize the relationship between Clusters, Silos and grains we can use the following diagram:
Source: Microsoft Learn
Usecases for Microsoft Orleans
So when should we use this framework? If you know your .NET applications will need scaling, Microsoft Orleans can help a lot in achieving this goal. Orleans does this by making smart choices, like using a familiar OOP paradigm. Each entity is a Grain, which is single-threaded (so no concurrency issues) and the system easily scales to multiple instances (silos) without needing to manage networking and much more.
These features make Microsoft Orleans ideal for various use cases like IoT software, banking apps, and chat apps. However, Microsoft Orleans is not suitable for scenarios where memory needs to be shared between entities or where scaling is not required, due to its added complexity.
Wrapping up
In this blog, we covered the absolute basics of Microsoft Orleans, though it offers many additional features such as:
Easy persistence support through storage providers
Grain placement (the logic that decides in which silo a grain should be instantiated)
Timers and reminders (durable and non-durable scheduling mechanisms for grains)
Stateless workers
ACID transactions
Grain versioning
Journaled grains (enabling event sourcing)
Observers
Streams
As I explore the framework further, I'll write more about these features. For now, I'm working on a step-by-step guide to build a small Microsoft Orleans application.
EDIT: I’ve just published the guide here.
I hope you learned something new in this post, and if you have any questions, feel free to reach out to me.
For now, enjoy coding! :D
Subscribe to my newsletter
Read articles from Hans Muns directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Hans Muns
Hans Muns
Hi, I’m Hans, a .NET developer from the Netherlands with over five years of experience, mostly working with cloud technologies. I’m certified in Azure (AZ-104, AZ-204, AZ-400, AZ-305), CKAD, and PSM1, and I’m also a Microsoft Certified Trainer (MCT), so I enjoy sharing what I’ve learned along the way. I’m interested in just about every new tech that comes my way, which keeps me constantly learning. It’s a bit of a blessing and a curse, but it keeps things interesting. Outside of work, I’m really into strength training and try to hit the gym four times a week. It helps me stay focused and balanced. On this blog, I’ll be sharing my thoughts, tips, and lessons learned from my work in .NET and Azure, and hopefully, it’ll be useful to anyone on a similar path.