サーバーサイドエンジニアの田実です!
Yappliのコンテンツ管理画面はSPAで実装されています。フロントエンド・サーバーサイド間の通信方式として gRPC-Web を採用しており、ライブラリは improbable-eng/grpc-web を使っています。
improbable-eng/grpc-webを使ってgRPCサービスを呼び出す場合は以下のように記述します。
const client = grpc.client(HogeService.Create, { host: config.url, }); client.start(metadata); const req = new CreateRequest(); req.item = new Item(); client.send(req);
こちらのコードですが、実はProtocol Buffers(Protobuf)の構造が同じな他のリクエストオブジェクトに差し替えても正常に動きます。
const client = grpc.client(HogeService.Create, { host: config.url, }); client.start(metadata); const req = new UpdateRequest(); // ここをUpdateRequestに変更 req.item = new Item(); client.send(req);
CreateRequest, UpdateRequestはそれぞれ以下のような定義になっています。
message CreateRequest { Item item = 1; } message UpdateRequest { Item item = 1; }
Createのサービスに対してUpdateRequestのリクエストオブジェクトを指定しているので明らかに型が間違っているはずです。
実際の開発でもこんなやり取りがありました↓
本記事では、なぜこのような挙動になるのかをProtobufの仕様や improbable-eng/grpc-web
のコードを踏まえて紹介したいと思います!
Protocol Buffersの仕様について
Protobufはタグ番号と値の繰り返しによって表現されるシリアライゼーション形式で、イメージはこんな感じです。
[タグ番号(+ wire type)] + [値] + [タグ番号(+ wire type)] + [値] ...
たとえば以下の2つのメッセージですが、タグ番号と型が同じなので、タグ番号1, 2にそれぞれ同じ値を入れた場合のバイナリは同一なものになります。
message Login { string username = 1; string password = 2; } message Product { string name = 1; string description = 2; }
仕様に関しては以下のリファレンス・記事を見てもらうとわかりやすいと思います。 developers.google.com qiita.com cipepser.hatenablog.com
improbable-eng/grpc-webの仕組み
improbable-eng/grpc-webはprotocでgRPC-Web用のスタブを生成する方式ではなく、 ts-protoc-gen
でTypeScript用のスタブを作成し、それをラップして利用するようなライブラリになります。
上記の例ですと、 HogeService.Create
や CreateRequest
UpdateRequest
がprotocで作成されたオブジェクトになります。
improbable-eng/grpc-webではリクエストオブジェクトの serializeBinary
メソッドでProtobufのバイナリデータを生成し、それをリクエストボディとして送信しています。
また、Client.send
の引数はProtobufMessageというインターフェースなのでProtobufなメッセージであれば何でも入れられます。
そのため、同じバイナリになる場合はリクエストオブジェクトを差し替えても問題なく動くことになります。 CreateRequestとUpdateRequestは同一のProtobufのメッセージ構造になっていたのでこれらを差し替えても正常に動いた感じです。
まとめ
improbable-eng/grpc-webはバイナリが同じであればリクエストオブジェクトを差し替えても正常に動くことを紹介しました。
ちなみにヤプリではgRPC-Webのライブラリを本家のgrpc/grpc-webに置き換えることを検討しています。こちらはprotocでgRPC-Web用のスタブを生成する方式で、型が明確に決まってくるためリクエストオブジェクトの差し替えができないようになっています。