Go gRPC & Protobuf
Proto source and generated code
Section titled “Proto source and generated code”- Proto files (
.proto) live in their own package (e.g.proto/). - Generated files (
*.pb.go,*_grpc.pb.go) are committed to the repository so thatgo buildworks withoutprotocinstalled. - Place a
generate.goin the proto package with a//go:generatedirective for regeneration:
package proto
//go:generate protoc --go_out=.. --go_opt=module=<module> --go-grpc_out=.. --go-grpc_opt=module=<module> <file>.protogo generateis a dev-time tool, not a CI step. CI builds against the committed.pb.gofiles.- Optional CI check: regenerate +
git diff --exit-codeto verify.pb.gofiles are up to date.
Never edit generated code
Section titled “Never edit generated code”- Do not modify
*.pb.goor*_grpc.pb.goby hand. Change the.protosource and regenerate. - Do not add custom methods to generated types. Wrap them in domain types instead.
Hexagonal boundaries
Section titled “Hexagonal boundaries”- Proto-generated code is infrastructure — it belongs outside
internal/core/. - Core domain types must not depend on protobuf types. Map between proto messages and domain types at the adapter boundary.
- The gRPC server implementation lives in
internal/infra/grpc/(or similar infra package). - Core handlers receive plain domain structs, never
pb.*messages.
Proto design
Section titled “Proto design”- Keep messages payload-agnostic where possible: use
stringorbytesfor opaque payloads that evolve independently of the proto contract. - Use
oneoffor polymorphic updates (e.g. event vs result in a stream). - Avoid importing
google/protobuf/empty.protofor trivial ack responses — define a localmessage Ack {}to keep the proto self-contained.