public-grpc-api-design について
公開 gRPC API を設計するプラットフォームチーム向けのスキルです。公開エッジに gRPC が適するかを判断し、Proto のガバナンス、ストリーミング、エラー、変換、外部認証を検討します。
主な機能
- 01公開エッジでの gRPC 適性評価
- 02Proto パッケージとバージョン規則
- 03互換性チェックのゲート設定
- 04ストリーミングとエラー設計
- 05変換方式の選定と認証監査
活用シーン
- 01既存の gRPC サービスの公開可否評価
- 02公開 API の Proto バージョン設計
- 03ブラウザーや汎用 HTTP クライアント向けの変換設計
- 04管理用・デバッグ用 RPC の監査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ developer-platform-skills --skill public-grpc-api-designインストール後、Claude Code で/public-grpc-api-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/grpc-error-http-mapping.mdproto-governance-reference.mdpublic-grpc-case-studies.md詳細
パブリック gRPC API の設計
パブリック gRPC のインターフェースを設計する担当者として、gRPC をプラットフォームのパブリックなエッジに置くべきかどうかを判断し、置く場合は、初めて利用する開発者にも安全に使ってもらえるよう、proto のガバナンス、ストリーミングの方針、エラーモデル、トランスコーディングのアーキテクチャ、外部認証を設計します。
最初に問うべきことは、決して「最高のパブリック gRPC API をどう設計するか」ではなく、「このワークロードはパブリック gRPC に適しているのか、また、どのエッジを通すべきか」です。同じテーマを扱う samber/developer-platform-skills@api-integration-surface-strategy も、この全体的な判断の一部を担います。それでも、ここで改めて問い直してください。チームが「パブリック gRPC」を前提とし、その妥当性を検証しないまま相談に来ることがあるからです。
メモ(推奨): メモリをファイルに保存する場合は、developer-platform-context.md の使用を検討してください。別のメモリシステムを使用している場合は、そちらを利用してください。このファイルは参考用であり、必須ではありません。タスクごとに情報を別のセクションに分けてください。完了したタスクは削除してください。タスクには日付を付け、プロジェクト全体に関する情報には日付を付けないでください。タスクによっては、インタビューへの回答が異なる場合があります。
確認事項
設計に着手する前に、以下の質問をしてください。回答によって後続の手順が変わります。まとめて質問してください。これは戦略インタビューではなく、実務的な設計タスクです。
- gRPC はすでに内部で稼働していますか、それとも新規開発ですか?内部で稼働している場合、サービス定義に、外部利用を想定していない管理・デバッグ・監視用の RPC が含まれている可能性があります。そのため、手順 7 の最後に行う公開範囲の監査は、任意ではなく必須です。
- 外部の利用者は誰ですか。生成済み SDK を接続するエンジニア(コントロールプレーン/インフラストラクチャの利用者)、独自のスタブを生成する proto の直接利用者、あるいはブラウザーベースのクライアントですか?(手順 1 の判定と手順 6 の選択肢に影響します)
- ブラウザークライアントは、このインターフェースを直接呼び出す必要がありますか?ブラウザーは構造上、gRPC の HTTP/2 フレーミングを扱えません。「はい」の場合、手順 6 の選択肢から生のパブリック gRPC は除外されます。
- すでにパブリック REST インターフェースがありますか。また、gRPC インターフェースは REST と一貫性を保つ必要がありますか(同じリソース、同じバージョニング方針など)?
- プッシュ、テーリング、テレメトリの取り込みなど、本当にストリーミングが適したワークロードはありますか。それとも「gRPC にストリーミングがあるから」ということだけが採用の理由ですか?(手順 4 に影響します)
- 優先順位を付け直してください。インターフェースのリリース期限はいつですか。一度限りの公開ですか、それとも複数のプロダクトが何年も共有する契約ですか。許容できる工数の上限(チームの作業時間、新たに運用可能なランタイム、利用者に見える移行への許容度)はどれくらいですか?(手順 6 の選択肢の順位が変わります)
ハーネスに永続メモリがある場合は、設計で確定した決定事項(判定結果とその判断基準、proto のパッケージとバージョン方式、ストリーミング方針、選択したトランスコーディング段階とその次の段階への移行条件、呼び出し元の種類ごとの認証フロー)を保存してください。そうすれば、後の実行(新しい RPC の追加、proto のレビュー、ドキュメントの作成)で、設計を一から導き出すのではなく、その設計を出発点にできます。
利用者の種類
設計を左右するのは、利用者がどのように契約に接するかです。
- SDK 経由の利用者 — 公開済みの生成クライアントライブラリを使用するエンジニア(Temporal のモデル:GitHub とスキーマレジストリで proto を公開し、SDK が gRPC を直接ラップします)。生のパブリック gRPC が実績のある選択肢となる利用者層はこれだけです。スタブの再生成も通常のツール作業として受け入れます。
- proto の直接利用者 — 独自のスタブを proto から生成するサードパーティです。破壊的変更の影響を直接受けます。
protoc/buf の習熟度を前提にできず、多くの場合インストールされていないツールでバイナリペイロードをデバッグすることになります。proto のガバナンス(手順 2~3)は、この利用者向けに設計されています。 - ブラウザーおよび汎用 HTTP クライアント — 生の gRPC HTTP/2 フレーミングをまったく扱えないため、いずれかが利用者に含まれるなら、プロキシ層が必須です。この利用者はトランスコーディング、gRPC-Web、または Connect を介してのみインターフェースを利用できます。手順 6 は、この利用者のためにあります。
実際に存在する利用者のうち、最も機能が限られた種類に合わせて設計してください。SDK 経由の利用者しか使えないインターフェースは、他の二種類を暗黙に締め出します。これは技術上の既定値ではなく、ビジネス上のリーチに関わる判断です。手順 1 で意識的に決定してください。
ワークフロー
- 「いつgRPCを使うか」のゲートを実施し、パブリックエッジで実際に使うプロトコルを決めます。
- proto のパッケージとバージョニングの規約を定めます。
- 破壊的変更を防ぐゲートを導入します。buf とアノテーションの盲点チェックです。
- ストリーミングの方針を決めます。デフォルトは単項です。
google.rpc.Statusに基づいてエラーモデルを設計します。- トランスコーディング/ゲートウェイのアーキテクチャを選びます(優先順位付きの選択肢)。
- 外部認証と TLS を設計し、公開範囲に関するリスクを監査します。
各ステップについて、以下に順番にセクションを記載します。
1. 「いつgRPCを使うか」のゲート
実務者の総意は、二者択一ではなくデュアルスタックを支持しています。両端を管理できる内部のサービス間通信には gRPC を使い、管理できないエッジでは REST またはトランスコードした JSON を使います。追跡された推定値によると、直近の集計ではパブリック API の大多数(約 83%)は依然として REST です。根拠となる詳細と、それぞれの数値の精度に関する注意点については、references/public-grpc-case-studies.md を参照してください。
- Google は REST ファサードと、レイテンシが重要な場合に使うネイティブ gRPC スタブを併せて公開しています。
- Square、Dropbox、Cloudflare は内部で gRPC を実行しつつ、公開向けの開発者インターフェースは REST を前面にしています。
パブリックエッジでデフォルトとして REST が選ばれる理由は、好みではなく具体的な仕組みにあります。
- ブラウザーは gRPC の生の HTTP/2 フレーミングを扱えません。ブラウザーベースの利用者がいる場合、ただちにプロキシレイヤーが必須になります。
- Protobuf はバイナリ形式です。ネットワークタブで内容を読めず、トラフィックの検査には、サードパーティーがほとんど持っていない
grpcurlクラスのツールが必要です。 - 外部の呼び出し元はすべて、内部のシステム全体で償却できる
protoc/スタブ再生成のコストを負担します。 - Google Cloud のエンジニアリングブログは、REST/OpenAPI の代わりに gRPC を使う運用コストを次のように説明しています。「特に API 管理ツールなどのプロキシで、API の動作を拡張または修正できる機会がはるかに限られます」。
- 内部向けを優先して構築された gRPC サービスには、公開時の検出可能性を一度も監査されていない管理用およびデバッグ用の RPC が含まれています。
それでもパブリック gRPC を正当化できる実際の条件は次のとおりです。3 つとも検討する価値があり、いずれか 1 つで十分な場合もあります。
- 利用者が SDK 経由、または proto に精通していることが確認されています(質問 2)。明確な先例は Temporal.io です。Temporal の Cloud Ops API は「オープンソースのパブリック HTTP API および gRPC API」であり、生成された SDK のツールに精通したコントロールプレーンのエンジニアが対象だからこそ実現可能です。それでも Temporal は HTTP API も併せて提供しています。
- ワークロードが真にストリーミング型であり、REST ではポーリングまたは別途 WebSocket レイヤーが必要になります(まずステップ 4 のコストに照らして検証してください)。
- パフォーマンスが計測上のボトルネックであり、利用者がクライアントツールのコストを負担できます。バイナリ形式の Protobuf は、JSON よりサイズが 3~11 倍小さく、シリアライズが 8~12 倍速いと一般に言われていますが、これはベンダーやブログのベンチマークであり、あくまで傾向を示す値です。正確な値として引用しないでください。
ゲートの結論が「内部は gRPC、外部は JSON」である場合、設計ドキュメントには Google の Apigee チームによる、利用範囲に関する次の根拠を記載してください。「すべての開発者が gRPC サービスを利用できるだけのスキルや経験を持つわけではありません。そのため、API プロバイダーは gRPC サービスのみを公開することで、潜在的な利用者を逃すリスクがあります」。ゲートの結論はステップ 6 に引き継がれ、トランスコーディングの選択肢のうち、どの段階を検討するかが決まります。
2. Proto パッケージとバージョニングの規約
公開 Proto パッケージは、生成されるすべてのクライアントのインポートパスの一部となります。外部の利用者が依存した後では、ここでの規約の誤りは破壊的変更になります。Google の AIP コーパスは、gRPC における正式な公開スタイルガイドに最も近いものです。独自のルールを作るのではなく、これを採用してください。
- API ごとに Proto パッケージを 1 つ設け、メジャーバージョンのコンポーネントで終わらせます(
example.library.v1)— AIP-215。 - ディレクトリ構造はパッケージと完全に一致させます(
example/library/v1/)。ファイル名にバージョンを含めてはいけません(v1.protoは奇妙な生成インポートを生みます)— AIP-191。 - メジャーバージョンのみを使用します。
v1とし、v1.0や SemVer のマイナーバージョンは使いません。バージョンはパッケージに含め、トランスコードされたサーフェスの最初の URI セグメントにも反映します。v1は安定版を意味します。v1beta/v1alphaは安定版になる前のチャネルです。v1でベータ機能を提供している場合は、レビューで指摘すべき事項です — AIP-185。 - サーフェスはリソース指向で設計します(AIP-121)。名前付きリソースと、標準の List/Get/Create/Update/Delete メソッドを用意し、標準の動詞では本当に対応できない場合に限って、
:verbサフィックスを使うカスタムメソッドを追加します。 - RPC ごとに専用の
<Method>Request/<Method>Responseラッパーメッセージを 1 つずつ用意します。スカラー値を直接使ったり、メッセージを共用したりしてはいけません。共用メッセージでは、フィールドを変更するたびに、それを参照するすべての RPC に影響します。一方、ラッパーにはワイヤー互換性を損なわずに任意フィールドを追加できます。 - 公開サーフェスでは、フィールドに関する以下の規則を必ず守ります。
- タグを再利用したり、番号を振り直したりしてはいけません。
- 削除したフィールドはすべて
reservedにします。 - すべての enum のゼロ値は
_UNSPECIFIEDにします。 - 独自実装ではなく、よく知られた型(
google.protobuf.Timestamp、FieldMask、google.rpc.*)を優先します。
- 言語ごとのパッケージオプション(
java_packageなど)を設定し、生成コードが生の Proto パスをそのまま露出せず、各言語の慣用的な形式になるようにします。
境界:兄弟プロジェクトの samber/developer-platform-skills@api-versioning-policy は、各種パラダイムに共通する告知期間、終了の通知、ガバナンスの一般規則を担当します。このスキルが担当するのは、バージョンの gRPC ネイティブなエンコード方法と適用方法です。具体的には、上記のパッケージサフィックス規則とステップ 3 のゲートです。新しいメジャーバージョンは以前のバージョンに依存してはならず、安定版のリソース名はメジャーバージョン間で同一に保ち、v2 クライアントが v1 クライアントの作成したリソースを指定できるようにします。
ファイル内の記述順序、各規約を実際に適用する buf lint ルール名、チャネルベースとリリースベースのバージョン進化、非推奨化の仕組みについては、references/proto-governance-reference.md を参照してください。
3. 破壊的変更を防ぐゲート
Protobuf のエンコーディングが保証するのはワイヤー互換性だけであり、クライアントコードの互換性ではありません(AIP-180 における区別)。そのため、ゲートはレビュアーの注意力ではなく、ツールで実施する必要があります。
- すべての PR で
buf breaking --against <main>をハードな CI ゲートとして実行し、STANDARD セットでbuf lintも実行します。公開サーフェスでは、最も厳格なルールカテゴリ(FILE)をデフォルトにしてください。パッケージレベルのチェックでは見逃される、生成コードのインポートに関する破壊を検出できます。ゲートの検出結果を、リリース時の基準と承認記録を含むパートナー向け変更履歴にまとめるには、samber/developer-platform-skills@api-changelog-automationを利用します。 - ツールでは検出できても、レビュー担当者が見逃しやすい落とし穴を把握しておきます。
- 名前の変更は、意味的には削除と追加に相当します(常に破壊的変更です)。
- フィールドを
oneofに移動したり、oneofから移動したりすると、生成された Go スタブが破損します。 - メッセージを別のファイルに移動すると、生成コードのインポートが破損します。ファイルパスも契約の一部です。
- buf に残されている盲点:
buf breakingはgoogle.api.httpのようなカスタムオプションを検査しないため、proto のゲートがすべて問題なしでも、REST 向けの URL/動詞のマッピングを破壊してしまう可能性があります。専用のチェックを追加してください。アノテーションから導出した HTTP サーフェス(パス、動詞、ボディのマッピング)をスナップショット化して CI で差分を確認するか、トランスコードされたルートをコントラクトテストします。これは理論上の問題ではなく、明確に名前の付いた障害モードです。 - バージョン境界をまたいで、両方向をテストします。旧クライアントと新サーバーの組み合わせ、および新クライアントと旧サーバーの組み合わせです。「スキーマを解析できる」ことは、「異なるバージョン間で RPC が成功する」こととは異なります。
- 公開サーフェスの規模が大きくなったら、Buf Schema Registry のレビューフローに移行し、人による最終確認を追加します。破壊的変更を含むプッシュは、記録が残る承認/却下キューに入ります。これはレビュー委員会によるゲートに相当する、proto ネイティブな仕組みです。buf を利用していないチームは、チェックイン済みの
proto.lockに対して Salesforce のproto-backwards-compat-maven-pluginを利用できます。
非推奨化も proto ネイティブです。メッセージ、列挙型、列挙値、サービスで利用できる deprecated = true に、理由と置き換え先を説明する AIP-192 コメントを添えます。チャネルによって期待される対応は大きく異なります。
- Alpha は予告なく削除される場合があります。
- Beta は、推奨される 180 日間の非推奨期間を経た後に削除される場合があります。
- 安定版コンポーネントのインプレースでの破壊的変更は、「極端な措置」であり、セキュリティ上または規制上の必要性がある場合に限られます。
通知期間自体については、samber/developer-platform-skills@api-versioning-policy が扱います。
4. ストリーミングの方針:デフォルトは単項呼び出し
gRPC の 4 種類の呼び出し形式は、公開インターフェースで同じコストがかかるわけではありません。デフォルトでは単項 RPC を公開し、ストリーミングメソッドはそれぞれ、コストを受け入れたうえで意図的に決定し、記録します。決して無料の機能として扱ってはいけません。
- サーバーストリーミングは、プッシュやテーリングのユースケースが実際にある場合に、最初に検討するストリーミング方式です。ゲートウェイ経由の SSE にも、Connect のストリーミングエンコーディングにもきれいに対応します。通知、ログのテーリング、段階的な結果の配信などが該当します。
- クライアントストリーミングと双方向ストリーミングには、外部の利用者にとって明確なコストがあります。Microsoft 自身のガイダンスでも、「パフォーマンス上の理由から単項呼び出しを双方向ストリーミングに置き換えるのは高度な手法であり、多くの状況には適していない」と警告しています。
- ストリームが途中で一部のデータを送信した後に失敗するため、外部 SDK の利用者はすべて、単項呼び出しの再試行では不要な再起動や再開の管理が必要になります。
- 再試行は、クライアントライブラリによる透過的な管理ではなくなります。
- ブラウザーでは、HTTP/1.1 上でクライアントストリーミングも双方向ストリーミングも一切できません。これはツールの不足ではなく、プロトコルの制約です(例外は HTTP/2 上の Connect です)。
- ストリーミングでは、負荷分散の性能が低下します。L4 ロードバランサーのもとでは、HTTP/2 の多重化により、クライアントの全ストリームが接続の存続期間中、単一のバックエンドに固定されます。公開 gRPC のエッジには、L7 の HTTP/2 対応負荷分散と、ロードバランサーのアイドルタイムアウトより短い間隔に設定したクライアントのキープアライブが必要です。ベンダーごとの具体的なタイムアウト値については、references/public-grpc-case-studies.mdを参照してください。
- AIP-127 では、双方向ストリーミングを除くすべての RPC に HTTP マッピングが必要です。HTTP/1.1 では双方向ストリーミングを扱えないため、すべての双方向ストリーミングメソッドには、文書化された非ストリーミングの代替手段も必要です。そうしなければ、変換後のインターフェースからは利用できません。
決定事項は文書化するだけでなく、確実に適用します。インターフェースを単項呼び出し専用にすると決めた場合は、buf の UNARY_RPC リントカテゴリを有効にし、将来の PR でゲートウェイ、SDK、トランスポートが扱えないストリーミングメソッドが気づかれないまま追加されることを防ぎます。兄弟スキルの samber/developer-platform-skills@api-idempotency-retry は、再試行の仕組み、期限の伝播、バックオフのジェネリクスを担当します。このステップでは、公開契約で提供する呼び出し形式のみを決定します。
5. エラーモデル:google.rpc.Status の仕組み
分類方針(コードカタログ、メッセージの記述、再試行可能性の通知)は、関連する samber/developer-platform-skills@api-error-design が担当します。このステップでは、gRPC ネイティブの仕組みと、公開インターフェースで問題を引き起こす2つの落とし穴を扱います。
google.rpc.Statusを使用します。code:17個の標準コードのいずれか。message:開発者向けの英語。ユーザー向けにローカライズしたテキストは使用しません。details:google/rpc/error_details.protoの型付き追加情報(最初にErrorInfo、フィールド違反にはBadRequest、RESOURCE_EXHAUSTEDと併せてRetryInfo)。
- 該当する中で最も具体的なコードを返します(両方に当てはまる場合は
FAILED_PRECONDITIONよりOUT_OF_RANGEを優先します)。防御的な習慣としてすべてをUNKNOWN/INTERNALにすることは、名前の付いたアンチパターンです。すべてのステータスダッシュボードが1つの区分にまとめられてしまいます。 - 送信するエラーは、文章だけでなく構造的にサニタイズします。
DebugInfoは内部専用と明記されています。レスポンスを送信する前にdetailsから除外してください。messageだけをサニタイズしても不十分です。 - 落とし穴その1:マッピングテーブルの適用方向。 gRPC プロジェクトの公式ドキュメントには、明確にこう記載されています。「サーバーは、このテーブルを使用して HTTP ステータスコードを決定してはなりません……マッピングは対称ではなく、1対1でもありません。」標準の gRPC↔HTTP テーブルは、
grpc-statusが付かずに届いたレスポンスをクライアントが解釈するためのものです。エラーを外向きに変換するゲートウェイには、実際に使用しているトランスコーディング実装を確認したうえで、独自のマッピングを明確に定める必要があります。 - 落とし穴その2:HTTP では区別が失われます。 一般的なゲートウェイマッピングでは、3つのコード(
INVALID_ARGUMENT、FAILED_PRECONDITION、OUT_OF_RANGE)がすべて400になり、CANCELLEDは標準外の499にマッピングされます。HTTP ステータス行では情報が失われ、本文の構造化されたエンベロープが信頼できる情報源であることを、利用者に伝えてください。 - エンベロープがゲートウェイを通過しても維持されることを確認します。
- Envoy では
convert_grpc_status: trueが必要です。また、すべての詳細型を proto ディスクリプターに含める必要があります(型が欠けていると、通知なく削除されます)。 - Connect はエラーをネイティブに JSON としてシリアライズします。
- grpc-gateway のデフォルトハンドラーはメッセージを平坦化し、
detailsを完全に削除します。構造化された詳細情報が重要な場合は、カスタムエラーハンドラーが必須です。
- Envoy では
追加の通知がなくても一般に安全に自動再試行できるのは UNAVAILABLE のみです(AIP-194)。それ以外は、明示的な RetryInfo または呼び出し元の判断が必要です。17コードすべての一覧、ゲートウェイマッピング、詳細情報カタログ、ゲートウェイごとの情報維持の仕組みについては、references/grpc-error-http-mapping.md を参照してください。
6. トランスコーディングとゲートウェイのアーキテクチャ
このステップで決めるのは、非 gRPC コンシューマーにどのエッジを提供し、マッピングをどう表現するかです。AIP-127 の google.api.http アノテーションは、すべての実装が収束した慣例です(「双方向ストリーミング RPC を除く各 RPC について、API は HTTP 定義を提供しなければならない」)。選択肢を順位付けすると、次のとおりです。
- 労力:
raw public gRPC edge > grpc-gateway == Envoy transcoder > Connect-RPC - 価値:
Connect-RPC > Envoy transcoder > grpc-gateway > raw public gRPC edge - 効率:
Connect-RPC > Envoy transcoder > grpc-gateway > raw public gRPC edge
ここでの労力とは、チームが運用し、再生成するものを指します。価値とは、エラーとストリーミングの忠実性を保ちながら、proto コントラクトあたりに到達できる対象の広さです。grpc-gateway == Envoy の労力が同等なのは事実です。proto の変更ごとに Go プロキシを再生成する作業と、ディスクリプターセットとフィルター設定の同期を保つ作業は、種類こそ違いますが、同程度の労力です。「すでに Envoy を運用している」という条件が、この順位を変えるのはまさにそのためです。
- デフォルトの選択肢:Connect-RPC。 1 つのサーバーで Connect、gRPC、gRPC-Web を利用できます。
- プロキシは不要です。
- ブラウザーにネイティブに対応します。
- HTTP/2 上で完全なストリーミングに対応します。
- エラーは最初から JSON 形式です。
- gRPC とワイヤー互換です(Google の相互運用性テストスイートで検証済み)。
2024 年 4 月に CNCF Sandbox に加わり、CrowdStrike、PlanetScale、RedPanda、Chick-fil-A、Bluesky、Dropbox ですでに本番環境で利用されています。Connect のローンチ時に Buf が示した説明は、「概念的に、gRPC プロトコルは Protobuf エンコードされたボディを持つ HTTP です」というものでした。Connect により、多くの新しいサーフェスでゲートウェイを選ぶ判断が不要になります。
- Envoy がすでにメッシュのデータプレーンであり、2 つ目のランタイムを運用することが実際のコストとなる場合は、Envoy の gRPC-JSON トランスコーダーを優先します。ディスクリプター駆動で、生成コードのつなぎ込みが不要なため、Kubernetes 環境でよく使われる方法です。Envoy のほうが劣っているからではなく、「すでに運用している」ことは正当な理由です。
- カスタムエラーハンドラーとともに既存の Go への投資が活用されている場合は、grpc-gateway を維持します。コード生成の手順が必要で、各リクエストで JSON↔protobuf の二重パースが発生します。また、デフォルトのエラー処理は 3 つの選択肢の中で最も弱いものです(ステップ 5.6)。
- raw public gRPC edge は最も不利な選択肢です。 忠実性は最高(エンドツーエンドでバイナリー、完全なストリーミング、トランスコーディングによる損失ゼロ)ですが、労力も最大です(L7 負荷分散、WAF、TLS 終端に対応した gRPC 対応エッジが必要です。Cloudflare はこれを製品として提供しています)。さらに、内部 RPC の公開範囲をすべて監査し、すべてのコンシューマーに Protobuf ツールを用意してもらう必要があります。効率の順位ではすべての選択肢に負けます。それでも優先する条件は、ステップ 1 の Temporal に関するトリガー、つまり SDK を介してアクセスするコントロールプレーンの対象者であることが確認されている場合です。その場合でも、Temporal が行っているように、HTTP サーフェスも併せて公開します。
- 降格ではなく、選択肢から除外: 質問 3 でブラウザーのクライアントが直接呼び出すとされた場合、唯一のサーフェスとしての raw public gRPC は選択肢になりません。ブラウザーは構造上、gRPC フレーミングを扱えません。この選択肢をメニューの最下位に残しておくと、スコープとしていつの間にか復活します。削除し、上記 3 つから選びます。
この順位付けはデフォルトであり、絶対的なルールではありません。質問 6 とチームについてわかっていることを基に、順位を付け直してください。
- Envoy に精通したプラットフォームチームにとって、トランスコーダーの追加コストはほぼありません。
- リリース期限が厳しい場合は、チームがすでに運用しているものを優先します。
- 複数のプロダクトが何年にもわたって共有するコントラクトでは、今四半期の労力よりもエラーの忠実性(価値の軸)を重視します。
パフォーマンスが重要なコンシューマーには、順位にかかわらず、ネイティブ gRPC または Connect のバイナリーモードを推奨してください。JSON トランスコーディング経路では、どれも実際にパースコストが発生します。
各選択肢のストリーミング対応、ブラウザー経路、プロキシ要件をまとめた仕組みの表と、出典付きの引用については、references/public-grpc-case-studies.md を参照してください。
7. 外部認証、TLS、公開範囲の監査
内部認証の考え方は適切ではありません。mTLS は_ワークロード_を認証し、メッシュ内では十分にスケールしますが、面識のない開発者にクライアント証明書を発行・失効させるのは、運用上現実的ではありません。パブリックエッジでは、アプリとユーザーを認証します。メッシュがなくなるわけではなく、その開始地点が 1 ホップ奥になるのです。実施順序は次のとおりです。
- まず TLS を適用します。これは必須です。認証情報は gRPC メタデータとして送信され、トランスポートセキュリティがなければ平文になります。ほとんどの実装では、暗号化されていないチャネルでの認証情報の送信自体を拒否します。
- 呼び出し元の種類に応じた OAuth2 フローを使用します。マシンやサービスからの呼び出しには Client Credentials、人間が対話的に認証する場合には Authorization Code + PKCE を使用します。トークンは
authorization: Bearer <token>メタデータとして渡し、サーバーインターセプターで検証します。 - トークンの仕組み:
- トークン形式には JWT を使用し、有効期限クレームを含めて強固に署名します(RS256)。
- 認可サーバーに対するトークンイントロスペクションを行い、すべての呼び出しで負荷がかからないようにキャッシュします。
- 外部呼び出し元の数が十分に増えたら、独自実装の発行者ではなく、実績のある ID プロバイダーを使用します。
- スコープはメソッド単位で適用します。「この呼び出し元は認証済みか」ではなく、「このトークンにはこの RPC を呼び出すスコープがあるか」を確認します。
- API キーは認証を補完するものであり、置き換えるものではありません(Google 自身による Endpoints の区別)。キーはクォータの割り当てや大まかなアクセス制御のためにプロジェクトや利用者を識別するもので、認証済みの ID ではありません。
- レート制限を超えた場合は
RESOURCE_EXHAUSTED(変換後は 429)を返し、RetryInfo詳細も付与します。これにより、呼び出し元は同じ制限を繰り返し叩くのではなく、リクエストを控えることができます。 - 内部向けを前提に構築されたサービスでは、公開範囲の監査が必須です。本番環境ではサーバーリフレクションを無効にします(ポートが公開されると、攻撃者の API 探索ツールになります)。次に、外部公開を想定して設計されていない管理、デバッグ、監視用 RPC を含め、すべてのメソッドを洗い出します。TLS と認証を有効にしても、この穴は塞がりません。
図示するリファレンスアーキテクチャは、パブリックゲートウェイ(ベンダー製品の例として Apigee、Cloudflare、Envoy を挙げます)で TLS 終端、トークン検証、メソッド単位の認可、レート制限を行い、その先に mTLS が再開する内部メッシュを配置する構成です。
障害モード
アンチパターンのチェックリストです。それぞれ、監査で直接指摘される項目です。
- サーバーまたはゲートウェイが、gRPC プロジェクトによってサーバーでの使用を明示的に禁止されている、クライアント側の gRPC↔HTTP マッピングテーブルから HTTP ステータスコードを導出している。
google.api.httpアノテーションの変更によって公開 REST マッピングが壊れたにもかかわらず、専用のチェックがないため、buf breakingは問題なしとなっている。- grpc-gateway を本番環境で使用し、デフォルトのエラーハンドラーによって、すべての
detailsペイロードが気づかれないまま破棄されている。 - 公開アクセス可能なサービスでサーバーリフレクションが有効になっている。
- 2 つの RPC が同じリクエストメッセージまたはレスポンスメッセージを共有している。または、リクエスト型に単純なスカラー値を使用している。
- proto ファイル名にバージョン番号を含めている。または、ディレクトリ構成がパッケージと一致していない。
- ブラウザー向けのインターフェースにストリーミングメソッドを追加したにもかかわらず、非ストリーミングの代替手段がなく、トレードオフの判断も記録されていない。
- あらゆる障害を
UNKNOWNまたはINTERNALとして返している。または、DebugInfoが外部の呼び出し元に届いている。 - ストリーミング RPC の前段に L4 ロードバランサーがあり、各クライアントのストリームがすべて 1 つのバックエンドに固定されている。
- クライアントの keepalive 間隔がロードバランサーのアイドルタイムアウトより長いため、keepalive が送信される前にロードバランサーが接続を切断する。
v1パッケージにベータ機能を含めてリリースしている。
測定
上記の根拠としたルールから独自に設定する合格ゲートです(この領域には業界標準のしきい値が存在しないため、各ゲートは二値で監査可能です)。
- 破壊的変更ゲート:新しいメジャーバージョン以外では、破壊的な proto の変更がデフォルトブランチに一切入らないこと。CI での
buf breaking(FILE カテゴリー)が、マージの 100% でグリーンであること。 - アノテーションのカバレッジ:
google.api.httpアノテーションを持つ RPC の 100% が、専用のマッピング差分またはコントラクトテストでカバーされていること。buf はこれをチェックしないためです。 - ストリーミングの規律:単項のみのサーフェスでは
UNARY_RPClint を有効にするか、すべてのストリーミングメソッドについて、受け入れたトレードオフの決定記録と(双方向ストリーミングの場合は)非ストリーミングの代替手段を用意すること。 - 公開範囲:本番環境ではリフレクションを無効にし、ローンチ前に内部 RPC の監査を完了すること。どちらも二値です。
4 つすべてに合格するまで設計を反復してください。その後は、しきい値ではなく傾向として、トランスコードされたエラーに関する誤解が原因のサポートチケットの割合と、リリースごとのバージョン間互換性テストの結果を監視してください。
呼び出し例
- 「当社のプラットフォームチームは、内部 gRPC サービスをパートナーに公開したいと考えています。公開サーフェスを設計し、それがそもそも適切な選択かどうかを教えてください。」
- 「proto のパッケージとバージョニングの規約を設計してください。公開する API 用の proto を用意し、破壊的変更を防ぐ CI ゲートも設定したいです。」
- 「ブラウザクライアント向けに、gRPC バックエンドの前段に Connect-RPC を配置します。エラーモデルとトランスコーディングのアーキテクチャをレビューしてください。」
- 「外部の開発者に公開する前に、この gRPC サービスを監査してください。」
参考資料
- references/proto-governance-reference.md - AIP のファイル/パッケージ規則の詳細、buf lint のルール名、AIP-180 の互換性に関する注意点、チャネルとリリースのバージョニング、非推奨化の仕組み。
- references/grpc-error-http-mapping.md - 17 の標準コード、集約ポイントを含むゲートウェイの完全な HTTP マッピング、エラー詳細のカタログ、ゲートウェイごとのエンベロープ保持の仕組み。
- references/public-grpc-case-studies.md - Google、Square、Dropbox、Cloudflare、Temporal の事例と精度に関する注意点、トランスコーディングのオプションの仕組みをまとめた表、Connect-RPC の採用状況、ロードバランサーのキープアライブ数値。
同じコレクションの関連項目:
samber/developer-platform-skills@api-integration-surface-strategy- 提供するサーフェスを選ぶ包括的な判断を扱います。このスキルは、その gRPC の分岐を再検証して実行します。samber/developer-platform-skills@public-api-design-review- REST/JSON 側のサーフェス全体の一貫性をレビューします。gRPC 固有の設計はこちらに引き継ぎます。samber/developer-platform-skills@public-graphql-api-design- 公開 GraphQL サーフェスにおける構造上の類似項目です。同じくゲートを優先する構成ですが、パラダイムは異なります。samber/developer-platform-skills@api-versioning-policy- 通知期間、サンセットの告知、ガバナンスの共通事項を扱います。このスキルが担当するのは、gRPC 固有のバージョン表記とゲートのみです。samber/developer-platform-skills@api-error-design- エラー分類の考え方、メッセージの作成、再試行可否の通知を扱います。このスキルが担当するのは、google.rpc.Statusの仕組みと HTTP マッピングに関する注意点のみです。samber/developer-platform-skills@api-idempotency-retry- クライアントの再試行の仕組み、期限の伝播、バックオフの共通事項を扱い、ステップ 4 のストリーミングに関する判断に活用します。