Go gRPC services and clients
Preserve the project's protobuf layout, compatibility policy, generation toolchain, and transport conventions. Use its existing generator versions and limit regeneration to affected APIs.
Inspect before changing
Locate:
go.mod,.protosources, Buf/protoc configuration, and generated-file policy;- server and client construction, credentials, interceptors, limits, health checks, and reflection policy;
- status-code mapping, metadata propagation, streaming ownership, and shutdown behavior;
- compatibility checks and transport-level tests.
Use API details supported by the selected module versions. Prefer the repository's existing buf or protoc command rather than inventing another generation path.
For concrete server/client APIs, status mapping, metadata, streaming, and graceful shutdown, read runtime recipes. For an in-memory transport test that exercises protobuf encoding and interceptors, read bufconn testing.
Protobuf evolution
- Treat field numbers and wire types as compatibility-sensitive. Do not reuse removed field numbers or names; reserve them where the project's policy requires it.
- Keep package names and
go_packagestable unless a migration is intentional. - Use request and response messages so an RPC can evolve without changing its method shape. Reuse
google.protobuf.Emptyonly when the project's compatibility policy accepts that constraint. - Preserve unknown-field and enum behavior expected by mixed-version clients and servers.
- Run the repository's lint or breaking-change check when available; compilation alone does not prove wire compatibility.
Never hand-edit generated protobuf or gRPC files.
Transport boundaries
Map expected domain failures to intentional gRPC status codes and keep unexpected internal details out of client-visible messages. A raw Go error generally becomes codes.Unknown; do not leak it merely to avoid writing a mapping. Preserve error details only when their protobuf contract is safe and stable for clients.
Use status.Code or status.FromError on the client instead of parsing messages. Distinguish Unauthenticated from PermissionDenied, and use Canceled or DeadlineExceeded consistently with the context result. Attach typed details with WithDetails only when clients can depend on that protobuf contract.
Propagate caller contexts. Give bounded unary calls and finite operations an appropriate deadline at the owning boundary; do not impose a short generic timeout on intentionally long-lived streams. Retry only operations whose status, idempotency, backoff, and retry budget make replay safe.
For streaming RPCs, define which side owns sending, receiving, and closure. Ensure cancellation unblocks goroutines, handle io.EOF distinctly from failures, and avoid concurrent sends on a stream unless the selected API explicitly permits them.
Security and resource controls
Authenticate and protect transport across the actual trust boundary. TLS or mTLS is common across networks; plaintext credentials can be acceptable only when another verified layer terminates transport security or the channel is explicitly local and trusted. Do not assume service-mesh presence makes application authorization unnecessary.
Enforce authorization against the full RPC method and relevant resource, not merely a valid token. Bound inbound and outbound message sizes, concurrent streams, metadata, connection age, and handler work where untrusted peers can consume resources. Configure keepalive defensively and compatibly on both ends.
Reflection is an operational policy, not a security boundary. Enable or restrict it according to the deployment's debugging and exposure needs; authorization and resource controls must remain correct either way.
Lifecycle
Register health state consistently with readiness and shutdown. During shutdown, stop accepting new work, mark the service unavailable when appropriate, allow in-flight RPCs a bounded drain period, and use the project's forced-stop fallback if graceful shutdown cannot complete.
Verification
Regenerate only affected outputs, format, compile, and run relevant tests. Use direct handler tests for domain mapping and bufconn or loopback transport when serialization, metadata, interceptors, streaming, or status codes matter. Verify cancellation, deadline, malformed input, authorization, message-limit, compatibility, and shutdown paths introduced by the change.