Orleans Hello World - 2016 Edition


Orleans has seen lots of improvements over the last year, in terms of the client APIs, the configuration and installation. So the process of getting started is now a little different. Let’s walk through setting up a grain, with a silo host and client application.

Creating the grains

First of all, open Visual Studio and create a class library which will host our grains.

Note that at the time of writing, the DLLs are all targeting .NET 4.5.1.

You can now put grain interfaces and implementations side-by-side in the same assembly, so let’s do that.

Is it a good idea to put grain interfaces and implementations in the same assembly? The advantages are that it can reduce the number of projects you have in you solution, and it could be used for ‘internal’ grains, which you don’t want to allow access to from outside the silo. The disadvantages are that you give the client code the implementation which you don’t want them to execute directly.

Orleans is now delivered by a number of nuget packages, so let’s install them:

PM> Install-Package Microsoft.Orleans.OrleansCodeGenerator.Build

We can now create an interface for our like, like this:

using Orleans;
using System.Threading.Tasks;

namespace TestGrains
{
    public interface IHelloWorldGrain : IGrainWithIntegerKey
    {
        Task<string> SayHello(string name);
    }
}

…and add an implementation:

using Orleans;
using System.Threading.Tasks;

namespace TestGrains
{
    public class HelloWorldGrain : Grain, IHelloWorldGrain
    {
        public Task<string> SayHello(string name)
        {
            return Task.FromResult($"hello {name}");
        }
    }
}

That’s the grains, now let’s create some calling code.

Creating the client

Let’s create a simple command line executable to act as a client.

Once added to the solution, add the client nuget packages:

PM> Install-Package Microsoft.Orleans.Client

…then add a reference to the project containing the grains.

We can now write a client application which connects to a local silo and calls the grain.

I have included some retry logic when attempting to initialise the client, so the application waits until the silo is ready.

using Orleans;
using Orleans.Runtime.Configuration;
using System;
using System.Threading;
using TestGrains;

class Program
{
    static void Main(string[] args)
    {
        // initialize the grain client, with some retry logic

        var initialized = false;
        while (!initialized)
        {
            try
            {
                GrainClient.Initialize(ClientConfiguration.LocalhostSilo());
                initialized = GrainClient.IsInitialized;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }
        }

        // get a reference to the grain from the grain factory

        var grain = GrainClient.GrainFactory.GetGrain<IHelloWorldGrain>(1);

        // call the grain

        var response = grain.SayHello("World").Result;

        Console.WriteLine(response);
        Console.ReadKey();
    }
}

Note previously the initialisation required a config file. This is being phased out in favour of programmatic configuration (see ClientConfiguration.LocalhostSilo()).

Creating the server

There are lots of ways to host an Orleans silo, but let’s write a separate console application to do that.

Create a command line application, and install the following nuget packages:

PM> Install-Package Microsoft.Orleans.Server
PM> Install-Package Microsoft.Extensions.DependencyInjection.Abstractions -Pre

Note the Dependency Injection package is a pre-release, which means it can’t be shipped with the Orleans nugets. This has to be installed separately.

using Orleans.Runtime.Configuration;
using System;

class Program
{
    static void Main(string[] args)
    {
        using (var silo = new Orleans.Runtime.Host.SiloHost("primary", ClusterConfiguration.LocalhostPrimarySilo()))
        {
            silo.InitializeOrleansSilo();

            var result = silo.StartOrleansSilo();
            if (result)
            {
                Console.WriteLine("silo running");
                Console.ReadKey();
                return;
            }
            Console.WriteLine("could not start silo");
        }
    }
}

Note we’re using the programmatic configuration in a similar way to the client.

Build the application, and then manually copy the DLL containing the grains into an bin\debug\Applications\ directory. This is one of the places the silo host will look when discovering grain assemblies.

Start both applications up, and you’ll have an Orleans client talking to a server :¬)