このドキュメントの内容
【Qiita】C# protoファイルを使用せずに gRPC サーバー/クライアントを実装する を発展させ、gRPC のリクエスト/レスポンスの型に値型を使用できるようにします。
int request = 1;
AsyncUnaryCall<int, long> call = client.ExecuteAsync(request);
リクエスト/レスポンスは参照型のみ
C# 版の Grpc.Core
では RPC メソッドの呼び出しには多くのジェネリッククラスが関わります。例えば、
- AsyncUnaryCall<TRequest, TResponse>
- AsyncClientStreamingCall<TRequest, TResponse>
- AsyncServerStreamingCall<TRequest, TResponse>
- AsyncDuplexStreamingCall<TRequest, TResponse>
- UnaryServerMethod<TRequest, TResponse>
- ClientStreamingServerMethod<TRequest, TResponse>
- ServerStreamingServerMethod<TRequest, TResponse>
- DuplexStreamingServerMethod<TRequest, TResponse>
などです。これらには型制約 where TRequest:class
where TResponse:class
が定義されているため、リクエスト/レスポンスの型は参照型である必要があります。値型は使用できません。
gRPC の仕組み
Unaryメソッド呼び出しのフローは次のように表すことができます。ストリーム方式のRPCメソッドはもう少し複雑なフローになりますが、リクエスト/レスポンスの型に対する処理は同じ仕組みです。
1. RPCメソッドの呼び出し
- クライアントからRPCメソッドを呼び出します。リクエストオブジェクトを引数で渡します。
2. リクエストオブジェクトのシリアライズ
- 呼び出されたRPCメソッド定義
Method<TRequest,TResponse>
に格納されているマーシャラーMarshaller<TRequest>
によってリクエストオブジェクトがバイト配列にシリアライズされます。
3. データ通信
- バイト配列がサーバーへ送信されます。
4. リクエストオブジェクトのデシリアライズ
- 呼び出されたRPCメソッド定義に格納されているマーシャラー
Marshaller<TRequest>
によってバイト配列がリクエストオブジェクトにデシリアライズされます。
5. サービスメソッドの呼び出し
- RPCメソッド定義にマッピングされているサービスメソッドが呼び出されます。
6. サービスメソッドの実行
- サービスメソッドがレスポンスオブジェクトを返します。
7. レスポンスオブジェクトのシリアライズ
- RPCメソッド定義に格納されているマーシャラー
Marshaller<TResponse>
によってレスポンスオブジェクトがバイト配列にシリアライズされます。
8. データ通信
- バイト配列がクライアントへ送信されます。
9. レスポンスオブジェクトのデシリアライズ
- RPCメソッド定義に格納されているマーシャラー
Marshaller<TResponse>
によってバイト配列がレスポンスオブジェクトにデシリアライズされます。
10. レスポンスの受信
- レスポンスオブジェクトがクライアントに返されます。
RPCメソッドの定義を Method<byte[],byte[]> にする
結局のところ送受信されるのはバイト配列です。RPCメソッドの呼び出しに割り込み、自前でシリアライズ処理を行います。実際に実行するRPCメソッドのリクエスト/レスポンスの型をバイト配列にしてしまうことで型制限を回避します。理論上、バイト配列へのシリアライズが可能であればどのような型でも使用することができることになります。
以下は BlockingUnary のフローをイメージしたシーケンス図です。
1. RPCメソッドの呼び出し
- クライアントからRPCメソッドを呼び出します。リクエストオブジェクトを引数で渡します。
2. リクエストオブジェクトのシリアライズ
- 呼び出されたRPCメソッド定義
Method<TRequest,TResponse>
に格納されているマーシャラーMarshaller<TRequest>
によってリクエストオブジェクトがバイト配列にシリアライズされます。
次のように変わります。
- 呼び出されたRPCメソッド定義
Method<TRequest,TResponse>
に格納されているマーシャラーMarshaller<TRequest>
によってリクエストオブジェクトをバイト配列にシリアライズします。 - RPCメソッド定義を
Method<byte[],byte[]>
に置き換え、RPCメソッドを呼び出します。バイト配列を引数で渡します。このRPCメソッド定義に格納されているマーシャラーMarshaller<byte[]>
は指定されたバイト配列をそのまま返すだけで何も行いません。
3. データ通信
- バイト配列がサーバーへ送信されます。
4. リクエストオブジェクトのデシリアライズ
- 呼び出されたRPCメソッド定義に格納されているマーシャラー
Marshaller<TRequest>
によってバイト配列がリクエストオブジェクトにデシリアライズされます。
次のように変わります。
- 呼び出されたRPCメソッド定義は
Method<byte[],byte[]>
であり、マーシャラーはMarshaller<byte[]>
です。
このマーシャラーは使用しません。 - あらかじめサービスをビルドする際、RPCメソッドに対して次のようなメソッドハンドラをマッピングしておきます。
- 受信したバイト配列をマーシャラー
Marshaller<TRequest>
でリクエストオブジェクトにデシリアライズします。 - サービスメソッドを呼び出します。リクエストオブジェクトを引数で渡します。
- サービスメソッドから返されたレスポンスオブジェクトをマーシャラー
Marshaller<TResponse>
でバイト配列にシリアライズします。 - バイト配列を返します。
- 受信したバイト配列をマーシャラー
5. サービスメソッドの呼び出し
- RPCメソッド定義にマッピングされているサービスメソッドが呼び出されます。
次のように変わります。
- 4 のメソッドハンドラ内部でサービスメソッドが呼び出されます。サービスメソッド側から見れば呼び出し元が変わるだけです。
6. サービスメソッドの実行
- サービスメソッドがレスポンスオブジェクトを返します。
7. レスポンスオブジェクトのシリアライズ
- RPCメソッド定義に格納されているマーシャラー
Marshaller<TResponse>
によってレスポンスオブジェクトがバイト配列にシリアライズされます。
次のように変わります。
- 4 のメソッドハンドラ内部でマーシャラー
Marshaller<TResponse>
によってレスポンスオブジェクトがバイト配列にシリアライズされます。
8. データ通信
- バイト配列がクライアントへ送信されます。
9. レスポンスオブジェクトのデシリアライズ
- RPCメソッド定義に格納されているマーシャラー
Marshaller<TResponse>
によってバイト配列がレスポンスオブジェクトにデシリアライズされます。
次のように変わります。
- 元々のRPCメソッド定義
Method<TRequest,TResponse>
に格納されているマーシャラーMarshaller<TRequest>
によってリクエストオブジェクトをバイト配列にシリアライズします。
10. レスポンスの受信
- レスポンスオブジェクトがクライアントに返されます。
リポジトリ
このドキュメントの内容を含むソースコードは GitHub で公開しています。
【GitHub】mxProject/GrpcCommon
【Nuget】mxProject.Helpers.GrpcCommon