xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

gRPC / MagicOnion 入門 (3) - プロジェクト構造と実装の前準備

今回は MagicOnion を使った API 開発を行うまでの前準備にフォーカスを当てます。主に Console / WinForms / WPF アプリなど向けです。MagicOnion は Unity にも対応していますが、これはだいぶ大変でややこしいので別途解説することにします。

基本的なプロジェクト構造

MagicOnion は基本的に以下のようなプロジェクト構成をとります。

プロジェクト 役割と解説
ServiceDefinition API 定義のみ
サーバー / クライアントの両方から参照される
Service サーバー側
ServiceDefinition で定義されたインターフェースを実装
Client クライアント側
ServiceDefinition で定義されたインターフェースを通してサーバー側の API を呼び出す

f:id:xin9le:20170604184611p:plain

通常の gRPC は Protocol Buffers の IDL を用いたサービス定義を行いますが、MagicOnion は IDL が不要*1です。その代わりにサービス定義をサーバーとクライアントで共有する必要があるため、サービス定義部分だけ別プロジェクトとして分離する必要があります。

NuGet Package の取得

MagicOnion は NuGet で配布されており、以下のコマンドで取得できます。

PM> Install-Package MagicOnion

このパッケージを前節で挙げたすべてのプロジェクトの参照設定に追加する必要があります。gRPC や MessagePack for C# などの MagicOnion 自体が利用するものは、MagicOnion をインストールする際に同時に取得されるので個別での対応は不要です。

*1:C# コード自体を IDL として利用し、実行時に動的解決する

gRPC / MagicOnion 入門 (2) - 4 種類の通信方式

gRPC は HTTP/2 の仕様に則った形で 4 種類の通信方式を提供しています。以下のドキュメントにも解説がありますが、簡単に図解します。

f:id:xin9le:20170604212009p:plain

Unary RPCs

最もシンプルな 1 リクエスト / 1 レスポンス型の通信方法です。通常の関数コールのように扱えるため、非常に分かりやすいのが特徴です。

f:id:xin9le:20170604153513p:plain

Server Streaming RPCs

クライアントが 1 度リクエストを送信し、サーバーから複数回数のレスポンスを返す方式です。クライアントはサーバーから送信完了の信号が来るまでストリームからメッセージを読み続けます。いわゆるサーバープッシュ型の実装に利用できます。

f:id:xin9le:20170604153542p:plain

Client Streaming RPCs

クライアントから複数回数のリクエストを送信し、それらを読み込んだサーバー側から 1 度のレスポンスを待つ方式です。サーバーはクライアントからリクエストの送信完了信号が来るまでストリームからメッセージを読み続け、レスポンスを返しません。

f:id:xin9le:20170604153550p:plain

Duplex Streaming RPCs

双方向ストリーミングの通信方式です。2 つのストリームはそれぞれ独立しているため、クライアントとサーバーはどのような順序でも読み書きが可能です。リクエストとレスポンスが 1 対 1 である必要がなく、扱い/実装共に難易度が高いのが特徴です。各ストリームのメッセージ送信順序は保証されます。

f:id:xin9le:20170604153558p:plain

ドキュメントには「Bidirectional Streaming」と記載されている場合がありますが、gRPC のコード上では「Duplex Streaming」とされているので、この連載では「Duplex Streaming」を使って解説を進めます。

gRPC / MagicOnion 入門 (1) - 概要

gRPC は Google が開発している HTTP/2 ベースの RPC 通信フレームワークで、以下のような特徴を備えています。

  • Protocol Buffers を利用したサービス定義
  • 多数のプラットフォーム/言語をサポート
  • HTTP/2 ベースの高パフォーマンスなストリーミング通信
  • 統合されたプラガブルな認証機構

gRPC は GitHub にて今も積極的に開発が進められており、Google も自身の Google Cloud Platform の API などとして積極的に採用しています。

f:id:xin9le:20170603185008p:plain

これまで HTTP/1 ベースでのストリーミング通信と言えば WebSocket などが有名ですが、通常の HTTP リクエストとは API が統合されておらず、サービスの口を 2 系統用意する必要がありました。ASP.NET に限って言えば、同様のコンセプトの SignalR がありますが、gRPC の方が高速で拡張性があり、先進的であると言えます。

MagicOnion

@neuecc さんが開発している、.NET 用の gRPC を薄くラップした高レベルな通信フレームワークです。素の gRPC の良さをそのまま残しつつ、C#er がより便利に利用できるように調整されています。

  • .NET Framework / .NET Core / Unity に対応
    • Unity 向けの gRPC Port を含み、それを利用して Unity で動作させる
  • MessagePack for C# を用いた超高速シリアライズ
  • Protocol Buffers を使わないため .proto (IDL : 中間定義) が一切不要
  • Heartbeat によるサーバー/クライアントでの切断検知
  • OWIN / ASP.NET Core MVC ライクな Filter による制御
  • Swagger を用いた HTTP/1 との相互運用

MagicOnion はグラニが開発/運用している黒騎士と白の魔王Project Sonata で採用されています。ただし、もし実プロダクトとしての採用を検討する場合は十分な検証を行ってください。

今すぐ試してみたいという方は、Quick Start が MagicOnionREADME.md に記載されているので是非トライしてみてください。

gRPC / MagicOnion 入門

HTTP/2 をベースとした通信フレームワーク gRPC と、その高レベルラッパーである MagicOnion についての連載インデックス

インデックス

f:id:xin9le:20170603134144p:plain

値の破棄

1 年ほど前 C# 7.0 の新規のについていろいろ書いていたのですが、値の破棄 (discards) という機能について書いてなかったことに気が付きました!ということで、イマサラですが紹介します。

これまでの「値を使わない」ときの書き方

コンパイルを通すために引数を指定しなければならないけれど、その引数は今使いません。

…と言ったケースはままあります。そんな「値を無視したい」ときによく見かける実装が「_ (アンダースコア)」変数によるエスケープです。慣例レベルではありますが、「利用しない変数」であることを明示した書き方をします。例えば以下のような感じです。

int _;
if (int.TryParse("123", out _))
{}

しかし C# においてアンダースコアは変数名として有効なので、複数の値を無視したい場合は以下のようにしなければなりません。

//--- 使いもしない変数なのにドンドン _ の数が増えていく...!
int _;
if (int.TryParse("123", out _)){}

int __;
if (int.TryParse("456", out __)){}

int ___;
if (int.TryParse("789", out ___)){}

//--- 当然変数として有効なので、ここで触ることができる
___ += 10;
Console.WriteLine(___);

「値の破棄」を明示したい

  • _ の数がドンドン増えるのはイヤだ
  • 値を無視するという意図の変数を誤って使わせたくない

そんなお気持ちを C# 7.0 が「discards (値の破棄)」としてサポートします。後述しますが、以下のようにいくつかのシチュエーションでのみアンダースコアが特別扱いを受けます。

//--- こんな関数があったとして
private void OutVariable(out int value)
    => value = 123;

//--- こんな風に書ける
this.OutVariable(out var _);
this.OutVariable(out _);

//--- 触ろうとするとコンパイルエラー
_ += 10;  // そんな変数はないぞ!

実は out var _ のように型を書く必要はなく out _ だけでも OK です。また上記の場合 _ は変数として認められていないため、以下のような書き方をしてもコンパイルエラーになりません。

//--- こんな複数の値を引数から戻す関数があったとして
private void OutVariable2(out int x, out string y)
{
    x = 123;
    y = "abc";
}

//--- 全部 _ ひとつで OK
this.OutVariable2(out var _, out var _);
this.OutVariable2(out _, out _);

discards が利用できる箇所

ザッと調べた範囲では、現状の C# 7.0 では以下の箇所で値の破棄の構文を使うことができます。

//--- 型分解のときに値を受けるけど捨てる
var (name, _) = ("xin9le", 32);
var (_, _) = ("xin9le", 32);

//--- 型スイッチで string 型に対して処理したいけど値は使わない
switch ("abc")
{
    case string _:
        break;
                
    //--- ちなみにコレはコンパイルエラー
    //case _:
    //    break;
}

LINQ やイベントハンドラを記述する際のラムダ式でも頻繁に _ を使うことがありますが、C# 7.0 ではサポートされていません。これは今後のバージョンに期待…かもしれません。

逆コンパイル

この機能がどうやって実現されているのか、いつも通り逆コンパイルしてのぞいてみます。すると、以下のような C# 6 までのフツーのコードに展開されます。

//--- これは
this.OutVariable2(out _, out _);

//--- 素直にこう展開される
int item1;
string item2;
this.OutVariable2(out item1, out item2);

つまり discards の構文であることを C# コンパイラが上手に判断して、良きに計らってくれているということですね。IL レベルでは一切変更がありません。