Orleans builds on the developer productivity of .NET and brings it to the world of distributed applications, such as cloud services. Orleans scales from a single on-premises server to globally distributed, highly-available applications in the cloud.
Orleans takes familiar concepts like objects, interfaces, async/await, and try/catch and extends them to multi-server environments. As such, it helps developers experienced with single-server applications transition to building resilient, scalable cloud services and other distributed applications. For this reason, Orleans has often been referred to as "Distributed .NET".
It was created by Microsoft Research and introduced the Virtual Actor Model as a novel approach to building a new generation of distributed systems for the Cloud era. The core contribution of Orleans is its programming model which tames the complexity inherent to highly-parallel distributed systems without restricting capabilities or imposing onerous constraints on the developer.
The fundamental building block in any Orleans application is a grain. Grains are entities comprising user-defined identity, behavior, and state. Grain identities are user-defined keys which make Grains always available for invocation. Grains can be invoked by other grains or by external clients such as Web frontends, via strongly-typed communication interfaces (contracts). Each grain is an instance of a class which implements one or more of these interfaces.
Grains can have volatile and/or persistent state that can be stored in any storage system. As such, grains implicitly partition application state, enabling automatic scalability and simplifying recovery from failures. Grain state is kept in memory while the grain is active, leading to lower latency and less load on data stores.
Instantiation of grains is automatically performed on demand by the Orleans runtime. Grains which are not used for a while are automatically removed from memory to free up resources. This is possible because of their stable identity, which allows invoking grains whether they are already loaded into memory or not. This also allows for transparent recovery from failure because the caller does not need to know on which server a grain is instantiated on at any point in time. Grains have a managed lifecycle, with the Orleans runtime responsible for activating/deactivating, and placing/locating grains as needed. This allows the developer to write code as if all grains were always in-memory.
Taken together, the stable identity, statefulness, and managed lifecycle of Grains are core factors that make systems built on Orleans scalable, performant, & reliable without forcing developers to write complex distributed systems code.
Consider a cloud backend for an Internet of Things system. This application needs to process incoming device data, filter, aggregate, and process this information, and enable sending commands to devices. In Orleans, it is natural to model each device with a grain which becomes a digital twin of the physical device it corresponds to. These grains keep the latest device data in memory, so that they can be quickly queried and processed without the need to communicate with the physical device directly. By observing streams of time-series data from the device, the grain can detect changes in conditions, such as measurements exceeding a threshold, and trigger an action.
A simple thermostat could be modeled as follows:
public interface IThermostat : IGrainWithStringKey
{
Task<List<Command>> OnUpdate(ThermostatStatus update);
}
Events arriving from the thermostat from a Web frontend can be sent to its grain by invoking the OnUpdate method which optionally returns a command back to the device.
var thermostat = client.GetGrain<IThermostat>(id);
return await thermostat.OnUpdate(update);
The same thermostat grain can implement a separate interface for control systems to interact with:
public interface IThermostatControl : IGrainWithStringKey
{
Task<ThermostatStatus> GetStatus();
Task UpdateConfiguration(ThermostatConfiguration config);
}
These two interfaces (IThermostat and IThermostatControl) are implemented by a single implementation class:
public class ThermostatGrain : Grain, IThermostat, IThermostatControl
{
private ThermostatStatus _status;
private List<Command> _commands;
public Task<List<Command>> OnUpdate(ThermostatStatus status)
{
_status = status;
var result = _commands;
_commands = new List<Command>();
return Task.FromResult(result);
}
public Task<ThermostatStatus> GetStatus() => Task.FromResult(_status);
public Task UpdateConfiguration(ThermostatConfiguration config)
{
_commands.Add(new ConfigUpdateCommand(config));
return Task.CompletedTask;
}
}