ここまで gRPC / MagicOnion を基本的な通信方法について見てきました。しかし、送受信に利用した型はすべて Primitive 型ばかりでした。独自に定義した型で通信したいと思うのは当然!ということで、今回は独自型でサーバー / クライアント間のやり取りをする方法について解説していきます。
MessagePack for C# のルールに従う
MagicOnion で通信されるデータのシリアライズには MessagePack for C# が利用されています。これにより MessagePack for C# が提供する超高速なシリアライズの恩恵を受けられるだけでなく IDL も不要になるため、.NETer が gRPC を使う環境としては最高に快適なものとなっています。
MagicOnion のシリアライザとして MessagePack for C# が利用されているということは、独自型をやり取りする場合も MessagePack for C# のルールに則るということになります。
独自型を定義する
通信でやりとりする独自型はサーバーとクライアントで型が共有されなければなりません。なので、サービス定義のプロジェクト内 (もしくはそれに類するサーバーとクライアントで共有可能なプロジェクト) に型を定義します。例えば以下のようにします。
using MessagePack; namespace MagicOnionSample.ServiceDefinition { //--- MessagePack for C# によるシリアライズ対象であることマーク [MessagePackObject] public struct Vector2 { //--- バイナリのレイアウトの順番を設定 [Key(0)] public float X { get; } [Key(1)] public float Y { get; } //--- デシリアライズに使うコンストラクタであることをマーク //--- ※ルールに沿っていれば必須ではない [SerializationConstructor] public Vector2(float x, float y) { this.X = x; this.Y = y; } } }
MessagePack for C# のルールに則った記述をソラでするのはなかなかに厳しいと思います。そんなときに便利なのが MessagePackAnalyzer です。サービス定義のプロジェクトに NuGet からアナライザーを取得して追加しておきましょう。
PM> Install-Package MessagePackAnalyzer
MessagePack for C# に関する詳細は README を参照ください。
独自型を利用して通信する
独自型の定義ができたので、これを利用して実装してみましょう。例えば以下のようにすれば OK です。
using MagicOnion; using MessagePack; namespace MagicOnionSample.ServiceDefinition { public interface ISampleApi : IService<ISampleApi> { //--- 独自型を使ったサービス定義 UnaryResult<Nil> Sample(Vector2 point); } }
using System; using MagicOnion; using MagicOnion.Server; using MagicOnionSample.ServiceDefinition; using MessagePack; namespace MagicOnionSample.Service { public class SampleApi : ServiceBase<ISampleApi>, ISampleApi { //--- そのまま受け取れます public async UnaryResult<Nil> Sample(Vector2 point) { Console.WriteLine($"(x, y) = ({point.X}, {point.Y})"); return Nil.Default; //--- 独自型を返せます } } }
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() { var channel = new Channel("localhost", 12345, ChannelCredentials.Insecure); var client = MagicOnionClient.Create<ISampleApi>(channel); //--- そのまま送受信できます var point = new Vector2(3, 5); var result = await client.Sample(point); Console.WriteLine(result.GetType().FullName); Console.ReadLine(); } } }
Nil
という初出の型がありますが、これは MessagePack for C# に定義されている「何もない」を表現する型です。Rx を知っている方であれば Unit
型と同じようなものと言えば良いでしょうか。実行結果は以下のようになります。
まとめ
MessagePack for C# のルールに則って独自型を定義するだけで難なくサーバー/クライアント間でやりとりすることができるようになりました。今回は説明していませんが、独自型のネストやコレクションにも対応しています。結局のところ gRPC は HTTP/2 上でバイナリデータを送受信しているだけなので、MessagePack for C# でシリアライズ / デシリアライズできるものであれば何だって通信できます。