先の記事で紹介した Sample_GrpcService と、クライアントモジュールに新規のAPIを追加してみる。
Protocol Buffers とは
その前に、gRPC のサンプルコードの中の「Protosgreet.proto」 について簡単に解説する。
API を追加する為に必要最小限の解説しかしないので、全ての仕様を解説することはできない。
この拡張子「.proto」ファイルは Protocol Buffers と呼ばれるサービス間通信のシリアライズの仕様を定めた規格と実装の「定義ファイル」である。
Protocol Buffers は略称で「Protobuf」とも呼ばれる。
Protocol Buffers は「.proto」ファイルによってテキストのソースコードで関数の仕様や、リクエスト(呼び出し)とレスポンス(結果)の値の定義を定めて、それをバイナリにシリアル化してサービスとクライアントで通信する。
従来のWCF(SOAP)やREST-APIなどでは、XMLやJSONなどのテキストファイルにシリアル化してデータの受け渡しをやっていたが、gRPCでは Protocol Buffers によってバイナリ化することでデータサイズをコンパクトにして軽量に通信できるようになっている。
また、通信プロトコルも gRPC では、HTTP/2 固定なので、複数のリクエストとレスポンスを同時にやり取りする為、通信効率が良く早い。
既に GO や Ruby などの言語の世界では標準的に使用されているらしい。
C# や Java は過去の資産が多いので、新しい規格の導入は遅れ気味だが、今後のスマホのアプリやクライアントサーバーや他システム間連携やマイクロサービスの通信規格は gRPC に変わっていくだろう。
通信相手が gRPC を使用していれば、こちらも gRPC で通信せざる得ないので、避けられないと思う。
現在の .NET Core 3.1 や .NET 5.0 の開発環境で使用している Protocol Buffers のバージョンは3である。
規格名は「proto3」である。
「Protosgreet.proto」のソースの中では以下のようにバージョンを宣言している。
syntax = "proto3";
「proto3」の仕様書
「proto3」の仕様書は単純でそれほど長くない。
短時間で読める。
仕様書は以下で公開されている。英語だが機械翻訳で読める内容だ。
Language Guide (proto3)
これを読めば「proto3」の書き方は理解できるので、詳細な説明を行う必要はないと思う。
どちらかと言えば、 gRPC の予備知識のない人をこの仕様書まで誘導する事に意義があると思う。
「proto3」の必要最小限の解説
いきなり、「proto3」の仕様書を見せられてもわけが分からないと思う。
だから知識導入までの入口の解説だけする。
入口の知識が付けば、あとは仕様書を読めば自習できるはず。
私も入口にたどり着く部分で若干苦労した。
分かってしまえば、それほど難しいものではない。
C#の.NETで「.proto」ソースコードを書く為に必要な最小減の宣言子と呼べる物は以下のものになると思う。
syntax
option csharp_namespace
package
service
message
syntax
syntax は先に説明したように、 Protocol Buffers のバージョンを宣言している。
Visual Studio 2019 の gRPCサービスプロジェクトを新規作成すると、自動で「syntax = “proto3”;」と定義されている。
option csharp_namespace
ビルドする .NETプロジェクトの名前空間を定義する。
サンプルコードでは以下の定義が自動生成される。
option csharp_namespace = "Sample_GrpcService";
これは Protocol Buffers には関係無く、.NET 側の都合で付けている定義だと思う。
package
シリアライズするデータの塊の名前を定義する。
「.proto」ファイルと同じ名前にする。
これは複数作成することもできる。
一つの「.proto」ファイルに一つ宣言する。
「.proto」ファイルの中から、他の「.proto」ファイルをインポートして参照する事もできる。
他の「.proto」ファイルの宣言を使用する場合にインポートする。
以下のように使用するらしい。
import "myproject/other_protos.proto";
ここでは解説しない。
message
自動生成された gRPC サービスでは以下のように「HelloRequest」と「HelloReply」が宣言されている。
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}
「//」はコメントである。
message 宣言は後で解説する service 宣言によって定義するAPIのリクエストとレスポンスのときに受け渡すデータの仕様を定義している。
この宣言は、クラスの宣言に似ている。
Protocol Buffers は、シリアライズの仕様なのでこのクラスのような message 宣言にはメソッドは存在しない。
メソッドが存在する必要もない。
message 宣言はメソッドの無いクラス宣言だと思ったら分かりやすい。
あるいは C言語の構造体の様な物、でも良い。
「HelloRequest」はメンバーに「name」文字列のプロパティを持つクラスだと思えば良い。
「HelloReply」はメンバーに「message」文字列のプロパティを持つクラスだと思えば良い。
C# で書き直すと以下のようなイメージだ。
class HelloRequest {
string Name;
}
class HelloReply {
string Message;
}
サンプルコードでは、クライアントから HelloRequest の Name に値を渡してAPIの「SayHello」を呼び出している。
結果は HelloReply の Message で受け取っている。
Name や Message の名称の大文字小文字はパスカル型に自動変換される。
プロパティは複数を定義できる。
プロパティを宣言する時は、「string name = 1;」のように、message 宣言の中で唯一の数値を指定する必要がある。
複数のプロパティを宣言する時は、プロパティごとに異なる数値が必要になる。
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
プロパティの型は他にも int32,int64,double,bool など様々な型が使用できる。
また、message 宣言の中に、message 宣言を入れる事もできる。
service
クライアントなどから呼び出す時に、記述する具体的なAPIの仕様を service 宣言によって定義する。
自動生成された gRPC サービスでは以下のように「Greeter」APIが定義されている。
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}
「//」はコメントである。
service 宣言はメソッドを持つクラスの宣言に似ている。
C# で書き直すと以下のようなイメージだ。
class Greeter {
HelloReply SayHello(HelloRequest);
}
サンプルコードでは、クライアントから HelloRequest の Name に値を渡してAPIの「SayHello」を呼び対している。
結果は HelloReply の Message で受け取っている。
//クライアントのコードサンプル
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var response = client.SayHello(new HelloRequest { Name = "World" });
Console.WriteLine("Greeting: " + response.Message);
使用できる変数型(Scalar Value Types)
message 宣言で使用できる変数型は、仕様書を見た方が良いが、ここにもC#のみ掲載しておく。
仕様書では「Scalar Value Types」と書かれている。
.proto Type | C# Type |
---|---|
double | double |
float | float |
int32 | int |
int64 | long |
uint32 | uint |
uint64 | ulong |
sint32 | int |
sint64 | long |
fixed32 | uint |
fixed64 | ulong |
sfixed32 | int |
sfixed64 | long |
bool | bool |
string | string |
bytes | ByteString |
列挙型も使用できる
これも仕様書を見た方が良いが、message 宣言では列挙型も使用できる。
「enum」型として定義する。
代入する数値は enum に対して「Corpus =4;」のように唯一の値を定義し、enumの値はそれと別に唯一の値を与える。
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
.NET アプリの日付型
日付型などの仕様は Protobuf の標準仕様には定義されていない。
しかし、Protobuf の “既知の型” 拡張機能で定義されている。
.NET の種類 | Protobuf の既知の型 |
---|---|
DateTimeOffset | google.protobuf.Timestamp |
DateTime | google.protobuf.Timestamp |
TimeSpan | google.protobuf.Duration |
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
message Meeting {
string subject = 1;
google.protobuf.Timestamp start = 2;
google.protobuf.Duration duration = 3;
}
詳しくは以下のMicrosoftの解説記事を読んでください。
gRPC サービスの型は4種類
service 宣言する gRPCサービスのメソッドの型は以下の4種類しかない。
単項メソッド
先に紹介した、SayHello はこれである。
rpc SayHello (HelloRequest) returns (HelloReply);
既にお気づきかも知れないが、リクエストもレスポンスも一つのメッセージしか使えない。
複数のメッセージをやり取りしたければ、メッセージの中にメッセージを複数定義する。
通常の業務システム開発では、この単項メソッドだけで済むと思う。
したがって、他のメソッドの解説はここではしない。
サーバー ストリーミング メソッド
クライアントからは単項で呼び出し、結果をストリーミングで受け取る。
// Server streaming
rpc StreamingFromServer (ExampleRequest) returns (stream ExampleResponse);
クライアント ストリーミング メソッド
クライアントからストリーミングでリクエストを送り、結果を単項で受け取る。
// Client streaming
rpc StreamingFromClient (stream ExampleRequest) returns (ExampleResponse);
双方向ストリーミング メソッド
クライアントからストリーミングでリクエストを送り、結果をストリーミングで受け取る。
テレビ電話のような通信に使用する。
// Bi-directional streaming
rpc StreamingBothWays (stream ExampleRequest) returns (stream ExampleResponse);
Microsoftの解説サイト
gRPC のサービスとメソッドを作成する
C# 側のメソッドコーディングの例も掲載されている。
終わりに
以上、業務システムのクライアント・サーバー開発などに必要最小限の Protobuf の解説だけ行った。
Protobuf は比較的簡単なので、詳細は仕様書とMicrosoftの解説サイトを参照してください。
最初の入門解説だけに絞って解説しました。
実際に Protobuf にAPIメソッドを追加したサンプルコードも以下に公開しています。
前回公開したサンプルコードに追加修正したものです。
お役に立てば幸いです。