- 澁川喜規
- 約 2,800 文字
- 22,900 View
Go 1.19が8/2に早々にリリースされました。個人的にはGo 1.19よりも楽しみだったのが、サービス間通信とIDL(インターフェース記述言語)連載の中でご紹介したgRPCのGo実装の新星、Connectのアップデートでした。そしてそれはやってきました。
詳しい内容は↑の記事を見ていただくとして、Connectがその開発元ブログの紹介記事で宣言していたのが次の2つのことでした。
- Go 1.19が出たらconnect-goは1.0にして以後後方互換性を守るよ
- connect-webを出すよ
前者はまだ0.3だったのですが、connect-webはリリースされました。歴史のあるフロントエンドのコードジェネレータはTypeScript対応が後付けだったりするのですが、TypeScriptがファーストシチズンかつ、ネイティブというコードジェネレータなので、開発はかなりやりやすくなることが期待されます。connect-webの公開は数日前でしたが、本家の紹介記事もちょうど今日、公開されました。
Connect-Web: It’s time for Protobuf and gRPC to be your first choice in the browser
本家のチュートリアルとかもまだない状態ですが、サンプルコードを見たら簡単そうだったので、connect-goとセットで試してみました。サンプルは以下のリポジトリにアップしています。
https://github.com/shibukawa/connect-test
元のエントリーではRuby, Python, PHPにも出していくよ宣言がありましたが、今回のconnect-webのブログには、Node.js、Android, iOSにも出すよ宣言がありました。あと、connect-webの中のソースコードを見ると、
fetch()がないってことは古いNode.js使っているのか?connect-nodeを待ってて!
とあるので、今でもfetch()のあるNode.js 18でクライアントなら動作しそうですね。
プロジェクトのフォルダ構成と初期化
フォルダ構造はこんな感じです。Goの標準的な構成(トップにgo.mod、cmdフォルダ以下にエントリポイント)にプラスして、frontendというフォルダにフロントエンドのプロジェクトフォルダを起きました。
connect-test |
プロジェクトのフォルダ(ここではconnect-test)を作り、次のようにプロジェクトを作りました。フロントエンドはSolid.jsを使ってます。
mkdir connect-test |
必要なツール類を入れます。メインのbufとGo用のツールはGo製なのでgo installで入れます。フロントエンドの方はnpmで配布されているのでnpm installします。
go install github.com/bufbuild/buf/cmd/buf@latest |
gRPCの定義作成
ここでは、connect-goのサンプルをとりあえずそのまま利用しました。go_packageだけはgo modで設定したルートのパッケージ名はじまりになるように修正しています。
syntax = "proto3"; |
buf mod initを実行してbuf.yamlを生成したあとは、buf.gen.yamlを自分で作成します。上2つはGoコード生成のためのconnect-goのサンプルと同じですが、下にフロントエンド用のコード生成用の設定です。出力先はフロントエンドフォルダの中になるようにしています。
version: v1 |
あとはbuf generateでサーバーとフロントのコードを生成します。
APIサーバーの中身の実装
中身はconnect-goのサンプルとほぼ同じで、リクエストできたnameパラメータを使って、レスポンスの文字列を作って返しているだけです。
package connecttest |
このサーバーをハンドラーとして登録してウェブサーバーを起動するエントリーポイントのコードは以下の通りです。
package main |
開発時には裏でfrontendフォルダでnpm run devして開発サーバーを起動してリバースプロキシ経由でSPAを表示して、リリース時にはgo embedで取り込んだ成果物のHTML/CSS/JSを使って表示してあげる(go:embed紹介記事でやっているのと同等)パッケージのgithub.com/shibukawa/frontend-goというのをこの前作ったので、それを使っています。
ついでにそれの設定用のコードも作成しますが、リバプロいらないよ、ということであれば、このファイルと↑の🐙の行を消してください。
//go:build release |
これでサーバーは完成です。
フロントエンドの作成
connect-webで作成したクライアントは、まずトランスポートを作成し、それを渡してクライアントを作成します。あとはクライアントのメソッドを呼べばサーバーにリクエストを送信できます。
import { |
この状態では、オリジナルのconnectプロトコルでの通信になります。なお、createConnectTransport()のオプションでテキスト形式、バイナリ形式が選べたり、インターセプターの設定ができたり、いろいろできます。リバースプロキシを外した場合はbaseUrlはhttp://localhost:3000とかにしておく必要があると思います。
ソースを見るとcreateGrpcWebTransport()というトランスポート作成のファクトリー関数もあり、これを使うと純正のgRPC-Webプロトコルの通信になってくれるようです。
また、クライアントは、いまどき使うことはないと思いますが、コールバックスタイルのものを作成するcreateCallbackClient()もあります。
最終系は次のようになります。Solid.jsではサーバー通信はcreateResource()でラップして使いますが、この中ではasync関数を渡せば良いので、connect-webで生成したサービスはばっちり活用できます。
import { |
これだけで、名前を入れたらサーバーで挨拶文を生成して返し、それを表示するコードができました。
今回の例は使ってみたかったのでSolid.jsを使いましたが、ReactユーザーでuseSWR()を利用しているならたぶんこうすることになるかと思います。SWRは最初のパラメータ(文字列か文字列の配列)がキャッシュキーになります。そのため、fetcherの中では不要な(生成コードが持ってくれている)サービスのメソッドの名前をあえてuseSWR()の引数に入れています。
async function fetcher(key: string, name: string) { |
まとめ
connect-goのときもそうでしたが、生成されるコードの分量がかなり少なくて理解しやすいので、生成されたコードに責任を持ってリリースするのがやりやすそうでいいですね。また、gRPCはOpenAPIのyamlよりもコンパクトに書けるし、もちろん、フロントもサーバーも型チェックがばっちり効いていい感じです。これであれば、OpenAPIのときみたいに、コード生成のテンプレートをいじり出したり、というのも当面は不要な気がします。