この記事はOrigami Advent Calendar 2016二日目の記事になります。
Origamiは主にモバイルアプリを提供している会社ですが、その裏側では様々なバックエンドサービスが動いています。今まで個々のサービスはREST APIを通じてお互いに通信を行っていましたが、最近gRPCの導入をスタートしました。本投稿では、その導入経緯について簡単にご紹介したいと思います。
以下、簡単なgRPCのご紹介・導入の動機・導入にあたっての懸念点・現状の導入状況について順に説明していきます。
1. gRPCとは?
gRPCは、ネットワーク上に存在する異なるウェブサービス間でRPC(Remote Procedure Call)を実現するためのフレームワークです。gRPCを使うと、図1に示すようにサービスに実装したいメソッドをprotoファイルという定義ファイルに記述することで、サーバ実装・クライアント実装が自動生成されます。
図1. gRPCの概念図 - 出典: grpc/grpc.io
自動生成の対象として多くのプログラミング言語がサポートされているため、サーバはGo言語で、クライアントはJavaで実装する、といったことにも対応できます。対応している言語の一覧はgrpc/Overviewから参照できます。
2. 導入の動機
今回私たちがgRPCの導入を決めた一番の理由は、サービスのインタフェース仕様管理を一元化する為です。
Origamiのバックエンドは、特定の機能を持つウェブサービスを複数組み合わせて作られています。今まで各ウェブサービスはREST APIを定義し、JSONデータをやり取りしていました。ウェブサービス数が増えていくにつれて、各サービスのエンドポイントやリクエスト・レスポンスフォーマットといった最新のインタフェース仕様を適切にドキュメントし続けるコストが問題になってきました。
Gitリポジトリにマークダウンでコミットする・編集しやすいようにWikiに残しておく・Swaggerで管理する、などメンテナンスコストを減らすための工夫はできるのですが、どうしても更新漏れ・記載漏れは発生してしまいます。
gRPCを利用してサーバ・クライアントの実装を自動生成することで、インタフェースが変更される際に、それをprotoファイルに反映することが必ず作業フローに入るため、protoファイルを確認すれば最新のインタフェース定義や、インタフェースの変更履歴が簡単にトラックできるようになります。また、protoファイルは下記のように呼び出し可能なメソッド名・そのリクエスト・レスポンスのパラメータがすべて記載されているため、完全なインタフェース仕様として参照することができます。
gRPCのインタフェース定義例 - 出典: Quick Start - grpc.io
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (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;
}
インタフェース定義の管理以外にも下記のようなメリットが見込めました。
- リクエストパラメータのバリデーション処理や、レスポンスフォーマットの定義などを自動生成できるため、定型処理の開発コストが省ける。
- サーバ側と異なる開発言語用のクライアントライブラリも自動生成することができる。
3. 懸念点
前節で記述したような理由でgRPCの導入を検討しはじめたのですが、実際に導入を始める前に確認しておきたい懸念点が出てきました。以下、それぞれについて検討した内容を簡単にまとめます。
懸念点1: Thriftとどちらを選ぶべきか?
類似のRPCフレームワークとしてThriftも有名です。導入検討を行っていたのはちょうどgRPCが1.0リリースする直前くらいで、Thriftの方が本番利用されている実績は多い状況でした。
検討結果
その中でgRPCの採用を決めた要因は以下の2点です。
- ドキュメントの充実度合い
- Go言語(以下Go)サポートの手厚さ
完全に調査できたかどうかは分からないのですが、ThriftとgRPCのドキュメントを比較した際に、gRPCのほうがフレームワークの構造や各言語バインディングの利用方法が詳細に記述してあり、罠を踏まずにトライアルが開始できた点が大きな選択理由の一つでした。
また、Origamiではバックエンド開発のほとんどがGoで行われており、サーバ側・クライアント側ともにGoが十分にサポートされている必要がありました。ThriftもGoをサポートしている(参考)のですが、一方で3rd Party製のライブラリもあったりと、Go言語で利用する際のサポートがオフィシャルで手厚く提供されているのはgRPC、と判断しました。
懸念点2: HTTP2が使えないクライアントはどうするのか?
gRPCは基本的にサービス間のコミュニケーションにHTTP2プロトコルを利用します。その為、例えばブラウザ上で動作するJavascriptバインディング(nodejsバインディングは存在します)は存在しません。このように、何かしらの都合でより一般的なHTTPプロトコルを利用する際に問題が生じないのか?という懸念がありました。
検討結果
grpc-gatewayというpluginがあり、図2に示すように通常のgRPCが生成するインタフェースに加えて、REST APIで呼び出すための実装も自動生成することができます。grpc-gatewayを利用しない場合でも、同様のメソッドをREST APIとして実装した別のサーバインスタンスを実装することも可能なため、この懸念点については対処可能と考えました。
図2. grpc-gatewayの概念図 - 出典: grpc-ecosystem/grpc-gateway: gRPC to JSON proxy generator
懸念点3: ベンダーロックインが強くないか?
gRPCを利用する場合、ある程度gRPCに沿った形でウェブサービスの実装を行うことになります。「後にgRPCの利用をやめようと決めた場合に、それが全体の実装にどの程度の影響を及ぼすか」が最後の懸念点でした。
検討結果
gRPCが自動生成するのは主に下記の四点です。
- リクエストとレスポンスのデータ型
- 各RPCメソッドを呼び出すサーバ
- リクエストデータのデシリアライズ
- レスポンスデータのシリアライズ
これに自分たちのビジネスロジックを加えると、実装は図3のようなります。
gRPCを使わずにREST APIとしてコントローラの実装を行った場合も、図4に示すようにルーティングやリクエストの読み込み、レスポンスのフォーマッティングといった処理によって結局似たような構造になります。
仮にgRPCの利用をやめてREST API用のコントローラを記述しようとした場合も、ビジネスロジックを分離しておけば、ルータの定義・リクエストの検証・レスポンスのフォーマットといった部分を追加で開発するだけでよく、大部分のコードは再利用できるため、いざとなったらgRPCの利用を止めることも可能と判断しました。
4. 導入の現状
OrigamiではgRPCの導入を決定してみたものの、まだ本格的に全てをgRPC化し初めているわけではなく、新規開発するサービス・もしくは非常に小さい既存サービスに随時導入している段階です。また、iOS/Androidのモバイルアプリとサーバの通信ではなく、バックエンドのウェブサービス同士の通信のみを対象としています。
本来、gRPCはObjective-C, Javaに対応しているので、モバイルアプリが直接リクエストするサーバもgRPCに移行することでアプリ側の通信モジュールも大部分を自動生成することができます。今後はチーム全体としてgRPCベースのシステムの安定運用に見込みがついた段階でgRPC対応を全サービスに広げていきたいと考えています。