Microsoft Orleans チュートリアル：最小の Orleans アプリケーションを作成する

![grain_formulation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/d5b3f52e-d87e-d484-cc79-0f962ac8102a.png)
堅牢でスケーラブルな分散アプリケーションのためのクロスプラットフォームフレームワーク [Microsoft Orleans](https://dotnet.github.io/orleans/) のチュートリアル [Tutorial One - Creating a Minimal Orleans Application](https://dotnet.github.io/orleans/docs/tutorials_and_samples/tutorial_1.html) に従ってアプリケーションを実装する手順をメモっとく。

# 検証環境

* Visual Studio 2019 16.9.3
    * インストール済みワークロード：.NET Core クロスプラットフォームの開発
* Orleans 3.4.2

# プロジェクトをセットアップする

下記に示す4つのプロジェクトを作成する。

* Grain のインターフェイスを含む GrainInterfaces クラスライブラリプロジェクト
* Grain の実装を含む Grains クラスライブラリプロジェクト
* Silo をホストする Silo コンソールプロジェクト
* クライアントになる Client コンソールプロジェクト

本節のセットアップが完了するとソリューションは最終的に下記のようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/23f05cf1-52c0-0f5c-00df-543aa45ce14e.png)

## 構造を作成する

Silo コンソールアプリケーションを作成する。ソリューション名は Orleans Basics にする。ターゲットフレームワークは .NET Core 3.1 にする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/eb79544b-2bd8-e27a-8bf6-7ae45eb03a3f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/9910b29a-fd80-5ee3-c537-350a0417fe41.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/40a38046-8bc5-c91f-56a8-a0d5970bb030.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/4139e8f6-5054-6182-f401-d4a173ab4e80.png)

ソリューションを右クリックして、「追加」から「新しいプロジェクト」を選択し、Client コンソールアプリケーションを作成する。ターゲットフレームワークは .NET Core 3.1 にする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/a8ef34f0-6454-8935-3b3b-380aa2017d9d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/d804f040-5782-dcdc-1fbe-5fee884ff7ed.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/0901c1ac-2b81-386a-3e27-f1c85e204d7d.png)

同様に、GrainInterfaces クラスライブラリを作成する。ターゲットフレームワークは .NET Standard 2.1 にする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/17c039a3-4a43-96ba-25d0-e2144c302872.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/4b09730c-48c7-aa50-17fe-7c9ce5e3589a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/afbad18e-56bb-c47c-1e76-ee6c651fed8d.png)

同様に、Grains クラスライブラリを作成する。ターゲットフレームワークは .NET Standard 2.1 にする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/b14073b6-2516-632a-2e31-aa76086c11c7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/d6895901-6e44-74fd-f850-b6fb9e4a441f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/24b86c9f-8c54-1bf0-cc57-28d463d4ceac.png)

## デフォルトソースファイルを削除する

GrainInterfaces プロジェクトの Class1.cs ファイルと Grains プロジェクトの Class1.cs ファイルを削除する。

## 参照を追加する

Grains プロジェクトの依存関係を右クリックし、「プロジェクト参照の追加」を選択する。Grains プロジェクトが GrainInterfaces プロジェクトを参照するように設定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/83338c48-a7b3-b838-3acd-6ad8696e0730.png)

同様に、Silo プロジェクトが GrainInterfaces プロジェクトと Grains プロジェクトを参照するように設定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/afcb75cf-98af-2adc-21f0-6429b073f37a.png)

同様に、Client プロジェクトが GrainInterfaces プロジェクトを参照するように設定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/0b7ead3e-e449-bc1e-1f4b-26704c1f0464.png)

## NuGet パッケージを追加する

下記の表にあるとおり各プロジェクトに NuGet パッケージを追加する。

| プロジェクト         | NuGet パッケージ                             |
|-----------------|-------------------------------------------|
| Silo            | Microsoft.Orleans.Server                  |
| Silo            | Microsoft.Extensions.Logging.Console      |
| Client          | Microsoft.Extensions.Logging.Console      |
| Client          | Microsoft.Orleans.Client                  |
| GrainInterfaces | Microsoft.Orleans.Core.Abstractions       |
| GrainInterfaces | Microsoft.Orleans.CodeGenerator.MSBuild   |
| Grains          | Microsoft.Orleans.CodeGenerator.MSBuild   |
| Grains          | Microsoft.Orleans.Core.Abstractions       |
| Grains          | Microsoft.Extensions.Logging.Abstractions |

例えば、Silo プロジェクトに Microsoft.Orleans.Server パッケージを追加するときは、Silo プロジェクトを右クリックし、「NuGet パッケージの管理」を選択する。「参照」タブを選択し、検索キーワードとして Microsoft.Orleans.Server を入力し、Microsoft.Orleans.Server パッケージを選択し、インストールをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/4f5872c7-bcf0-794a-5f9a-86b7787c7e9d.png)

# Grain インターフェイスを定義する

GrainInterfaces プロジェクトを右クリックし、「追加」から「新しい項目」を選択する。IHello インターフェイスを IHello.cs という名前で追加する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/723d8eda-d32e-0a65-64d8-aee0a075aeb1.png)

IHello.cs を下記のように修正する。

```csharp
using System.Threading.Tasks;

namespace OrleansBasics
{
    public interface IHello : Orleans.IGrainWithIntegerKey
    {
        Task<string> SayHello(string greeting);
    }
}
```

# Grain クラスを定義する

Grains プロジェクトを右クリックし、「追加」から「新しい項目」を選択する。Grains プロジェクトに HelloGrain クラスを HelloGrain.cs という名前で追加する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/a5447051-2ce2-4ce4-d367-8e4926faf75b.png)

HelloGrain.cs を下記のように修正する。

```csharp
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace OrleansBasics
{
    public class HelloGrain : Orleans.Grain, IHello
    {
        private readonly ILogger logger;

        public HelloGrain(ILogger<HelloGrain> logger)
        {
            this.logger = logger;
        }

        Task<string> IHello.SayHello(string greeting)
        {
            logger.LogInformation($"\n SayHello message received: greeting = '{greeting}'");
            return Task.FromResult($"\n Client said: '{greeting}', so HelloGrain says: Hello!");
        }
    }
}
```

# Silo を作成する

Silo プロジェクトの Program.cs を下記のように修正する。

```csharp
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Configuration;
using Orleans.Hosting;

namespace OrleansBasics
{
    public class Program
    {
        public static int Main(string[] args)
        {
            return RunMainAsync().Result;
        }

        private static async Task<int> RunMainAsync()
        {
            try
            {
                var host = await StartSilo();
                Console.WriteLine("\n\n Press Enter to terminate...\n\n");
                Console.ReadLine();

                await host.StopAsync();

                return 0;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return 1;
            }
        }

        private static async Task<ISiloHost> StartSilo()
        {
            // define the cluster configuration
            var builder = new SiloHostBuilder()
                .UseLocalhostClustering()
                .Configure<ClusterOptions>(options =>
                {
                    options.ClusterId = "dev";
                    options.ServiceId = "OrleansBasics";
                })
                .ConfigureApplicationParts(parts => parts.AddApplicationPart(typeof(HelloGrain).Assembly).WithReferences())
                .ConfigureLogging(logging => logging.AddConsole());

            var host = builder.Build();
            await host.StartAsync();
            return host;
        }
    }
}
```

# Client を作成する

Client プロジェクトの Program.cs を下記のように修正する。

```csharp
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Configuration;
using System;
using System.Threading.Tasks;

namespace OrleansBasics
{
    public class Program
    {
        static int Main(string[] args)
        {
            return RunMainAsync().Result;
        }

        private static async Task<int> RunMainAsync()
        {
            try
            {
                using (var client = await ConnectClient())
                {
                    await DoClientWork(client);
                    Console.ReadKey();
                }

                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine($"\nException while trying to run client: {e.Message}");
                Console.WriteLine("Make sure the silo the client is trying to connect to is running.");
                Console.WriteLine("\nPress any key to exit.");
                Console.ReadKey();
                return 1;
            }
        }

        private static async Task<IClusterClient> ConnectClient()
        {
            IClusterClient client;
            client = new ClientBuilder()
                .UseLocalhostClustering()
                .Configure<ClusterOptions>(options =>
                {
                    options.ClusterId = "dev";
                    options.ServiceId = "OrleansBasics";
                })
                .ConfigureLogging(logging => logging.AddConsole())
                .Build();

            await client.Connect();
            Console.WriteLine("Client successfully connected to silo host \n");
            return client;
        }

        private static async Task DoClientWork(IClusterClient client)
        {
            // example of calling grains from the initialized client
            var friend = client.GetGrain<IHello>(0);
            var response = await friend.SayHello("Good morning, HelloGrain!");
            Console.WriteLine($"\n\n{response}\n\n");
        }
    }
}
```

# アプリケーションを実行する

Silo プロジェクトを右クリックし、「デバッグ」から「新しいインスタンスの開始」を選択する。そうすると、下記のようなコンソールが表示され、大量の起動ログと共に Silo アプリケーションが起動する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/32774f91-89b7-c805-6f03-304b07347397.png)

Silo アプリケーションを実行した状態で、折りたたまれているソリューションエクスプローラーにある Client プロジェクトを右クリックし、「デバッグ」から「新しいインスタンスの開始」を選択する。そうすると、下記のようなコンソールが表示され、Client アプリケーションが起動する。起動すると即座に Silo アプリケーションで動作している HelloGrain に SayHello メッセージを送信する。Client は "Good morning, HelloGrain!" をメッセージの引数として送信し、HelloGrain は "Hello!" と返信している。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/4fcba078-f430-d315-8eb1-ef7f2adf7d93.png)

Silo アプリケーションのコンソールにもログが記録され、"Good morning, HelloGrain!" という引数を伴う SayHello メッセージが届いたことを記録している。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67627/2a482e20-9bb0-879b-9c9f-5250aed00f99.png)

---

今回は堅牢でスケーラブルな分散アプリケーションのためのクロスプラットフォームフレームワーク Microsoft Orleans のチュートリアル Tutorial One - Creating a Minimal Orleans Application に従ってアプリケーションを実装する手順について説明した。Orleans の有用性を示す実用的な例ではないので、今後は Orleans の実用性について理解を深めたい。

# 参考文献

https://github.com/dotnet/orleans
https://dotnet.github.io/orleans/