{"id":11980,"date":"2025-11-23T20:10:55","date_gmt":"2025-11-23T20:10:55","guid":{"rendered":"https:\/\/putridparrot.com\/blog\/?p=11980"},"modified":"2025-11-23T20:10:55","modified_gmt":"2025-11-23T20:10:55","slug":"getting-started-with-orleans","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/getting-started-with-orleans\/","title":{"rendered":"Getting started with Orleans"},"content":{"rendered":"<p>Microsoft Orleans is a cross platform framework for distributed applications. It&#8217;s based upon the Actor model which represents a lightweight, concurrent, immutable objects encapsulating state. <\/p>\n<p><strong>Basic Concepts<\/strong><\/p>\n<p>A <em>Grain<\/em> is a virtual actor and one of several Orleans primitives. A Grain is an entity which comprises of<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nidentity + behaviour + state\r\n<\/pre>\n<p>where an identity is a user-defined key.<\/p>\n<p>Grains are automatically instantiated on demand by the Orleans runtime and have a managed lifecycle with the runtime activating\/deactivating grains as well as placing\/locating grains as required.<\/p>\n<p>A <em>Silo<\/em> is a primitive which hosts one or more Grains. A group of silos run as a cluster and in this mode coordinates and distributes work. <\/p>\n<p>Orleans can handle persistence, timers and reminders along with flexible grain placement and versioning.<\/p>\n<p><strong>Use cases<\/strong><\/p>\n<p>Whilst grains can be stateless, the &#8220;sweet spot&#8221; for using Orleans is where you require distributed, durable and concurrent state management without locks etc. Long running stateful process such as event driven workflows are very much in the Orleans world.<\/p>\n<p>Where Orleans is not the best solution include stateless, computer heavy tasks, these are better suited to Azure functions (for example). In such situations Orleans just adds complexity. <\/p>\n<p><strong>Lifecycle<\/strong><\/p>\n<p>Orleans automatically manages the lifecycle of grains. A grain may be in one of the following states <em>activating<\/em>, <em>active<\/em>, <em>deactivating<\/em> and <em>persisted<\/em>. <em>Persisted<\/em> maybe wasn&#8217;t the state you first thought of when looking at the progress through other states. Let&#8217;s look at the states in a little more depth (although probably fairly self explanatory)<\/p>\n<ul>\n<li>Activation occurs when a request for a grain is received and the grain current state is not active. Hence the grain will be initialized and when active, can accept requests. An important point is that the grain will stay active based upon the fulfilment of requests.<\/li>\n<li>When a grain is active in memory it will accept requests but if it&#8217;s busy messages will be stored in a queue until the grain is ready to receive them. Whilst We can call the grain concurrently, due to the Actor model design, only one execution is permitted on the grains thread, ensuring thread safety.<\/li>\n<li>Deactivation takes place once the grain stops receiving requests for a period of time. This state is not yet persisted, however once we reach the persisted state it will be removed from memory.<\/li>\n<li>Persisted state is when a grain has been deactivated and it&#8217;s final state is stored in a database or other datastore.<\/li>\n<\/ul>\n<p>The framework takes care of the life cycle and allows the developer to just concentrate on using grains.<\/p>\n<p><strong>The Silo lifecycle<\/strong><\/p>\n<p>The silo&#8217;s lifecycle goes something like the following<\/p>\n<ul>\n<li>When created the silo initializes the runtime environment etc.<\/li>\n<li>Runtime services are started and the silo initializes agents and networking<\/li>\n<li>Runtime storage is initialized<\/li>\n<li>Runtime services for grains is started, includes grain type management, membership services and the grain directory<\/li>\n<li>Application layer services started<\/li>\n<li>The silo joins the cluster<\/li>\n<li>Once active the silo is ready to accept workload<\/li>\n<\/ul>\n<p><strong>Concurrency<\/strong><\/p>\n<p>Grains are virtual actors and hence based upon the Actor model which essentially has a single thread that accepts request\/messages. Hence when a grain is processing a request it&#8217;s in a busy state and in the default, turn based concurrency, other requests are queued. It&#8217;s possible to override turn-based concurrency to handle multiple messages on the same thread but this does come with potential risks around sharing a threads.<\/p>\n<p>Orleans maintains a relative small thread pool which is determined by the number of CPU cores in the system, therefore we must still be careful around any potential blocking of threads. Grains can use the .NET thread pool but this should be fairly rare and using async\/await should be used.<\/p>\n<p><strong>Mesage flow<\/strong><\/p>\n<p>Requests for a grain are passed from client to silo and then the request is passed onto the grain. When the grain has completed it&#8217;s work and if a response is required this will pass back to the silo and onto the client.<\/p>\n<p><strong>Let&#8217;s write some code<\/strong><\/p>\n<p>Let&#8217;s get to writing the equivalent of &#8220;Hello World&#8221; by creating two Console projects, mine are named Client and Silo. However I also want some interface and implementation of a HelloGrain, so you&#8217;ll need to create two libraries. I&#8217;ve named mine GrainInterfaces and Grains (not very imaginative I admit).<\/p>\n<p>In the Client project add the following nuget package <em>Microsoft.Orleans.Client<\/em>, so my packages are as follows in the .csproj<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&lt;PackageReference Include=&quot;Microsoft.Extensions.Hosting&quot; Version=&quot;9.0.10&quot; \/&gt;\r\n&lt;PackageReference Include=&quot;Microsoft.Extensions.Logging.Console&quot; Version=&quot;9.0.10&quot; \/&gt;\r\n&lt;PackageReference Include=&quot;Microsoft.Orleans.Client&quot; Version=&quot;9.2.1&quot; \/&gt;\r\n<\/pre>\n<p>Now in the Silot project add <em>Microsoft.Orleans.Hosting.Server<\/em>, I&#8217;m also wanting to host in Kubernetes so added <em>Microsoft.Orleans.Hosting.Kuberneres<\/em> and <em>Microsoft.Clustering.Kuberneres<\/em>. Finally I want to use the OrleansDashboard, so add the <em>OrleansDashboard<\/em> package, hence my .csproj looks like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&lt;PackageReference Include=&quot;Microsoft.Extensions.Hosting&quot; Version=&quot;9.0.10&quot; \/&gt;\r\n&lt;PackageReference Include=&quot;Microsoft.Extensions.Logging.Console&quot; Version=&quot;9.0.10&quot; \/&gt;\r\n&lt;PackageReference Include=&quot;Microsoft.Orleans.Hosting.Kubernetes&quot; Version=&quot;9.2.1&quot; \/&gt;\r\n&lt;PackageReference Include=&quot;Microsoft.Orleans.Server&quot; Version=&quot;9.2.1&quot; \/&gt;\r\n&lt;PackageReference Include=&quot;Orleans.Clustering.Kubernetes&quot; Version=&quot;8.2.0&quot; \/&gt;\r\n&lt;PackageReference Include=&quot;OrleansDashboard&quot; Version=&quot;8.2.0&quot; \/&gt;\r\n<\/pre>\n<p>Notice I also have the Microsoft.Extensions packaged to include logging and to create the host.<\/p>\n<p>For the GrainInterfaces project add the package <em>Microsoft.Orleans.Sdk<\/em> so the GrainInterfaces .csproj has this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&lt;PackageReference Include=&quot;Microsoft.Orleans.Sdk&quot; Version=&quot;9.2.1&quot; \/&gt;\r\n<\/pre>\n<p>and finally add the same to the Grains project but also let&#8217;s add <em>Microsoft.Extensions.Logging.Abstractions<\/em> for us to do some logging, so the .csproj should look like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&lt;PackageReference Include=&quot;Microsoft.Extensions.Logging.Abstractions&quot; Version=&quot;9.0.10&quot; \/&gt;\r\n&lt;PackageReference Include=&quot;Microsoft.Orleans.Sdk&quot; Version=&quot;9.2.1&quot; \/&gt;\r\n<\/pre>\n<p>For the GrainInterfaces add an interface IHello.cs which looks like this<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic interface IHello : IGrainWithIntegerKey\r\n{\r\n  ValueTask&lt;string&gt; SayHello(string greeting);\r\n}\r\n<\/pre>\n<p>Add a project reference in the Client to this project.<\/p>\n<p>Next up, the Grains project has a new class named HelloGrain.cs which looks like this<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class HelloGrain(ILogger&lt;HelloGrain&gt; logger) : Grain, IHello\r\n{\r\n  private readonly ILogger _logger = logger;\r\n\r\n  ValueTask&lt;string&gt; IHello.SayHello(string greeting)\r\n  {\r\n    _logger.LogInformation(&quot;&quot;&quot;\r\n            SayHello message received: &quot;{Greeting}&quot;\r\n            &quot;&quot;&quot;,\r\n            greeting);\r\n\r\n        return ValueTask.FromResult($&quot;&quot;&quot;\r\n            Client said: &quot;{greeting}&quot;\r\n            &quot;&quot;&quot;);\r\n    }\r\n\r\n    public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken)\r\n    {\r\n        if(reason.ReasonCode == DeactivationReasonCode.ShuttingDown)\r\n        {\r\n            MigrateOnIdle();\r\n        }\r\n\r\n        return base.OnDeactivateAsync(reason, cancellationToken);\r\n    }\r\n<\/pre>\n<p>For the server code, edit the Program.cs within the Client project as follows<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing Microsoft.Extensions.Logging;\r\nusing Microsoft.Extensions.Hosting;\r\nusing Microsoft.Extensions.DependencyInjection;\r\nusing GrainInterfaces;\r\n\r\nvar builder = Host.CreateDefaultBuilder(args)\r\n    .UseOrleansClient(client =&gt;\r\n    {\r\n        client.UseLocalhostClustering();\r\n    })\r\n    .ConfigureLogging(logging =&gt; logging.AddConsole())\r\n    .UseConsoleLifetime();\r\n\r\nusing var host = builder.Build();\r\nawait host.StartAsync();\r\n\r\nvar client = host.Services.GetRequiredService&lt;IClusterClient&gt;();\r\n\r\nvar friend = client.GetGrain&lt;IHello&gt;(0);\r\nstring response = await friend.SayHello(&quot;Hi Orleans&quot;);\r\n\r\nConsole.WriteLine($&quot;&quot;&quot;\r\n                   {response}\r\n\r\n                   Press any key to exit...\r\n                   &quot;&quot;&quot;);\r\n\r\nConsole.ReadKey();\r\n\r\nawait host.StopAsync();\r\n<\/pre>\n<p>For the server code, edit the Program.cs within the Silo project as follows<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing Microsoft.Extensions.Hosting;\r\nusing Microsoft.Extensions.Logging;\r\n\r\nvar builder = Host.CreateDefaultBuilder(args)\r\n    .UseOrleans(silo =&gt;\r\n    {\r\n        silo.UseLocalhostClustering()\r\n            .ConfigureLogging(logging =&gt; logging.AddConsole());\r\n        silo.UseDashboard(options =&gt; \r\n        {\r\n            options.HostSelf = true;       \/\/ Enables embedded web server\r\n            options.Port = 7000;           \/\/ Default port\r\n        });\r\n    })\r\n    .UseConsoleLifetime();\r\n\r\nusing IHost host = builder.Build();\r\n\r\nawait host.RunAsync();\r\n<\/pre>\n<p>If we&#8217;re wanting to run these project from a single solution then don&#8217;t forget to go to Visual Studio&#8217;s solution, right mouse click and select Configure Startup Projects from here select the Common Properties | Configure Startup Projects and then Multiple startup projects, set the Silo and Client project actions to <em>Start<\/em>.<\/p>\n<p><strong>Persistence<\/strong><\/p>\n<p>As mentioned previously grains can be have their state persisted by Orleans. If we edit the silo project, Program.cs we can add various types of persistence, table store, SQL database etc. but also for testing we can use an in memory storage.<\/p>\n<p>We just add the following to the UseOrleans method<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nsilo.AddMemoryGrainStorage(&quot;docStore&quot;);\r\n\/\/ Or Azure Table storage\r\n\/\/ silo.AddAzureTableGrainStorage(&quot;documentStore&quot;, options =&gt;\r\n\/\/ {\r\n\/\/     options.ConnectionString = &quot;&lt;your-connection-string&gt;&quot;;\r\n\/\/ });\r\n<\/pre>\n<p>Now in our HelloGrain we can add persistence as easily as the following, add the PersistanceState attribute as a ctor parameter for the IPersistentState object to be injected<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class HelloGrain(\r\n  &#x5B;PersistentState(&quot;hello&quot;, &quot;docStore&quot;)] IPersistentState&lt;State&gt; state, \r\n  ILogger&lt;HelloGrain&gt; logger) : Grain, IHello\r\n<\/pre>\n<p>The state name here is &#8220;hello&#8221; and the storageName should match the storage we set up in the Silo project.<\/p>\n<p>Now to save data on the state we just write the following in the SayHello method<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nstate.State.Data = greeting;\r\nawait state.WriteStateAsync();\r\n<\/pre>\n<p>Here&#8217;s a very simple State object example<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class State\r\n{\r\n    public string? Data { get; set; }\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Microsoft Orleans is a cross platform framework for distributed applications. It&#8217;s based upon the Actor model which represents a lightweight, concurrent, immutable objects encapsulating state. Basic Concepts A Grain is a virtual actor and one of several Orleans primitives. A Grain is an entity which comprises of identity + behaviour + state where an identity [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[156,765],"tags":[],"class_list":["post-11980","post","type-post","status-publish","format-standard","hentry","category-actor-model","category-orleans"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11980","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=11980"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11980\/revisions"}],"predecessor-version":[{"id":12023,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11980\/revisions\/12023"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=11980"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=11980"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=11980"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}