Node.js + TypeScript で gRPCに入門する [前編: gRPCとは]

TopPage [出典: https://cncf-branding.netlify.app/projects/grpc/]

本記事は2021年度の「株式会社カケハシ x TypeScript」アドベントカレンダーの16日目の記事として執筆いたしました。

概要

gRPCは私も以前から興味はあり、なんとなくGeek達が集まってGO言語でハイパフォーマンスなバックエンドを作るあれでしょ!くらいに思っていました。しかし、最近ではNode.js(TypeScript)を利用した開発環境も徐々に充実してきていることを知ったのがきっかけで、この本記事を書きました。

gRPCに関しては既に多くの記事がありますが、まだまだ集約した情報が少ないのと、バックエンドエンジニア目線の記事が多いということもあり、今回はフロントエンドエンジニアの目線で、さらに使い慣れた Node.js + TypeScript で入門していきたいと思います!

ちなみに内容が少し長くなったので、記事を分割しておりますが、 前半(本記事)ではgRPCとは?と、実装に必要なツール群のご紹介をして、 後半では具体的な実装の仕方をご紹介します。

gRPCとは

RPCとは

gRPCとは何か?の前にまずはRPCとは何かをおさらいします。 RPCは(Remote Procedure Call)で、リモートから関数を呼び出す手法です。よくREST APIと比較されますが、REST APIはクライアントとサーバをある意味別のアプリケーションとした上でサーバのリソースに着目してエンドポイントを整理しているのに対して、RPCはリモートをあたかもクライアントアプリケーションの一部として利用できる様に関数を実行します。このため、REST APIと比較してRPCエンドポイントは仕様が柔軟で理解しやすい物になります。 ちなみに、OpenAPIを利用して開発した場合でも、REST APIになることは稀で、基本的には多くの開発者はJSONを利用したRPCを定義しています。 この辺りは Googleのポスト を参考にさせて頂きましたので、詳しくはご参照ください。

pros/consで比較すると、REST APIの定義はリソースで厳格に整理されるためエンドポイントの仕様がある程度予測できます。一方、RPCの場合はクライアントがエンドポイントの仕様を知らないと利用することができません。そのため、RPCを利用する時はOpenAPIProtocol Buffersなどなんらかのスキーマ言語やドキュメンテーション方法があると便利です。

gRPCとは

本題に戻りますが、gRPCは独自のRPCプロトコルでAPIエンドポイントを作成するためのフレームワークです。頭にgがつくだけあって gRPC = GO言語 見たいなイメージがありましたが、各種言語に対応しています。もともとはGoogleで開発され、現在はCloud Native Computing Foundationに寄贈されています。

gRPCHTTPで通信しますが、HTTP(2)上でさらに独自のgRPC通信を行うことで、高パフォーマンスなAPIが作成できるとされています。そのため、gRPCライブラリによってHTTPの詳細は隠蔽されます。

また、Protocol Buffersを利用してスキーマ定義やpayloadのシリアライズを行っており、gRPCの最大メリット=Protocol Buffersのメリットとしている記事もよく見かけます。Protocol Buffersに関して次節で説明します。

純粋にgRPCJSONを利用した普通のRPCエンドポイントを比較すると以下の特徴があります。

■ pros(特徴)

  1. HTTP2で通信する
  • ie) バイナリ送信、同時接続、多重通信
  • ハイパフォーマンスが期待できる
  1. 多様なストレーミング形式に対応している
  • 双方向ストリーミングにも対応
  • チャットルームの実装など

■ cons

  1. WEBブラウザからは使いにくい ( Webで利用する際の注意 を参照 )
  2. サーバ、クライアントがどちらもHTTP2に対応している必要がある
  3. サーバ、クライアントがどちらもgRPCフレームワークが必要
  • 言語間の実装格差があったりする
  1. バイナリで通信するので人間に見えにくい
  2. 学習コスト、実装コストが高い
  • 小規模であれば、JSON RPCの方が実装コストとパフォーマンスの費用対効果が高い場合がある

3に関しては、JSON RPCHTTPの知識だけでリクエストを送信できるので、OpenAPIでスペックから自動生成したクライアントを利用していてもサーバ側は自前で実装するというということが可能ですが、gRPCは必ずgRPCフレームワークを利用してサーバ・クライアントのコードを実装する必要があります。

Protocol Buffersとは

OpenAPIなどと同様にインターフェース定義言語(IDL)の一種でAPIの仕様を定義するための仕様です。また、定義した構造体のための優秀なシリアライズ方式が仕様化されており、高速かつ低容量なバイナリに変換できます。

■ インターフェース定義言語(IDL)であること自体の利点

  • サーバ・クライアントの実装言語に関わらずAPIの仕様を定義できる
  • 周辺ツールを利用して、定義からサーバやクライアントコードを生成し利用することができる
  • 作成したモデルを利用して、サーバ・クライアント間で型安全性を保った開発ができる

Protocol Buffersの利点

  • OpenAPIより記述が簡素で、人間に対する可読性が高い
  • 優秀なシリアライズ方式が利用できる

個人的にはOpenAPIでも code first な開発方式を取ればスペックの可読性を気にする必要は無いと思いますが(別途記事にまとめました )、spec first な開発を好む場合はOpenAPIの巨大化するスペックは(yamlが使えるにしろ)非常に読みにくいため、オブジェクト思考のクラスっぽく簡潔に記述できるProtocol Buffersの利点が生かされると思います。

gRPCはProtocol Buffersの定義から、サーバ・クライアントの雛形を自動生成し、実際の処理を穴埋めしていくという開発スタイルになります。

gRPCの勘所

gRPCのキャッチアップをしてみて一番難しいと感じる点はツールの多さです。非公式をふk目多くのツールを導入しながら Node.js + TypeScript での開発環境を整えていく必要があります。以下、それぞれのツールの役割が明確になる様に務めて、解説していきたいと思います。

gRPC + Node.js(TypeScript) のエコシステム

全言語共通の物

protoc (protocolbuffers/protobuf)

Protocol Buffersで定義した内容を各プログラム言語のモデル(クラスなど)にコンパイルするためのCLIコマンドです。これにプラグインを追加することでgRPC用のコードも生成していく流れになります。brewで簡単にinstall可能 ですが、protoc は 後述する grpc-tools というnpmパッケージにも同梱されていますので、フロントエンドエンジニアはこっちの方が楽だと思います。

Protocol Buffers系

google-protobuf

Protocol Buffersを記述する際には Timestamp などのよく利用するモデルが Googleに事前定義されてライブラリとして提供されておりますが、JavaScript から同様のモデルをランタイムで利用するためのライブラリになります。

gRPCの公式protoc用プラグイン

grpc/grpc-node

Node.jsgRPC を実装する時に利用する公式ツール群がまとめられているレポジトリです。Githubでは grpc/ 配下に grpc/grpc-go , grpc/grpc-web , grpc/grpc-java など各言語のgRPCライブラリが並列していますが、そのうちのNode.js 版です。 grpc/grpc-node はモノレポ構造となっており、下記の通りいくつかのnpmパッケージを内包しています。

grpc-tools

npm版のprotoc(CLIコマンド)と protoc用の grpc-node プラグインが格納されています。grpc-toolsを npm install すると、protoc に相当するバイナリファイルが node_modules/.bin/grpc_tools_node_protoc にインストールされます。grpc-node プラグインは gRPCのクライアントとサーバスタブ(サーバの雛形)をprotocから吐き出せる様にします。TypeScript対応はしていないので、型定義ファイルを生成するには別のプラグインが必要になります。

grpc

2021年に deprecated しますが、もともとNode.jsで開発する場合のgRPCライブラリはこれだった様です。C言語ベースで開発されています。

@grpc/grpc-js

現行のgRPCライブラリです。100% JavaScriptgRPCが実装されております。grpc-tools で生成したコードが参照する事になります。

@grpc/proto-loader

ランタイムで動的にProtocol Buffersの定義を読み込んでサーバ・クライアントを起動するライブラリもあります。事前にprotocでコンパイルする必要がないのは利点ですが、その分TypeScriptの型の恩恵を受ける事ができないので今回は省略します。

grpc/grpc-web

詳細は後述しますが、gRPCをブラウザで利用する際には考慮点があるのですが、一つの解決方法が grpc/grpc-web を利用してクライアントを生成する方法です。まだexperimentalですが、TypeScriptがサポートされています。

gRPC外部系

agreatfool/grpc_tools_node_protoc_ts improbable-eng/ts-protoc-gen

両方とも、ptorocJavaScript出力に対して型つけをする型定義ファイル (*.d.js) を生成するためのプラグインです。個人的にはimprobable-eng/ts-protoc-gen の方が使いやすいかな?と感じました。 また、improbable-eng/ts-protoc-genimprobable-eng/grpc-webを利用したWEBクライアントを生成する事も可能ですが、公式のgrpc/grpc-webではない点は注意が必要です。双方の差分は 公式ドキュメント や、issue(ちょっと古い) に記載がありました。

--------------

以上たくさんあって、覚えるのが大変ですが後半では上記のライブラリを活用した実装方法を紹介していきます。

Webで利用する際の注意

gRPCはもともとバックエンドで利用することが想定されており、ブラウザの制約でブラウザから直接gRPCリクエストを送ることができません。フロントエンド開発者であれば、ブラウザでの利用を最初に思い浮かべると思いますので、gRPCの注意するべき点です。

では、ブラウザアプリからgRPCへリクエストが不可能なのかというと、二つの一般的な対応方法があります。

方法1 サーバ・ブラウザ間はREST APIにする

単純にブラウザからサーバへのリクエストでgRPCを使うのを諦めてREST APIとしてサーバにアクセスし、さらにバックエンドのサーバに通信する時はgRPCを利用して通信する方法です。

method1

grpc-gateway を利用すると、上記のサーバ1の部分を自動で生成できる様です。

ここでの REST API HTTPで通信する一般的なAPIであることを意図しています。

方法2 grpc-web を利用する

2018年に grpc-web というツールのリリースが発表 されました。 動作の仕組みは gRPC のリクエストをXHRfetchにマッピングして送信する物です。そのため、クライアント・サーバ間には XHR -> gRPC に通信を逆変換するプロキシが必要になります。

method2

現在 grpc-web というツールは公式(grpc/grpc-web)improbable社製(improbable-eng/grpc-web) があります。双方で利用するproxyが異なり、公式はenvoy, improbableは独自のGO言語ベースのプロキシになります。また、実装状況によっては、双方向ストリーム機能など一部機能に制限があったりします。純正の方のRoadmap を見ると、まだまだ途上だなぁと感じます。 それぞれの対応状況の外観や、gRPCのブラウザ対応状況はこちら に記載があります。

まとめ

前半ではgRPCそのものを振り返りました。各種ツールの関係やブラウザの対応状況などは一度整理ができてよかったと思います。 細かい機能ではserver interceptorNode.js環境で利用できなかったり、やっぱりGO言語のサーバの方が機能が充実しているような印象がありますが、JS界隈のツールも徐々に充実してきております。 次回は具体的なコードで Node + gRPC での実装方法をご紹介していきたいと思います。