マイクロサービス × gRPCの すゝめ

## マイクロサービス×gRPC
マイクロサービスは非常に優れたアーキテクチャです。
疎結合によりシステムはある程度 自由な技術を採用することができますし
システムが細分化されているので個別デプロイも可能で 且つ スケーリングも柔軟に対応できます。

マイクロサービスはモノリスシステムの頃の課題を解決する銀の弾丸のようにも見えますが、
実際にマイクロサービス化を進めてみると幾つかの闇の存在に気がつくことになります。

幸いにも闇を払うための幾つかの方法が先人たちにより提案されていて、
その中でgRPCを利用した闇を払う手法を紹介したい思います。


## マイクロサービスの問題
ここで今回のgRPCの話を進めるにあたり、イメージしやすいように 
簡単なECサイトのシステム構成例を挙げておきます。
(かなり大雑把なので ご容赦下さい...!)

<img src='https://qiita-image-store.s3.amazonaws.com/0/116147/3cd8bd0d-ff1c-92b9-9d74-35fab8bf45f3.png' width=500px />

各サービス構成は今回の話をする上で重要ではないので触れませんが
マイクロサービスは このような責務ごとに細分化されており、
疎結合で、多様性を保証することができる構成のことを指します。

このようなマイクロサービスという考え方が浸透したことで、
過去、頻繁に設計されていたモノリスシステムは姿を消し、
多くのサービスでマイクロサービス化が進められてきました。
( 勿論そうすることが出来ないシステムもある訳ですが.... )

とはいえ、マイクロサービスも銀の弾丸ではありません。
ざっと上げると以下の様なデメリットを抱えています。

- APIリクエストが頻発するためパフォーマンスが劣化しやすい
- システムを分割しているため管理コストが増える
- 障害発生時にサービスが分散しているため原因の解析が困難
- 全てのシステム間でインターフェイスが統一されている必要がある。

いずれも課題に対しても銀の弾丸はなく、
マイクロサービスを導入する際には苦しい戦いを強いられることになります。
これらの課題に立ち向かう最も効率的な方法は
マイクロサービスを導入した先人達の知識を参考にすることです。

今回紹介するgRPCもその一つであり、
以下の課題を解決するものとして注目されています。
>・ 全てのシステム間でインターフェイスが統一されている必要がある。
>・ APIリクエストが頻発するためパフォーマンスが劣化しやすい。

それぞれの課題を見ていきましょう。

### 全てのシステム間でインターフェイスが統一されている必要がある。

システム間での通信を行うには当然ですがインターフェイスを統一しておく必要があります。
ですが、ご存知の通りシステムは頻繁に変更されるため、
システム間のインターフェイスの整合性を取るのはリリースのタイミングなどの観点から非常に困難です。

また、ドキュメントの管理も問題です。
インターフェイスを更新したら その内容をドキュメントに反映する必要があります。
当然の事のように思うかも知れませんが実際のところ
ドキュメントは更新されないことが多く、古いドキュメントのまま放置されていることも珍しくありません。
( また、それが原因でシステムで何らかの問題が発生することも珍しくないです )

この問題に対しては様々な解決策が提案されてきました。
wiki, git ... もう少し工夫して　Swaggerなどが挙げられますが、
いずれも、そこに人の手が入る以上、どうしても更新漏れ、記載ミスが発生します。
チーム内であればクロスチェックを行えば(それもどうかとは思いますが)回避することは可能かもしれません。
ですが大規模サービスに慣れば複数のチームが絡んでくるため、
全てのチームで仕様書が正常かを管理するのは容易ではないはずです。

#### gRPCによる解決
この問題に対してgRPCは非常に優れた解決策を提供してくれます。
ProtocolBuffer(.proto)からサーバー, クライアントの通信処理、及びIFのコードを自動で生成し、
それを各サービスで実装することでgRPCを利用した通信が可能となります。
この際、生成するコードはgRPCがサポートしている言語を指定することができます。

<img src='https://qiita-image-store.s3.amazonaws.com/0/116147/9dab80ae-3d86-a818-e1b3-ccaae76d085b.png' width=500px />
(参考: [grpc / grpc.io](https://grpc.io/))


サービス間の通信は.protoファイルから生成されているため
IFを更新するには必ず.protoファイルを更新する必要があります。

つまり 正しい仕様は.protoファイルで確認することが出来るということです。
今までのフローでは実装後にドキュメントを更新する必要がありましたが、
そもそも.protoファイルを更新しなければ実装が出来ない訳ですから
更新漏れ、記載漏れが発生する余地がありません。

また、通信処理、及びIFのコードを自動で生成することも大きなメリットです。
従来の開発では当然通信の処理を実装する必要がありましたが、
それは本来エンジニアが注力したい部分では無いことは誰もが理解していることです。

ですが、gRPCを利用することで 実装をする必要があるのはリクエストに対する実体処理のみとなり、
本来注力したかったビジネスロジックへ集中することを可能にします。

<img src='https://qiita-image-store.s3.amazonaws.com/0/116147/ba410b11-2a3b-4cb8-6ca7-f053ad49c051.png' width=500px />

また、実装時に決められた規格でレスポンスすることを強制し、
規格に沿わない実装をした場合はコンパイルエラー または 実行エラーを起こします。
これにより実装段階で常に規格に沿っていることが保証されるため
想定しない項目が入り込む、構造が違うといった稀に良くある問題を防ぐ事ができます。

### APIリクエストが頻発するためパフォーマンスが劣化しやすい
システムを責務ごとに分割することによりコードベースは非常に綺麗になります。
しかし、システムを分割したことで分散されたリソースを取得しに行く必要が生まれ、
結果としてパフォーマンスがモノリスシステムの頃より低下するという課題を抱えています。
そして この課題はシステムを分割すればするほど顕著に現れます。

#### gRPCによる解決
この課題の根本的な原因はHTTP1のコネクションオーバーヘッドにあります。
リクエストをする度にコネクションを貼り直す必要があるため
サービスが分散すればするほど オーバーヘッドは膨れ上がります。

またリクエストの多重化も問題です。
現在のWEBページを表示するためには画像、JS, CSSなどを取得する必要がありますが
その一つ一つでHTTPリクエストが発生しています。
HTTP1の仕様では一度のコネクションで取得できるリソースは一つという仕様のため
リソースのダウンロードは逐次処理されることになります。

実際のところ、この課題に関してはブラウザ側で複数のTCPを同時に貼ることで
回避されていますが、同時に貼ることが可能なコネクション数に制限があるため
結局の所、複数回に分けてリソースを取得することに変わりはありません。

これらの課題に対してHTTP2は以下の解決策を提供してくれます。

まずコネクションオーバーヘッドですがHTTP2ではコネクションは一度貼られた後、
捨てられることなく、そのコネクションを使い続けることになります。
そのため、コネクションのオーバーヘッドは初回接続の一度のみとなり
パフォーマンスの低下を抑えることが可能になります。

続いてリクエストの多重化の問題ですが
HTTP2では一度のTCPのコネクションで複数のリクエスト, レスポンスを取扱うことが可能です。
つまり今まで逐次処理でダウンロードされていたものが並列処理でダウンロードされることになり、
より素早いコンテンツの提供が可能になります。


## gRPCの課題
gRPCを採用する際に問題となるのは通信規格です。
プロトコルはHTTP2(TCP)を利用しているためHTTP1は利用できません。
つまりブラウザからのリクエストを受けることが出来ないという問題がある訳です。

ですが、その問題に関してはそれほど心配する必要はありません。

gRPCでは grpc-gatewayと呼ばれるライブラリが提供されており、 
これを利用することでHTTP1でのIFを提供することが可能になります。

<img src='https://qiita-image-store.s3.amazonaws.com/0/116147/dccab24d-de88-2085-f248-4bcc79043651.png' width=500px />

(参考: [GitHub - grpc-ecosystem/grpc-gateway: gRPC to JSON proxy generator following the gRPC HTTP spec](https://github.com/grpc-ecosystem/grpc-gateway))


簡単に要約すると
grpc-gatewayはHTTP1通信をgRPCに変換するためのリバースプロキシで 
こちらも同様に ProtocolBufferからリバースプロキシ用の コードを自動生成します。 
こちらのコードに関しては goでの提供となりますが
リバースプロキシ用なので サービス自体への影響はありません。

このgrpc-gatewayによるリバースプロキシにより
gRPCを実装していないクライアントはgRPC対応をする必要なく
HTTP1による疎通を行うことが可能となります。

## gRPCを導入すべきなのか
とはいえ手放しに喜んで採用すべきかと言われれば難しい問題でもあります。
例えば学習コストやHTTP2のインフラ構築も必要ですし
通信処理には様々な考慮すべき点や、社内の認証などの規約があり、
それに対応することが可能なのかどうかは採用に関わる非常に重要な要素です。

現段階では 詳しい情報を洗い出せていませんが
認証機能などは独自で持っており、拡張することも可能なようです。
https://grpc.io/docs/guides/auth.html

個人的な意見としては
全体としてgrpcの実績は出ているとはいえ、全サービスをいきなりgRPCで構築するのはリスクがあるため、
小規模なサービスから始め、ある程度の安定してナレッジが溜まった時点で
順次gRPC対応していくというのが良さそうです。