🌐

gRPCの素晴らしいところと技術選択のメンタル

2021/12/12に公開

初めに

この記事は作者の考えをまとめたポエムで、世間一般常識ではないです。
くれぐれもこれを妄信してシステム開発を行わないように。

言いたいこと

  • やりたいのはリソースアクセスではなくrpcだったことにgRPCは気づかせてくれた
  • gRPCのコード生成は素晴らしい
  • 技術選択は理由をちゃんと考えたい

言わないこと

  • 各技術の使い方
  • 具体的な実装

開発者から見たRESTとgRPC

RESTがサーバ間アクセスにルールをもたらした功績は素晴らしい。
しかし、RESTはその思想ゆえに「リソース」に縛られて使いにくく感じる。
フロントエンドからサーバのAPIを叩くとき、サーバから他のサーバを叩くときに、私がやりたいのはリソースへのアクセスではない。他のサーバのメソッドを呼びたいだけ。daoを叩いたら、サーバのコントローラーメソッドが呼ばれることを期待している。
実際にRESTクライアントを実装するときにdaoを、RESTサーバを実装するときにコントローラメソッドを作らない人はいないだろう。であれば、間に挟まるURLパスとHTTPメソッドは何の意味があるのか。
対外的なインタフェース?コントローラーのメソッド名がそのまま公開されていれば良いのでは?
この観点から見ると、RESTはgRPCに比べて1つ過程が多い。

gRPC + protobufによるデータ圧縮や通信効率云々は公式ドキュメントを見てほしい。実際にそちらの方面でもとても素晴らしい。が、今回は着眼点を変えて語りたいと思う。

REST APIを叩くのは辛い

コードを書いていて外部のライブラリを呼ぶとき、我々はライブラリが公開しているメソッドを叩く。
自分達でコードを書いているとき、責務を別のメソッドに切り出し、そのメソッドを呼ぶことでロジックを実装する。
では、全く責務が異なる処理を行うときは?別サーバを立てる。特にマイクロサービスアーキテクチャが盛んな昨今なら尚更だ。(e.g. ログイン系の処理をまとめて認証サーバを立てる)

そして、別サーバを立てて処理を分離して我々が呼ぶのは...なぜか突然リソースになる。
コードレベルで分割した後呼んでいたのはメソッドだったのに、サーバレベルで分割した途端にリソースを呼び出す必要がある。
そして、我々はhttp clientをdaoとしてラップし、サーバのコントローラーをメソッドで実装し...擬似的にdaoメソッドを叩くとサーバのコントローラーメソッドが呼ばれるように実装し、通信している。
ここまでは別に良い。メソッド呼び出しの裏側が隠蔽され、擬似的にサーバ間のメソッド呼び出しに成功しているからである。

最大の苦痛はRESTful APIのURLパスとHTTPメソッドを考えなくてはならないことだ。
記事を公開するならGETで/articlesで公開し、コントローラーメソッドをListArticlesとすればよい。
だが、ストップウォッチのタイマーをスタートさせたい場合はどうする?
メソッド名ならstartTimer()あたりにすればよいが、URLパスとHTTPメソッドは...?難しい。

また、もう一つ苦痛がある。これはRESTの問題ではないのだが、openapi(旧swagger)が地獄である。あのyamlフォーマットは本当に見づらいし、書きづらい。openapi用IDCが登場したことで多少マシになったが、専用IDEがないと書けないyamlという時点でプログラミング言語並みの難易度ということ。

gRPCが我々に気が付かせてくれたこと

gRPCのAPI定義方法を見ていこう。公式をざっと見ていただきたい。

一番見ていただきたいのがservicesの部分。ここはAPIを定義する部分である。
見た通り、まんまメソッドを定義する。引数もあるし、戻り値もある。今までURLパスを考えて、HTTPメソッドを考えて、daoのメソッド名とサーバのコントローラー名を考える必要はない。クライアントはこのメソッドを叩けば、サーバ側のこのメソッドが呼ばれる。素晴らしい。

gRPCじゃなくてもよいのでは?

私が欲しかったのはメソッドを叩いたらサーバのメソッドが呼ばれる仕組みであって、gRPCである必要はないのでは?という疑問が浮かぶかもしれない。答えはYesだ。gRPCは目的を達成するための道具の一つなので、代替手段があればそれでもよい。
個人的にはサーバ間通信であればgRPCがおすすめだが、ブラウザからの通信であればgraphqlをおすすめしている。
gRPCはブラウザでは使えない。ブラウザがHTTP2を喋れないからだ。だから、ブラウザからgRPCを使おうとすると、gRPC-Webという仕組みが必要になる。詳しくはドキュメントを読んでほしいが、簡単に説明する。

  1. ブラウザからHTTP1.1で送信
  2. リバースプロキシ(Envoy)でHTTP2に変換
  3. gRPCバックエンドサーバに到達

いつか試してみたいが、プロダクションで使うにはちょっと怖い。間にいろいろな変換が入っており、何かあった時に調査が難航しそうな気がしている。が、使ってみたら意外と良いかもしれない。

一方、graphqlはbodyが特殊なPOSTリクエストにすぎない。ロードバランサーもリバースプロキシも今までと同じ感覚で使える。それでいてAPIの命名自由度が高い。
もちろんgraphqlのqueryだったら、queryっぽい名前をつけることは重要だが、いつもの変数名やクラス名と同じ感覚で命名できる。 mutationも同様である。

RESTのコード生成とgRPCのコード生成、graphqlを添えて

openapiのyamlが書きにくいのは先に述べた通りだが、公式のコード生成ツールで生成したコードは凄まじく読みにくいし、既存のコードにマージしにくい。
さまざまな言語の様々なフレームワークに対応しようとした努力を感じるが、生成したコードを使うのは骨が折れる。正直自分で実装した方が早いまである。
一方、gRPCが生成したコードは読みにくいが、生成されたコードを読む必要ない。生成されたメソッドを呼ぶだけでサーバを実装できるし、クライアントも実装できる。
既存のサーバフレームワークに対応するのではなく、独自のサーバフレームワークを使うことを強制することで、このあたりがシンプルになっている。開発者としても、gRPCの言う通りに実装すれば動くので安心感が強い。
また、gRPCは独自フレームワーク故にスキーマ定義を無視した実装はできない。実装とスキーマ定義の乖離が絶対に起きない。ここもかなり高評価ポイント。
ちなみに、graphqlでもスキーマからコード生成できる。が、こちらはサードパーティ製のツールしかない。typescriptで使うならgrpahq-code-generatorが良いかもしれない。
こちらは、スキーマと実装が乖離する可能性がある。が、生成されたコードをマージしやすい。少なくともRESTよりはマシである。

RESTに限らず技術採用する時はちゃんと考えよう

今回はRESTと比較してgRPC良いよ。という話をした。もちろん、protobufは圧縮率が高いとか、通信効率が良いとか、いろいろ良いところがある。今回はその辺ではなく、呼び出し方に着目した。
公式リファレンスには技術的な優位性がいっぱい書いてあるので、今回はそれ以外のところに着目したポエムを書いてみた。
とはいえ、これだけgRPCを推している私もgrpc-webの採用には乗り出せていなかったりもする。
今後、新しいシステムを開発するときに生やすAPIは本当にRESTである必要があるのか、ちゃんと考えた方が良いと思っている。少なくともREST一択の時代ではなくなっていると感じる。
これはRESTに限った話ではない。流行りだからってサーバサイドにGoを盲目的に使うのも違うし、古いという印象でjavaを遠ざけるのも違う。javaは古いみたいなステレオタイプな情報も多いので、常に先入観に囚われず技術選択をしていきたい。とはいえ、チャレンジングな技術選択もしていきたい。この辺りの塩梅は本当に難しい。

Discussion