All Go templates

Go gRPC Services

gRPC + protobuf: service definitions, interceptors, streaming, errors.

DevZone Tools980 copiesUpdated Mar 26, 2026Go
# CLAUDE.md — Go gRPC Services

## Service definition

- One `.proto` per service. Live in `proto/` at the repo root or in a separate buf module.
- Use **buf** for codegen, lint, and breaking-change detection. Don't shell out to `protoc` directly.
- Versioned packages: `package myapp.users.v1`. New incompatible API → new version, not a rewrite.

## Code generation

- `buf generate` writes Go stubs into `gen/proto/...`. Commit the generated code so consumers don't need buf.
- Lock plugin versions in `buf.gen.yaml`. Stub drift is a tax on every PR.

## Server skeleton

```go
srv := grpc.NewServer(
    grpc.ChainUnaryInterceptor(loggingInterceptor, recoveryInterceptor, authInterceptor),
    grpc.MaxRecvMsgSize(4 * 1024 * 1024),
)
v1.RegisterUsersServiceServer(srv, &usersServer{...})
reflection.Register(srv)
```

- Always register reflection in non-prod (debugging tools need it). Gate behind a flag in prod.
- Set message size limits — the default 4 MB recv is sometimes too small, often too generous.

## Errors

- Return `status.Error(codes.NotFound, "user %s", id)` — gRPC status codes map to HTTP and other transports.
- Define a single helper that maps domain errors to gRPC codes. Don't sprinkle `status.Errorf` everywhere.
- Detail proto messages for structured errors: use `errdetails.BadRequest`, `errdetails.PreconditionFailure`.

## Streaming

- Server-stream for "long list" responses where pagination would round-trip too much.
- Client-stream for batch ingestion (logs, metrics).
- Bidi for chat-like or tightly coordinated flows. Don't reach for it without a reason.
- Always honor `ctx.Done()` in stream loops — clients hang up.

## Interceptors

- One unary chain, one stream chain. Order: recovery → logging → tracing → auth → app middleware.
- Don't write business logic in interceptors. They're for cross-cutting concerns.

## Metadata & auth

- Auth tokens go in metadata: `authorization: Bearer ...`. Read with `metadata.FromIncomingContext`.
- Don't use the `Context` to carry app values across services. Carry them in the proto message.

## Performance

- Prefer many small RPCs over one huge one. Latency is mostly serialization, not the network.
- Use connection pooling (`grpc.NewClient` returns a multiplexed conn — share it).
- Compression (`gzip`) only when payloads are large and the network is slow. Otherwise it's CPU for nothing.

## Testing

- `bufconn` for in-memory gRPC tests:
  ```go
  lis := bufconn.Listen(1024 * 1024)
  go srv.Serve(lis)
  conn, _ := grpc.Dial("bufnet", grpc.WithContextDialer(...), grpc.WithTransportCredentials(insecure.NewCredentials()))
  ```
- Don't spin up a real listener for unit tests.

## Don't

- Don't expose internal IDs as `string` when you have a typed enum or wrapper. Make protos express intent.
- Don't break wire compatibility. Field tags are forever.
- Don't mix gRPC concerns with domain types. Translate at the boundary.
- Don't return raw `error` from server methods. Always a `status.Error`.

Other Go templates