Vanilla gRPC standards — .proto services, status.Error with standard codes, domain→code mapping, interceptor chain (auth/log/recovery/validation/metrics), client deadlines, context propagation, reflection off in prod, bufconn testing. Language-agnostic; Go examples. Use when designing or reviewing a gRPC service.
Scanned 9/23/2026
Install to Claude Code
npx -y skills add ralvarezdev/ralvaskills --skill grpc-architect --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Grpc Architect?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ralvarezdev-grpc-architect)More formats (shields.io, HTML) on the badges page.
---
name: grpc-architect
version: 1.0.0
description: Vanilla gRPC standards — .proto services, status.Error with standard codes, domain→code mapping, interceptor chain (auth/log/recovery/validation/metrics), client deadlines, context propagation, reflection off in prod, bufconn testing. Language-agnostic; Go examples. Use when designing or reviewing a gRPC service.
---
# gRPC Architecture
Vanilla gRPC over HTTP/2 — backend-to-backend services. Pair with [protobuf-architect](../../encoding/protobuf-architect/SKILL.md) for schema design. Go-specific implementation skeletons in [RECIPES.md](RECIPES.md); pinned deps in [STACK.md](STACK.md). Other languages follow the same protocol-level conventions with idiomatic substitutions.
## 1. Service definition
One service per file, named after the resource. Methods are verb-noun, request and response are always typed messages — never raw primitives or `google.protobuf.Empty` as input. Example in [RECIPES.md](RECIPES.md).
- **`<Verb><Noun>Request` / `<Verb><Noun>Response`** naming for every method's I/O message. Even when the response is a single resource, prefer `CreateUserResponse { User user = 1; }` over returning `User` directly — leaves room to add fields without bumping the major version.
- **Pagination via cursor**, mirroring [rest-api-architect §5](../../protocols/rest-api-architect/SKILL.md#5-pagination--cursor-not-offset). `ListUsersRequest { string cursor = 1; int32 limit = 2; }` → `ListUsersResponse { repeated User users = 1; string next_cursor = 2; }`.
- **`google.protobuf.Empty`** only as a response type for "fire and forget" actions with no useful return. Never as input.
## 2. Error handling — `status.Error` with codes
Use gRPC standard codes, return errors via `status.Error(code, msg)`. Map domain errors to codes in one central place (skeleton in [RECIPES.md](RECIPES.md)).
### Standard codes — the ones architects actually use
| Code | Use for |
|---|---|
| `OK` | success |
| `INVALID_ARGUMENT` | request fails schema or business validation |
| `FAILED_PRECONDITION` | request valid but system state forbids it (e.g. delete a non-empty resource) |
| `OUT_OF_RANGE` | numeric / range-specific violation distinct from `INVALID_ARGUMENT` |
| `UNAUTHENTICATED` | missing or invalid credentials |
| `PERMISSION_DENIED` | authenticated but not authorized |
| `NOT_FOUND` | resource doesn't exist |
| `ALREADY_EXISTS` | unique-constraint or idempotency-key collision (with a different body) |
| `ABORTED` | concurrency conflict (ETag-equivalent) — client should re-fetch and retry |
| `RESOURCE_EXHAUSTED` | rate limit; per-tenant quota |
| `DEADLINE_EXCEEDED` | the request didn't complete in time — set by the runtime |
| `UNAVAILABLE` | transient — load-balancer drained, restart in progress; client retries |
| `INTERNAL` | unexpected server-side failure — bug or external dependency error |
| `UNIMPLEMENTED` | method exists in proto but server doesn't handle it (use during rollout) |
**Don't reach for `INTERNAL` as a default.** Map every known domain error to a specific code.
- **`status.WithDetails`** attaches structured detail (`google.rpc.ErrorInfo`, `google.rpc.BadRequest`) when clients need machine-readable error context — equivalent to REST's RFC 7807 (see [rest-api-architect §7](../../protocols/rest-api-architect/SKILL.md#7-error-contracts--rfc-7807-problem-details)). Always include a correlation id.
- **Never leak stack traces or DB errors** to clients. Log server-side; return a generic `INTERNAL` with the correlation id.
## 3. Request / response shape
- **Always typed messages.** Don't define a method as `rpc Ping(StringValue) returns (StringValue)` — wrap in `PingRequest` / `PingResponse`.
- **Validation at the boundary** via `protovalidate` (see [protobuf-architect §5](../../encoding/protobuf-architect/SKILL.md#5-validation--protovalidate-cel)). Enforced server-side via an interceptor (§4).
- **No business logic in generated handler files.** Generated handlers are thin shims that call into service-layer code (same discipline as REST routers per [fastapi-architect](../../frameworks/fastapi-architect/SKILL.md) / [gin-architect](../../frameworks/gin-architect/SKILL.md)).
## 4. Interceptors — mandatory chain
Interceptors are gRPC's middleware. **Order matters** — outermost first: recovery → request-id → log → auth → validation → metrics. Full chain in [RECIPES.md](RECIPES.md).
- **Recovery first** — catches panics anywhere downstream and converts to `INTERNAL` with correlation id (never a stack trace).
- **Auth before validation** — no point validating an unauthenticated request's body. Per-method authorization (scopes/roles) happens inside the handler or via a small `WithAuthFunc` interceptor.
- **Validation is centralized via `protovalidate`** — don't hand-write validation in every handler. The interceptor calls `validator.Validate(req)` and returns `INVALID_ARGUMENT` with `google.rpc.BadRequest` details on failure.
- **Same interceptor chain for streaming RPCs** via `ChainStreamInterceptor`. Streaming validation requires handling per-message in client/bidi streams.
## 5. Streaming patterns
gRPC supports four call types. Pick the simplest one that meets the requirement.
| Pattern | Use for | Pitfalls |
|---|---|---|
| **Unary** | Default — request/response | None — start here |
| **Server-stream** | Server emits N responses to one request (event feeds, paginated downloads that don't fit one response, log tail) | Connection state outlives the request; resume tokens needed for restarts |
| **Client-stream** | Client uploads N messages, server returns one summary (large uploads, batch ingest) | Backpressure from server requires careful flow control |
| **Bidi-stream** | Genuinely interactive (chat, collaborative editing, control protocols) | Connection lifecycle complexity; reconnect / resume logic; deadlines |
- **Don't reach for streaming "to save round-trips"** — unary with proper pagination is usually fine and orders of magnitude simpler.
- **Server-streams need resume tokens.** Pass `start_after_id` or a cursor in the request so a disconnected client can resume from a known point.
- **Bidi-streams need a clear protocol** — define exactly which side sends what and when. Sketch the message flow in the `.proto` comments; future-you will thank you.
- **Set generous deadlines** on streams — but **always** set them. An unbounded stream is a leak.
## 6. Deadlines & context propagation
Every gRPC call has a deadline. Clients set; servers respect; downstream calls inherit the remaining time. Client skeleton in [RECIPES.md](RECIPES.md).
- **Server respects** — check `ctx.Err()` periodically in long-running handlers; abort work the moment the deadline fires.
- **Propagate context to all downstream calls** — DB queries, HTTP calls, other gRPC calls. Deadlines and cancellation flow automatically.
- **Default deadlines per call type:** unary 5–30s; server-stream often much longer (minutes / hours) but always bounded.
- **Server-side deadline guard:** wrap the entire handler in `context.WithTimeout` slightly less than the client deadline to leave headroom for response serialization.
## 7. Metadata vs message fields
| Use metadata for | Use message fields for |
|---|---|
| Auth tokens (`authorization: Bearer ...`) | Business data |
| Request IDs / correlation IDs (read by middleware) | Anything the handler reads as part of business logic |
| Tracing context (`traceparent`) | — |
| Rate-limit hints (`x-tenant-id` for routing) | — |
- **Metadata is HTTP/2 headers under the hood.** Don't send large payloads here.
- **Keys are case-insensitive ASCII;** values are strings (binary metadata uses the `-bin` suffix).
- **Standardize one correlation-id header** (e.g. `x-request-id`) — interceptor reads it on entry, injects into context, logs against it.
## 8. Reflection
Reflection enables `grpcurl` and IDE plugins to introspect the service without the `.proto` file — **on in dev, off in production**. It leaks the entire service surface. Skeleton in [RECIPES.md](RECIPES.md). The health service (`grpc.health.v1`) is always on — load balancers and orchestrators need it.
## 9. Testing — `bufconn` for in-process
The `google.golang.org/grpc/test/bufconn` package gives an in-memory listener — full server + client without a real socket. Faster than `net.Pipe`, simpler than spinning up a test server on a port. Skeleton in [RECIPES.md](RECIPES.md).
- **`bufconn` for unit + integration tests**; spin a real server on a random port only when you specifically need the full network path (TLS, HTTP/2 frame behavior, etc.).
- **Table-driven tests** per [go-architect §9](../../languages/go-architect/SKILL.md#9-testing) — one row per (input, expected code, expected error type).
- **`grpcurl`** is the manual-testing tool. Pin it in your task runner / mise config.
## 10. When to pick gRPC over REST
gRPC's wins are real but specific:
- **Backend-to-backend** — gRPC's binary framing and HTTP/2 multiplexing beat JSON-over-HTTP/1.1 in throughput and tail latency at scale.
- **Strong contracts** — `.proto` is the canonical schema; clients in any language are generated. No OpenAPI drift.
- **Streaming** — first-class server-stream / client-stream / bidi.
- **Compact wire format** — binary; smaller than JSON for the same payload.
REST is the better default when:
- **Browser callers** — vanilla gRPC isn't browser-callable without a gateway. Connect-RPC fixes this; consider it (or a separate REST facade) if browser is in scope.
- **Public APIs** — external consumers expect REST; `curl` works without tooling; OpenAPI is the universal documentation format.
- **Cache-friendly reads** — HTTP caching (ETag, Cache-Control) is built-in; gRPC has no equivalent.
- **Small scope** — for one CRUD service, a Gin/FastAPI REST API is shorter to build.
The two coexist: gRPC for east-west backend traffic, REST for north-south client-facing endpoints. **Connect-RPC** lets one set of `.proto` files serve gRPC, gRPC-Web, and Connect (browser-friendly) — worth considering as the upgrade path. `grpc-gateway` is an alternative for REST/JSON facades but adds spec complexity and error-translation discipline.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!