ここから実際に gRPC で API を作成し、クライアント/サーバー間で通信していきます。まず「gRPC / MagicOnion 入門 (2) - 4 種類の通信方式」で紹介した最も簡単な Unary 通信から見て行きます。
Step.1 - サービス定義
最初のステップでは、サーバー側にどのような API を用意するのか、そのサービスのインターフェースを決定していきます。例えば以下のような感じになります。
using MagicOnion; namespace MagicOnionSample.ServiceDefinition { public interface ISampleApi : IService<ISampleApi> { UnaryResult<int> Sum(int x, int y); } }
非常にシンプルなインターフェースです。ポイントは以下の 3 点のみです。
IService<T>
インターフェースを実装するIService<T>
の型引数には自身のインターフェースを入れる- メソッドの戻り値を
UnaryResult<T>
にする
Step.2 - サービスの実装
Step.1 で定義したインターフェースを実装する形でサービスの中身を書いていきます。今回は加算メソッドなので、例えば以下のような感じになります。
using System; using MagicOnion; using MagicOnion.Server; using MagicOnionSample.ServiceDefinition; namespace MagicOnionSample.Service { public class SampleApi : ServiceBase<ISampleApi>, ISampleApi { //--- async/await をそのまま書ける public async UnaryResult<int> Sum(int x, int y) { Console.WriteLine($"(x, y) = ({x}, {y})"); await Task.Delay(10); // 何か非同期処理したり return x + y; } /* //--- 「await がないぞ!」と警告されるけれど、これでも OK //--- プロジェクトレベルで警告 CS1998 を抑制するのがオススメです public async UnaryResult<int> Sum(int x, int y) => x + y; //--- async を使わない書き方もできるけれどスマートじゃないので not recommend public UnaryResult<int> Sum(int x, int y) => new UnaryResult<int>(x + y); */ } }
実装も非常にシンプルだと思います。ポイントは以下の 3 点です。
ServiceBase<T>
を継承する- サービス定義インターフェースを実装する
ServiceBase<T>
の型引数にはサービス定義のインターフェースを入れる
また、UnaryResult<T>
は C# 7.0 で追加された Task-like に対応しているため、C# 7.0 以降であれば Task<T>
抜きで自然に async/await
を記述することができます。
作者である @neuecc さんによる解説記事はこちら。
ちなみに「async キーワードがあるのにメソッド本体に await がない」という警告 (CS1998) をプロジェクトレベルで抑制する場合は、プロジェクトのプロパティで以下のように設定します。
Step.3 - gRPC サーバーを起動
ここまでで API 本体の実装が終わったので、次に gRPC サーバーを起動します。以下のようなコードを書けば OK です。
using System; using Grpc.Core; using MagicOnion.Server; namespace MagicOnionSample.Service { class Program { static void Main() { //--- ここで動的にアセンブリを解釈し、通信されてきたデータと API 本体とのマップを作る var service = MagicOnionEngine.BuildServerServiceDefinition(); //--- API を公開する IP / Port / 認証情報などを決定し、gRPC サーバーを起動 var port = new ServerPort("localhost", 12345, ServerCredentials.Insecure); var server = new Server{ Services = { service }, Ports = { port } }; server.Start(); //--- exe が終了しちゃわないように Console.ReadLine(); } } }
これでサーバー側の実装はすべて完了です。実行すれば localhost:12345
で gRPC サーバーが起動します。
Step.4 - クライアントの実装
最後に、Step.3 までで実装した API を呼び出すクライアントを作成します。以下のような感じです。
using System; using System.Threading.Tasks; using Grpc.Core; using MagicOnion.Client; using MagicOnionSample.ServiceDefinition; namespace MagicOnionSample.Client { class Program { static void Main() => MainAsync().Wait(); static async Task MainAsync() { //--- API が公開されている IP / Port / 認証情報を設定して通信路を生成 var channel = new Channel("localhost", 12345, ChannelCredentials.Insecure); //--- 指定されたサービス定義用のクライアントを作成 var client = MagicOnionClient.Create<ISampleApi>(channel); //--- RPC 形式で超お手軽呼び出し var result = await client.Sum(1, 2); //--- 結果表示 Console.WriteLine($"Result : {result}"); Console.ReadLine(); } } }
見た通り、非常にシンプルな書き心地でサーバー側のメソッドを叩くことができます。
実行してみる
実行してみると (当然ですが) 以下のような結果が得られます。
あとは同様の手順で API を増やしていくだけです。簡単ですね!