gRPC-Web を利用したクライアント・サーバー間の通信

Adventarを支える技術 Advent Calendar 2019 の4日目です。

今日は gRPC-Web について書きます。

gRPC-Web とは

gRPC-Web は今年の10月に GA になったプロトコルで、今回の Adventar システムリニューアルでは絶対に gRPC-Web を production で使ってみる、という気持ちの元、 gRPC-Web を中心にその他の設計を決めました。

gRPC は Google が公開している RPC 方式で、Protocol Buffers と HTTP/2 をベースにしたバイナリプロトコルです。ブラウザは HTTP/2 に対応していないブラウザもまだまだ現役でたくさんいますし、バイナリを扱うのが苦手だったりします。

そこで、ブラウザでも利用できる gRPC-Web という新しいプロトコルを作り、gRPC-Web を gRPC に変換する proxy 層を介して通信することで、gRPC の旨味をブラウザでも利用できるようにする、というのが gRPC-Web です。

gRPC の旨味というのは、Protocol Buffers による API スキーマの定義、そのスキーマを利用したクライアントの自動生成などが挙げられると思っています。grpc-gatewayを利用すると gRPC のサーバーとブラウザで通信することはできますが、Protocol Buffers の定義を使ったクライアントの自動生成などはできませんでした。

proxy の実装は公式でいくつか提供されていますが、今回は envoy を利用しました。envoy 採用の理由は、gRPC-JSON transcoder などの機能も使いたかったためです。これについては後日の記事で解説します。

実際に gRPC-Web でブラウザがどのような通信を行っているかは Chrome のコンソールなどで確認できると思います。リクエスト・レスポンスの body は Base64 なので読めないですが。

f:id:hokaccha:20191203205918p:plain

TypeScript 対応

今回 gRPC-Web を採用して一番よかったと思うところは、TypeScript のクライアント自動生成です。まだ Experimental な機能ではあるのですが、公式で TypeScript のサポートをしています。

https://github.com/grpc/grpc-web#typescript-support

実際に生成しているのは以下で

https://github.com/adventar/adventar/blob/f580de20510f9debe6356a5ad193c4532d8f6a0d/protobuf/protoc.sh#L18-L21

以下のように利用しています。

https://github.com/adventar/adventar/blob/f580de20510f9debe6356a5ad193c4532d8f6a0d/frontend/lib/GrpcClient.ts

自動生成されたクライアントを一段 wrap してアプリケーション内の型にキャストしているのがややダサいですが、これは実装し始めたときに PromiseClient というものが生えることに気づいてなくて、callback を Promise にキャストするついでに型変換も行っていた名残りだったりします。

このように、TypeScript によるクライアントを自動生成することで、API のリクエスト/レスポンスの型が保証されるのは非常に良い体験でした。

とはいえ、ハマったところや苦労したところも多々ありましたので、明日はそのあたりの話を書こうと思います。