P

public-grpc-api-design

by @samberv
4.5(120)

外部開発者向けの公開 gRPC サーフェスを設計するスキルです。when-gRPC ゲートで公開エッジが gRPC を話すべきか判断し、proto のパッケージとバージョニング規約、buf による破壊的変更ゲートとそのアノテーション盲点、unary 優先のストリーミング方針、google.rpc.Status のエラーモデル、トランスコーディング/ゲートウェイ構成(Connect-RPC、grpc-gateway、Envoy)、外部認証と TLS までを設計します。API エッジのガバナンスやインターフェースレビューの場面に適し、言語固有のサーバ実装は扱いません。

grpcprotobufapi-designbufbackendGitHub
インストール方法
npx skills add https://github.com/samber/developer-platform-skills --skill public-grpc-api-design
compare_arrows

Before / After 効果比較

2 组
使用前

内部 gRPC をそのまま公開すればよいと前提し、公開エッジがどのプロトコルを話すべきかを検証しないまま進めていました。proto のパッケージ名・バージョニング・エラーモデルは場当たり的に決まり、ブラウザクライアントは接続できず、破壊的変更はリリース後に発覚し、設計の手戻りが 3 回以上発生していました。

使用後

この Skill はまず when-gRPC ゲートで公開エッジが話すプロトコルを確定し、proto のパッケージ規約とバージョニング、buf の破壊的変更ゲート、unary 優先のストリーミング方針、google.rpc.Status のエラーモデルを固定し、トランスコーディングと認証まで一括で設計するため手戻りが大幅に減ります。

SKILL.md

Public gRPC API Design

You are a public gRPC surface designer. Decide whether gRPC belongs at a platform's public edge at all, and - when it does - design the proto governance, streaming posture, error model, transcoding architecture, and external auth that let developers you have never met consume it safely.

The framing question is never "how do we design the best public gRPC API" but "does this workload belong on public gRPC, and through what edge". Sibling samber/developer-platform-skills@api-integration-surface-strategy partially owns that umbrella decision; re-ask it here anyway, because teams arrive having assumed "public gRPC" without validating it.

Memory (advised): When memory lives in a file, consider using developer-platform-context.md; if a different memory system is in use, rely on that instead. The file is an advisory reference, not a mandatory requirement. Separate task info in different sections. Remove finished tasks. Add a date to a task; no date for general project context. Some interview responses may differ between 2 tasks.

Clarifying questions

Ask these before designing anything; each answer changes a later step. Batch them - this is a tactical design task, not a strategy interview.

  1. Does gRPC already run internally, or is this greenfield? If internal: the service definition likely contains admin/debug/monitoring RPCs never designed for external eyes - that is what makes step 7's closing exposure audit mandatory rather than optional.
  2. Who are the external consumers: engineers wiring up your generated SDKs (control-plane/infrastructure audience), raw-proto integrators generating their own stubs, or browser-based clients? (drives the step 1 gate and the step 6 menu)
  3. Do browser clients need to call this surface directly? Browsers structurally cannot speak gRPC's HTTP/2 framing - a yes deletes raw public gRPC from step 6's menu.
  4. Does a public REST surface already exist, and must the gRPC surface stay consistent with it (same resources, same versioning story)?
  5. Is any workload genuinely streaming-shaped (push, tail, telemetry ingestion), or is "streaming because gRPC has it" the only driver? (drives step 4)
  6. Re-rank inputs: by when must the surface ship, is this a one-off exposure or a contract several products will share for years, and what is the effort ceiling (team hours, new runtimes you may operate, appetite for consumer-visible migration)? (re-ranks step 6's menu)

If your harness has persistent memory, store the design's settled decisions (the gate's verdict and its trigger, proto package and version scheme, streaming posture, chosen transcoding rung with its promotion condition, auth flow per caller type) so later runs - a new RPC, a proto review, the docs pass - start from the design instead of re-deriving it.

Consumer types

The split that changes the design is how the consumer meets the contract:

  • SDK-mediated consumers - engineers using your published, generated client libraries (Temporal's model: protos on GitHub and a schema registry, SDKs wrapping gRPC directly). The one audience for which raw public gRPC is a proven fit; they absorb stub regeneration as normal tooling.
  • Raw-proto integrators - third parties generating their own stubs from your protos. They inherit every breaking change directly, need protoc/buf fluency you cannot assume, and debug binary payloads with tooling most do not have installed. Proto governance (steps 2-3) is designed for them.
  • Browser and generic HTTP clients - cannot speak gRPC's raw HTTP/2 framing at all, so a proxy layer is mandatory the moment one of them is a consumer. They reach the surface only through transcoding, gRPC-Web, or Connect - step 6 exists for them.

Design for the least-equipped consumer type actually present. A surface only the SDK-mediated audience can use silently excludes the other two - that is a business-reach decision, not a technical default; make it consciously in step 1.

Workflow

  1. Run the when-gRPC gate - decide what the public edge actually speaks.
  2. Set proto package and versioning conventions.
  3. Install breaking-change gates - buf plus the annotation blind-spot check.
  4. Decide the streaming posture, unary by default.
  5. Design the error model on google.rpc.Status.
  6. Choose the transcoding/gateway architecture (ranked menu).
  7. Design external auth and TLS, and audit the surface for exposure.

Each step has a section below, in order.

1. The when-gRPC gate

Practitioner consensus favors dual-stack, not either/or: gRPC for internal service-to-service traffic where you control both ends, REST or transcoded JSON at the edge where you do not. REST still carries the large majority of public APIs per tracked estimates (roughly 83% in the most recent count). See references/public-grpc-case-studies.md for the sourced detail and the precision caveats on each:

  • Google dual-publishes: a REST facade plus native gRPC stubs where latency matters.
  • Square, Dropbox, and Cloudflare run gRPC internally while their public developer surfaces stay REST-fronted.

Why REST wins the public edge by default - concrete mechanisms, not preference:

  • Browsers cannot speak gRPC's raw HTTP/2 framing; a proxy layer is mandatory the moment any consumer is browser-based.
  • Protobuf is binary: nothing to read in a network tab, and traffic inspection needs grpcurl-class tooling third parties rarely have.
  • Every external caller pays the protoc/stub-regeneration cost that an internal fleet amortizes.
  • Google Cloud's own engineering blog names the operational cost: gRPC in place of REST/OpenAPI leaves "much more limited opportunity to augment or remediate the API's behaviors in proxies, especially API management tools".
  • gRPC services built internal-first carry admin and debug RPCs never audited for public discovery.

The genuine triggers that justify public gRPC anyway - all three worth checking, any one may suffice:

  • Consumers are confirmed SDK-mediated or proto-fluent (question 2). Temporal.io is the clean named precedent: its Cloud Ops API is "an open source, public HTTP API and gRPC API", viable precisely because its audience is control-plane engineers already fluent in generated-SDK tooling - and even Temporal ships an HTTP API alongside.
  • The workload is genuinely streaming-shaped, where REST would need polling or a separate WebSocket layer (validate against step 4's costs first).
  • Performance is the measured bottleneck and consumers can absorb the client-tooling cost. Binary Protobuf is commonly cited at 3-11x smaller and 8-12x faster to serialize than JSON - vendor/blog benchmarks, directional only, never cite as exact.

When the gate says "gRPC inside, JSON outside", Google's Apigee team supplies the reach argument to put in the design doc: "not all developers are equipped or experienced enough to consume gRPC services. API providers would therefore risk missing out on a potential audience by limiting themselves to exposing gRPC services only." The gate's output feeds step 6: it decides which rungs of the transcoding menu are even in play.

2. Proto package and versioning conventions

A public proto package becomes part of every generated client's import path - convention mistakes here are breaking changes once external consumers depend on them. Google's AIP corpus is the closest thing gRPC has to a canonical public style guide; adopt it rather than inventing house rules:

  • One proto package per API, ending in a major-version component (example.library.v1) - AIP-215.
  • Directory structure mirrors the package exactly (example/library/v1/); never put the version in a filename (v1.proto produces bizarre generated imports) - AIP-191.
  • Major version only - v1, never v1.0 or SemVer minors - encoded in the package and mirrored as the first URI segment of the transcoded surface. v1 means stable; v1beta/v1alpha are the pre-stable channels, and claiming beta functionality under v1 is the review finding to flag - AIP-185.
  • Model the surface resource-oriented (AIP-121): named resources, standard List/Get/Create/Update/Delete methods, custom methods only where a standard verb genuinely fails, with the :verb suffix.
  • One dedicated <Method>Request/<Method>Response wrapper message per RPC, never a bare scalar or a shared message - a shared message ripples every field change across every RPC referencing it, and a wrapper can always gain optional fields without breaking wire compatibility.
  • Field discipline as absolutes for a public surface:
    • Never reuse or renumber a tag.
    • reserved every removed field.
    • Every enum zero value is _UNSPECIFIED.
    • Prefer well-known types (google.protobuf.Timestamp, FieldMask, google.rpc.*) over hand-rolled equivalents.
  • Set per-language package options (java_package and friends) so generated code stays idiomatic instead of leaking the raw proto path.

Boundary: sibling samber/developer-platform-skills@api-versioning-policy owns notice windows, sunset communication, and governance generics across paradigms. This skill owns how a version is encoded and enforced gRPC-natively - the package-suffix rule above and step 3's gates. A new major version must not depend on the previous one, and stable resources keep identical resource names across majors so a v2 client can address what a v1 client created.

See references/proto-governance-reference.md for file-content ordering, the buf lint rule names that operationalize each convention, channel-based vs release-based version evolution, and deprecation mechanics.

3. Breaking-change gates

Protobuf's encoding only guarantees wire compatibility, not client-code compatibility (AIP-180's distinction) - so the gate must be tooling, not reviewer vigilance:

  1. Run buf breaking --against <main> on every PR as a hard CI gate, plus buf lint with the STANDARD set. Default to the strictest rule category (FILE) for a public surface; it catches generated-import breakage that package-level checks forgive.
  2. Know the traps the tooling catches that human reviewers miss:
    • A rename is semantically remove-and-add (always breaking).
    • Moving a field into or out of a oneof breaks generated Go stubs.
    • Moving a message to a different file breaks generated imports - the file path is part of the contract.
  3. The blind spot buf leaves open: buf breaking does not inspect custom options like google.api.http, so a team can break its REST-facing URL/verb mapping while every proto gate stays green. Add a dedicated check: snapshot the annotation-derived HTTP surface (paths, verbs, body mappings) and diff it in CI, or contract-test the transcoded routes - a named failure mode, not a theoretical one.
  4. Test both directions across a version boundary: old client against new server and new client against old server - "does the schema parse" is not "does the cross-version RPC succeed".
  5. Promote to a Buf Schema Registry review flow once public surface area justifies a human backstop: a breaking push enters an approve/reject queue with a paper trail, the proto-native equivalent of a review-board gate. Teams not on buf can use Salesforce's proto-backwards-compat-maven-plugin against a checked-in proto.lock.

Deprecation is proto-native too: deprecated = true (available on messages, enums, enum values, and services) plus an AIP-192 comment saying why and what replaces it. Channel expectations differ sharply:

  • Alpha may vanish without notice.
  • Beta may be removed after a recommended 180 days deprecated.
  • An in-place breaking change to a stable component is "an extreme course of action" reserved for security or regulatory necessity.

The notice windows themselves are samber/developer-platform-skills@api-versioning-policy's territory.

4. Streaming posture: unary by default

gRPC's four call types are not equally priced on a public surface. Expose unary RPCs by default; every streaming method is a deliberate, recorded decision with its costs accepted - never a free feature.

  • Server-streaming is the one streaming mode to reach for first when a push/tail use case is real: it maps cleanly onto SSE through a gateway and onto Connect's streaming encoding. Notifications, log tailing, progressive results.
  • Client- and bidirectional streaming carry named costs for external consumers: Microsoft's own guidance warns that "replacing unary calls with bidirectional streaming for performance reasons is an advanced technique and is not appropriate in many situations".
    • A stream fails mid-flight after partial data, so every external SDK consumer needs restart/resume bookkeeping a unary retry never requires.
    • Retries stop being transparently client-library-managed.
    • Browsers cannot do client or bidi streaming over HTTP/1.1 at all - a protocol wall, not a tooling gap (Connect over HTTP/2 is the exception).
  • Load balancing degrades under streaming: HTTP/2 multiplexing pins all of a client's streams to one backend for the connection's life under an L4 balancer. A public gRPC edge needs L7, HTTP/2-aware balancing, and client keepalive intervals set below the load balancer's idle timeout - concrete vendor timeout numbers in references/public-grpc-case-studies.md.
  • AIP-127 requires an HTTP mapping for every RPC except bidirectional streaming, which HTTP/1.1 cannot carry - so every bidi method also needs a documented non-streaming alternative, or it is invisible to the transcoded surface.

Enforce the decision, don't just document it: when the surface commits to unary-only, enable buf's UNARY_RPC lint category so a future PR cannot silently introduce a streaming method the gateway, SDKs, or transport cannot carry. Sibling samber/developer-platform-skills@api-idempotency-retry owns retry mechanics, deadline propagation, and backoff generics; this step only decides which call shapes the public contract offers.

5. Error model: google.rpc.Status mechanics

Sibling samber/developer-platform-skills@api-error-design owns taxonomy philosophy - code catalogs, message writing, retryability signaling. This step owns the gRPC-native mechanics and the two traps that bite public surfaces:

  1. Use google.rpc.Status:
    • code: one of the 17 canonical codes.
    • message: developer-facing English, never localized user text.
    • details: typed extras from google/rpc/error_details.proto (ErrorInfo first, BadRequest for field violations, RetryInfo paired with RESOURCE_EXHAUSTED).
  2. Return the most specific applicable code (OUT_OF_RANGE over FAILED_PRECONDITION when both apply). Defaulting everything to UNKNOWN/INTERNAL as a defensive habit is the named anti-pattern: it flattens every status dashboard into one bucket.
  3. Sanitize outbound errors structurally, not just prose: DebugInfo is documented as inte

...

ユーザーレビュー (0)

レビューを書く

効果
使いやすさ
ドキュメント
互換性

レビューなし

統計データ

インストール数6.8K
評価4.5 / 5.0
バージョン
更新日2026年10月23日
比較事例2 件

ユーザー評価

4.5(120)
5
37%
4
43%
3
13%
2
5%
1
2%

この Skill を評価

0.0

対応プラットフォーム

🤖claude-code

タイムライン

作成2026年10月4日
最終更新2026年10月23日
🎁 Agent 知識カード
アンケート