Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

improbable-eng/grpc-webの挙動とProtocol Buffersの仕様について

サーバーサイドエンジニアの田実です!

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のリクエストオブジェクトを指定しているので明らかに型が間違っているはずです。

実際の開発でもこんなやり取りがありました↓

f:id:tzmfreedom:20210802164741p:plain:w800

本記事では、なぜこのような挙動になるのかを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.CreateCreateRequest UpdateRequest がprotocで作成されたオブジェクトになります。

improbable-eng/grpc-webではリクエストオブジェクトの serializeBinary メソッドでProtobufのバイナリデータを生成し、それをリクエストボディとして送信しています。

github.com

また、Client.send の引数はProtobufMessageというインターフェースなのでProtobufなメッセージであれば何でも入れられます。

github.com

そのため、同じバイナリになる場合はリクエストオブジェクトを差し替えても問題なく動くことになります。 CreateRequestとUpdateRequestは同一のProtobufのメッセージ構造になっていたのでこれらを差し替えても正常に動いた感じです。

まとめ

improbable-eng/grpc-webはバイナリが同じであればリクエストオブジェクトを差し替えても正常に動くことを紹介しました。

ちなみにヤプリではgRPC-Webのライブラリを本家のgrpc/grpc-webに置き換えることを検討しています。こちらはprotocでgRPC-Web用のスタブを生成する方式で、型が明確に決まってくるためリクエストオブジェクトの差し替えができないようになっています。