gRPC、OpenAPI、RESTの理解とAPI設計での使い分け(2020)
(cloud.google.com)- API設計の出発点はRPCとRESTの違いであり、3つの方式はいずれもHTTPと結び付けられるが、呼び出しの作り方とクライアントの構成モデルが異なる
- RESTはサーバーが提供したURLをたどるハイパーテキストモデルであり、クライアントがURL形式を組み立てるOpenAPI型のAPIとは区別される
- OpenAPIはURLパステンプレートとHTTPメソッドで操作を定義するため、実用的で広く使われているが、RESTよりもHTTPにマッピングされたRPCモデルに近い
- gRPCはIDL、コード生成、バイナリペイロード、HTTP/2接続管理によってRPC APIを直接定義し、HTTPの詳細を隠蔽する
- gRPCは両側に特別なソフトウェアが必要で、プロキシ拡張・同時更新防止・部分更新に制約があるため、内部APIやCloud Endpointsのような変換レイヤーを使える場合に特に適している
HTTPベースAPIを見る3つのモデル
- HTTPをAPIの転送層として使う代表的な方式はREST、OpenAPI、gRPCに分かれる
- 多くの公開APIや社内分散APIがHTTPを使う理由の1つは、組織が80番と443番ポートのHTTPトラフィックのセキュリティ問題に慣れているためである
- 3つの方式はいずれもHTTPと結び付けられるが、何をアドレスとして扱うか、そしてクライアントが呼び出しをどう作るかが異なる
REST: URLを組み立てずにたどる方式
- RESTではクライアントはサーバーが渡したURLをそのまま使い、URL形式をAPI仕様の一部として理解したり組み立てたりしない
- ブラウザは現在のページ、ブックマーク、ユーザーが入力したURLをたどり、URLからHTTPリクエスト情報を抽出したり相対URLを絶対URLに変換したりする程度しか行わない
- REST APIの核心は、リソース間参照を他のリソースのURLとして表現するハイパーテキスト/ハイパーメディアの利用にある
- REST方式ではすべての識別子をURLとしてやり取りする
POST /accounts→account_URLPOST /subscriptionsにaccount_URLを渡す →subscription_URLGET {account_URL}→ アカウントデータツリーを返す
- RESTの利点は、Web自体の安定性、一貫性、普遍性に近く、HTTP/RESTのエンティティ指向モデルがAPIをより単純で規則的にできる点にある
OpenAPI: HTTPに直接マッピングされたRPCに近い
- OpenAPIは
pathsの下にURLパステンプレートを定義し、パスとHTTPメソッドの組み合わせを**オペレーション(operation)**と呼ぶ /pets/{petId}のようなパスは、クライアントが{petId}の値を知っていて、URLテンプレートに埋め込んでHTTPリクエストを作ることを要求する- この方式ではクライアントがURL形式を詳しく理解している必要があるため、RESTのハイパーテキストモデルとは逆である
- OpenAPIが広く使われる理由は明確である
- 従来のRPCモデルに似ており、プログラマーになじみやすい
- RPCの概念をHTTPリクエストに合わせて直接マッピングできる
- 公開APIでは、標準的なHTTP技術だけでほぼすべての言語と環境からアクセスできるという利点が大きい
- 一方で、URLパス、HTTPメソッド、パラメータマッピングを設計する必要があるため、API提供者と利用者の双方が学ぶべき詳細が増える
gRPC: HTTP/2を隠蔽するRPC実装技術
- gRPCはHTTP/2を転送層として使うが、API設計者やクライアント・サーバーコードにはHTTPの詳細を露出しない
- gRPCクライアントの呼び出しフローは単純である
- 呼び出すプロシージャを決める
- 使用するパラメータ値を計算する
- コード生成されたstubにパラメータを渡して呼び出す
- gRPCはinterface description languageベースでリモートプロシージャを定義するため、OpenAPIのようにURLパス・パラメータ・HTTPメソッドのマッピングを併せて表現する必要がない
- コード生成、フレームワーク、ライブラリにより、クライアントライブラリやサーバー実装を作りやすい場合がある
- バイナリペイロードとHTTP/2接続管理を活用して性能上の利点があり、同じ技術をgRPCなしで直接使うこともできるが、より多くの技術習得が必要になる
OpenAPIの代わりにgRPCを検討する場合
- OpenAPIでAPIを設計する際には、操作とパラメータをURLパスとHTTPメソッドの組み合わせで表現する必要があり、選択肢が多く難しくなりがちである
- RPC型モデルを使い続けるなら、gRPCはHTTPへのカスタムマッピングを自分で設計する負担を減らしてくれる
- gRPCとOpenAPIは基本となるAPIモデルは似ているが、HTTPの露出方法が異なる
- OpenAPIはHTTP転送の詳細をクライアントに露出し、設計者がマッピングを制御する
- gRPCはあらかじめ定義されたマッピングと生成コードによってHTTPの詳細を隠蔽する
- OpenAPIの大きな利点は、クライアントが標準HTTPツールと技術をそのまま使える点であり、多くのAPI設計者にとってこの利点は追加の設計コストを正当化する
RPCにエンティティ指向モデルを組み合わせる
- gRPCやOpenAPIを使う場合でも、RPCメソッドをエンティティ中心に制限すればRESTの一部の利点を得られる
- 手続き定義から始めるのではなく、リソースタイプを先に定義し、その後で各タイプに標準的なエンティティ操作を対応付ける方式である
- 基本操作はCreate、Retrieve、Update、Delete、Listで、一般にCRUD plus Listとみなせる
- 必要に応じて追加操作を置くこともできるが、エンティティ指向の考え方と手続き指向の考え方が混ざると、一部の利点が弱まる可能性がある
- 手続きをエンティティタイプごとに束ねる方式は、オブジェクト指向言語の中心的な考え方の1つでもある
gRPCの制約と注意点
- gRPCはクライアントとサーバーの両方に特別なソフトウェアが必要で、生成コードを双方のビルドプロセスに統合しなければならない
- JavaScriptやPythonのように、開発環境でビルドプロセスがほとんど存在しないこともある動的言語の利用者にとっては、この要求が負担になりうる
- Google Cloud EndpointsはgRPC APIをHTTPとJSONからアクセス可能にしてクライアントの選択肢を回復するが、すべての利用者がこれを使える、または同等機能を構築できるわけではない
- REST APIはメタデータなしでも全体をクロールするボットを作りやすいが、gRPCやOpenAPIのようなRPC型APIでは、エンティティタイプごとに異なるAPIとメタデータ、または専用ソフトウェアが必要になる
- HTTP APIではApigee EdgeのようなAPI管理ツールやプロキシを通じて、セキュリティ機能、入力検証、データ形式マッピング、ヘッダー・本文修正などを追加することが多いが、gRPCではこうしたプロキシ拡張がはるかに難しい場合がある
- gRPCは同時更新によるデータ損失を防ぐ標準メカニズムを定義していない
- HTTPはそのために
EtagとIf-Matchヘッダーを提供する
- HTTPはそのために
- gRPCは部分更新メカニズムも定義していない
- HTTPには
PATCHがあり、JSON向けの標準としてJSON merge patchとJSON patchがある - JSON merge patchはより単純だが、配列更新のようなすべてのケースを扱えない
- JSON patchはより多くのケースを扱えるが、利用はより複雑である
- HTTPには
選択基準
- RESTハイパーテキストモデルをすでに設計できる、または学ぶ意思があるなら、安定性・一貫性・普遍性を目指す良い選択肢になりうる
- OpenAPIは標準HTTP技術だけでアクセス可能なAPIを作れるが、RPCの概念をHTTPにマッピングする設計上の選択肢が増えるため、設計・実装・学習が難しくなることがある
- OpenAPIを検討するAPIであれば、gRPCも併せて検討する価値がある。両者の基本APIモデルは比較可能で、gRPCはHTTPマッピングを自分で作る必要を減らしてくれる
- gRPCは次の条件で特に魅力的である
- Cloud Endpointsのような製品により、クライアントがgRPC技術を必ずしも採用しなくてよい場合
- サーバーとすべてのクライアントの技術選択を制御できる内部APIである場合
- gRPCをOpenAPIやRESTの代わりに採用する際は、Apigee EdgeのようなAPI管理ツールベースのプロキシでAPIの振る舞いを拡張または補正する機会が大幅に制限されうる点を考慮する必要がある
1件のコメント
Hacker Newsの意見
時間を巻き戻せるなら、gRPCをそもそも学べないようにしたいと思うほど
最初はそのビジョンに惹かれたが、数年たってみると厄介ごとが多すぎた。内部を隠してくれるという話も冗談に近く、10回に1回失敗するリクエストの原因を突き止めようとしてデバッグログを大量に吐き出しつつ、名前も曖昧なタイムアウト/リトライ設定を10〜20個調整することになった
Mavenプラグイン、謎の
deadline exceeded、HTTP/2を嫌うロードバランサー、ファイアウォールのせいで結局標準APIを使わなければならない状況、貧弱なドキュメント、可観測性の面で使い物になるエラーメッセージを得ることまで、全部が時間を食った生成されるコードの開発者体験が悪く、クライアントスタブは
finalの具象クラスなのでテストでモックしづらく、サーバー実装もインターフェースではなく具象クラスを継承しなければならないサーバーメソッドは非同期シグネチャなので
@TransactionalのようなAOPベースの動作を壊し、例外サポートもなく、不変値クラスは良いが全部ビルダーで作らなければならない結局、SOAでgRPCを使うなら、gRPCのノイズを隠してテスト可能できれいなコードを得るために多くの配管コードを書く必要があり、ThriftのRPCコンパイラにも似た問題があり、さらに別の問題まで抱えている
最新の.NETとC#でのgRPC体験はかなり良く、MicrosoftもWCFのような既存のRPC技術を終了してgRPCに注力した
protocが生成したバインディング出力を検証する作業は、直接シリアライズするよりも冗長でエラーを起こしやすかったワイヤープロトコルは型安全ではなく、型タグはあるが複数のデータ型で同じタグを再利用している。zig-zag整数エンコーディングも遅い
RPCライブラリとしてはひどく、実際に使った中でこれより悪かったのはFlatBuffersだけだった
この違いがJavaのせいなのか、gRPC技術そのもののせいなのか気になる
Java+gRPCに特化した問題なのか気になる
長年APIを作ってきており、gRPCとHTTP/RESTの両方を使ったことがあり、OpenAPI仕様からGoクライアントとサーバーを生成するライブラリ https://github.com/oapi-codegen/oapi-codegen も公開している。
この文章で OpenAPIとRESTを区別するやり方には同意しにくい。OpenAPIはHTTP APIの動作を文書化する方法であり、RESTful APIでも完全に任意のAPIでも表現できる。ツールが解釈できるようにAPIを記述するスキーマ言語という点で、gRPCプロトコルを指定するProtocol Bufferファイルと概念的に似ている。
gRPCはprotoをやり取りするRPCメカニズムだが、Googleがprotobufを公開した際に内部RPC層であるStubbyは公開せず、gRPCはStubbyほど優れてはいない。それでも転送効率は高く、拡張も比較的容易だ。
ただしgRPCは主流のHTTPライブラリほど堅牢なエコシステムではないため、ロギングや認証のようなミドルウェアを自前で実装しなければならないことが多く、異なる言語で実装されたサービス間のRPCでは特にそうだ。
ただ、gRPCの本当の問題はprotoファイルにあると思う。すべてのクライアントがサーバーと互換性のある
.protoファイルでビルドされていなければならないため、発見可能なプロトコルではない。HTTP APIはOpenAPI記述がなくてもcurlや手書きのコードで呼び出せるので結合がより疎で、そのぶん作業やデバッグがしやすい。Roy Fieldingの2000年の博士論文で定義されたRESTでは、ルートURLに
GETすると200 OKのレスポンス内にリンク群があり、そのリンクをたどりながらAPIが提供するすべてのリソースを探索できる必要があった。階層構造は許容されるが、すべてがリンクツリーのどこかからアクセス可能でなければならず、これは発見可能性を与えるための意図だった。実際、この20年間働いてきた先ではほぼすべて
POST resource_name/resource_id/sub_resource/sub_resource_id/mutation_typeか、会社の冪等性処理の方針に応じてPUT resource_name/resource_id/sub_resource/sub_resource_idを使っており、クライアントはSwagger/OpenAPIのような構造知識をもとに魔法のようなURLを組み立てていた。そのため厳密な人たちは、FieldingのREST定義を実装していないという意味で、実務の方式を「REST」ではなく「RESTful」と呼ぶことがある。
内部・外部クライアントを持つかなり大きなAPIを管理する立場からすると、OpenAPI仕様からコードを生成する流れも理解しにくい。生成されたスタブを埋めたあとでAPI仕様を反復的に改善すると、ツールは新しいスタブをまた生成し、手動マージが必要になり、APIが大きくなるほど関連する変更を見つけにくくなる。
そこで
go/astなどを使ってコードからOpenAPI仕様を生成する怪物を作った。完璧ではないが、EchoとGinの両方で動く95%解で、新しいエンドポイントが必要ならリクエスト/レスポンス構造体と空のハンドラを作ってからドキュメントを生成し、フロントエンド開発者に渡すという形で素早く進められる。ほとんどの開発者はOpenAPIでAPIをどう表現するか悩む必要がなく、ドキュメントも常にコードと一致する。
Apipe.new(GitHun) |> from("search/repositories") |> eq(:language, "elixir") |> order_by(:updated) |> limit(1) |> execute()利用可能なgRPC関数やサードパーティAPIのRESTfulな癖を知らなくてもよく、組み込みドキュメント化と型アクセスも維持できる。
https://github.com/cpursley/apipe
JS/TSプロジェクトにSupabaseのように組み込めるよう、TypeScriptアダプタ層も検討中だ。
const { data, error } = await apipe.from('search/repositories').eq('language', 'elixir').order_by('updated').limit(1)この呼び出しはElixirプロキシ経由で、非同期処理やレート制限のような重い仕事を担わせることができる。
ProtobufのJSONシリアライズをもとにOpenAPI記述を作ってSwaggerで提供できるし、gRPC自体にも組み込みの reflection と、それを使う
grpcurlユーティリティがある。いくつかのFAANGで働いた経験から言うと、Thrift/gRPC は内部サービスのルーティングには本当に有用だと思う
ただし、その複雑さのかなりの部分は、ライブラリ、サービスディスカバリ層、ルーティングなどを作るチームが面倒を見てくれている。RPCプロトコルを使えば、一般的なJSON/RESTサービスでは難しい規模と速度でこうしたことが可能になる
REST APIで動詞がにじみ出てこないものを見たことがないし、バックエンドのサービスメッシュを作る、あるいは2つのローカルサービスをネットワークストリームでつなぐなら、私は常にgRPCを選ぶ
しかし、顧客向けやWebに公開する用途ではgRPCは絶対に使わない。RPCは多くの決定を固定し、「一つのやり方」を強制するからこそ強力だ。逆に、複数の技術スタックにまたがる多様なクライアントがサービスを使う必要があるなら、RESTのほうがずっとよい
POST /api/doThingyにJSON本文を送る形で処理している最低限のHTTPクライアントさえあれば誰でも参加できる簡単なRPCで、すべてのOSとブラウザでうまく動く。URLパスに入れるか、クエリパラメータに入れるか、本文に入れるかで格闘する必要もない
BufやConnectのように不便さを減らそうとするサーバー系を使えば、gRPCでもHTTP経由のJSONを喜んで受け付けてくれる
オンラインゲームやMMOのように、RESTよりはるかにリアルタイム性の高い通信が必要なケースを思い浮かべるが、今はソケット接続の上に何かを載せて使うのか、よく分からない
他に何を試したのかも知りたい
双方向ストリーミングをするのでなければ、gRPCはたいてい時間の無駄だと思う
実行時の推移的依存関係地獄やツールチェーン地獄があるし、Google内部で各実装を管理しているチーム同士も、基本機能がどう動くべきかについて哲学的に合意できていないように見える
自分の言語を使わないチーム、特にGo/Python/Java以外、あるいはそれですら古いバージョンを使っているチームにgRPC APIを公開したり、市販の既製品と統合したり、ブラウザに公開したりすると、結局どれも 中間層 が必要になる
gRPCはおおむね順調だった
バックエンドサービス間通信にRESTを使うのも、性能要件があるならあまり意味がないし、たまに小さなデータを受け取る呼び出しでない限り、人間が読めるプロトコル/APIを使う理由は弱い
oneofのサブタイプのうち、現在フィールドのない型を返すと、自動変換の結果が{ "id": "id", "sub_type_two": { } }のようになった機能的には動作するし、後でフィールドが追加されてもそのコードは動き続ける。だが、Webの世界で空のオブジェクトで応答型を表すのは奇妙で、protobufを書いているときにはこうした問題が見えにくいことがある
想定できるほぼすべてのバイナリプロトコルと比べても、アンチストリーミング 寄りだ
会社でgRPCを使った唯一の経験は、「性能が必要だ」と別のシニア開発者が押し切ったプロジェクトだった
結局フロントエンドが消費できる必要があったのでJSON APIも作ったし、その開発者以外にgRPC経験者はいなかった。肝心のその開発者自身もgRPC Pythonクイックスタート以上には踏み込んでおらず、バグ修正も手伝わなかった
プロジェクトは数多くの理由でひどいものになり、gRPCを正当化できるような規模にもまったく達しなかった
それでも、個人的に少し触ったgRPC自体は気に入ったし、もっと多くの作業と検討が必要だという印象だった。これは単に、私がJSON APIのほうをずっと多くやってきたからかもしれない
ConnectRPC https://connectrpc.com/ を楽しく使っている
gRPCの問題のある部分をかなり改善してくれるし、SafariがWebTransportを採用すれば、ConnectRPCはより良いストリーミングを実現できるのではと期待している
最初は https://buf.build はやりすぎだと思っていたが、サードパーティのprotoファイルを1つずつダウンロードせずにインポートできる機能が決定打だった
deps:- buf.build/landeed/protopatch- buf.build/googleapis/googleapis自動SDK生成も大きい。以前は約9言語向けSDKの自動生成を褒めようと思っていたが、ここ1〜2日で更新されたらしく、今では16言語に加え、OpenAPIやほかの新機能まで見える
私もgRPCストリーミングの偽りの約束に振り回されたし、この文書は自分の経験とぴったり一致していた: https://connectrpc.com/docs/go/streaming/
そのおかげでブラウザでも双方向ストリーミングが使える
Googleが業界全体に心理戦でも仕掛けたかのように、内部サービス間通信で gRPC を使わせる流れを作ったように感じる
gRPC の開発者体験は REST よりかなり悪い
誰かにエンドポイントを呼ぶ簡単なコマンド 1 つすら渡せず、標準化されていない追加ツールが必要になる。しかも生成されるクライアント側コードは、どの言語でもめったに見ないほど醜い塊だ
プロトコル変更 1 つで、どの下位コンシューマーを更新して再デプロイすべきかを静的に把握できるので、数週間かかる作業が 1 時間の変更で済むことがある
受け取るメッセージと送信するメッセージが即座に検証されることも分かるし、後で復元するために安価に保存しておくこともできる
proto によって非常に読みやすい API ドキュメントが得られ、コードやビジネスロジックに埋もれない。バージョニングと廃止の意味論も組み込まれており、map を除けばより豊かなデータ構造もサポートしている
それに比べると、JSON はバックエンドでは肥大化して古臭く見える
データ型と関数シグネチャを書いておけば、実際の関数のように呼び出せるものが手に入り、シリアライズ/デシリアライズの定型処理ではなくビジネスロジックに集中できる
Thrift も同様に、全部を手で組み立てるやり方よりはるかに優れているし、GraphQL はさらに良いと思う
Go でも再生成と共有 proto のバージョン管理を把握するのは面倒で、言語が 1 つ増えるたびにさらに悪化する
それでも、すべてのスタートアップはマイクロサービス 100 個と gRPC が必要だと思っているようだ
「API が REST API であるなら、クライアントは URL の形式を理解する必要はなく、その形式はクライアントに渡す API 仕様の一部ではない」という文は、Roy Fielding の REST 定義と通じている
Fielding は、REST API は初期 URI と標準化されたメディアタイプの集合以外の事前知識なしに開始されるべきで、その後のすべてのアプリケーション状態遷移は、サーバーが提示した選択肢をクライアントが選ぶ形で行われるべきだと書いている
https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypert...
何度も語られてきた話ではあるが、本当に RESTful なシステムでは、クライアントに与える「API 仕様」は初期エントリポイントの URI/URL だけであるべきだという点はいまでも興味深い
個人的には肥大化していて、現実の問題を解決しないと思う
https://en.m.wikipedia.org/wiki/HATEOAS
特に、クライアントが操作可能なリソースやリクエスト/レスポンスモデルをどう発見するのかが分からない
設定はそうかもしれないが、API 仕様は URL 以上のものを含むべきであり、そのシステムが使うメディアタイプを詳しく説明する必要がある。つまり、HTTP リクエスト/レスポンス本文の説明が大部分を占める
実際、リンク先にも「REST API は、リソースを表現しアプリケーション状態を駆動するメディアタイプの定義に、説明のための努力のほぼすべてを費やすべきだ」と書かれている
要するに、単に
application/jsonを返すのではなく、特定の+jsonのようなものを返すことになり、そこに含まれるのは単なる一般的な JSON ではなく、アプリケーションが理解すべき業務データであることが多い大衆的な議論では初期 URL にばかり焦点が当たり、Fielding の言う「メディアタイプを説明せよ」という作業の大半が抜け落ちてしまう。だから「URL 1 つで十分だ」と聞かされた人が「残りの仕様はどこにあるのか?」と尋ねるのは当然だ
私もまた REST を理解していないブロガーの話かと思って記事を開いたが、著者は少なくとも基本概念は理解しているようだ
gRPC も好きで、商用プロジェクトではかなり魅力的だが、個人的あるいは理想主義的なプロジェクトなら REST のほうがよいと思う
データセンター内部で gRPC を使うのは好きではない
性能を理由に選ばれがちだが、gRPC は高性能と言えるほどではなく、公開クライアントの品質も、特に C++/Java の中核実装以外ではかなり低い。たとえば Node.js 実装がそうだ
protobuf を API 仕様として使うこと自体には反対しないが、TCP 上のフレーミングプロトコルと組み合わせて使えるべきだと思う。ただ、その種の RPC には明確な支配的選択肢がない
Web ベースの API では可読性のあるペイロードのほうを好むが、通常は JSON を使うことで型の厳密さが緩み、バックエンド言語間の相互運用性の問題が生じる。特に Node.js では
JSON.parseがスキーママッピング実装のように使われがちだこれを正しくやるには、スキーマからエンコーダーとデコーダーを明示的に生成する必要があり、そうなると JS の文脈で JSON を使う利点はある程度薄れてしまう
Microsoft の TypeSpec プロジェクトも追っている: typespec.io
単一言語の技術スタックなら重要性は下がる。そして Google の主要言語以外を使うなら、その分だけ体験は良くない可能性が高い
gRPCは、Googleの外にいるそれ以外の人々にとっては、不必要に敷居が高く感じられた
gRPCのJSクライアントは無駄に重く、かなり不透明だ。アイデアは良いが、RESTの「シンプルさ」に慣れた人たちと比べると実装面が惜しい
RPCは、データモデルのカーディナリティや関係性をひとつの規範的なパターンに無理やり押し込める必要がないため、意味論的にはより保守しやすい。APIが急速に変化する世界では、美しいRESTfulエンティティを当てはめるのは難しく、大きなチームと変化する要件/オーナーシップのもとでは、サービス中心設計のほうが優れている
フロントエンド側はバックエンドシステムを保守しない。理解しやすいAPIを望み、RESTで抽象化できるエンティティを求める。そうした設計の最終的な受益者だ
RESTにかかる労力は、APIを販売しており、サードパーティ開発者が主要顧客である会社では意味がある
バックエンド開発では、protobufとバイナリのワイヤエンコーディングのほうが簡単だ。APIを定義して静的型付けの方法でサービス間共有でき、メッセージのエンコード/デコード時間も減る。JSONは意味論的でも型付きでもなく、オーバーヘッドも大きい
逆にフロントエンドは、テキストとJSONをネイティブに扱う。protobuf定義をダウンロードしたり、バイナリデータを二級市民のように扱ったりしたくないし、ツールともきれいに噛み合わない
gRPCにはルーティング、リトライ、付加チャネル、ストリーミング、プロトコル廃止のセマンティクスがうまく組み込まれているが、フロントエンドにはほとんど現れない。すべてバックエンド利用者のためのものだ
結局のところ、100% フロントエンド/バックエンドのツール格差であり、インターフェースとユーザビリティの不一致だ
基本的にソースを見ることができ、人間が読めて、検査しやすい
gRPCは機械同士が効率的に会話するためのもので、コーディングでもリクエスト/レスポンスの検査でも、人が介在すると少し不便だ
文脈と目標が違っていたため、このような使い勝手の差は理解できる
私の見るところでは、buf.build側の実装が良い
https://buf.build/blog/protobuf-es-the-protocol-buffers-type...
契約を伴うバイナリRPCのようなプロトコルが必要に見えるので多くのものがそれを選んだのだろうが、GoLangから離れるほど徐々に悪くなる
単純なCRUDサービスならRESTでも十分だ