Code Generation
What we’re building
Section titled “What we’re building”The gen/ tree — real, buildable Go code produced by running buf generate (the toolchain Protobuf Tooling → configured) against the two .proto files from The Contracts →. Nothing in gen/ is hand-written; every file is a deterministic function of the .proto source plus buf.gen.yaml’s four plugins.
Four remote plugins were wired up in buf.gen.yaml back in Module 1, each producing a different downstream need from the identical .proto input: the Go message types your code will actually import, the gRPC client and server stubs a service implements and calls, the REST-to-gRPC reverse proxy the API Gateway runs, and an OpenAPI document for anyone who wants to browse the REST surface without reading a .proto file at all. Running one command against the two contracts from the previous lesson produces all of it, in one deterministic pass, with nothing hand-maintained to fall out of sync.
Pros & cons
Section titled “Pros & cons”Whether gen/ itself gets committed to Git is a real, recurring decision every buf-based project has to make — this course commits to one side of it, but both are legitimate.
Committing generated code
- Pros: a fresh clone builds immediately with
go build ./...— no buf toolchain, no network access to the BSR needed just to compile; editor tooling (go-to-definition, autocomplete) works the instant the repo is cloned;git diffon a.protochange shows reviewers exactly what generated code it produced, which can catch an accidental breaking change during review. - Cons: generated files are large and numerous — a single field rename can touch hundreds of lines spread across
.pb.go,_grpc.pb.go,.pb.gw.go, and.swagger.json; two unrelated.protochanges that happen to touch overlapping generated line ranges produce meaningless, noisy merge conflicts; nothing stops someone from editing a.protoand forgetting to regenerate, silently leaving committedgen/stale unless CI has an explicit check for it.
Generating in CI (not committing gen/)
- Pros:
gen/is always in sync withproto/by construction — there’s no “forgot to regenerate” state to drift into; pull requests only ever show.protodiffs, never generated-code noise. - Cons: every fresh clone and every CI job needs the buf toolchain plus either network access to the BSR or a warmed local plugin cache before it can build at all — a slower first build, and a hard failure if the BSR is ever unreachable.
This course takes the second path: Repo Layout → already .gitignored /gen/ specifically so it never gets committed — buf generate is treated as a required, one-command build step every contributor and every CI job runs before go build ./..., exactly like go mod download.
Set it up
Section titled “Set it up”1. Run buf generate
Section titled “1. Run buf generate”buf generateThis reads buf.gen.yaml’s inputs: - directory: proto, resolves the four remote plugins (cached locally after the first run), and — because clean: true is set — wipes gen/ before writing anything, so deleted or renamed messages never leave stale files behind.
2. The resulting gen/ tree
Section titled “2. The resulting gen/ tree”gen/└── shopmicro/ ├── catalog/ │ └── v1/ │ ├── catalog.pb.go │ ├── catalog_grpc.pb.go │ ├── catalog.pb.gw.go │ └── catalog.swagger.json └── order/ └── v1/ ├── order.pb.go ├── order_grpc.pb.go ├── order.pb.gw.go └── order.swagger.jsonFour files per .proto, one per plugin:
catalog.pb.go(protocolbuffers/go) — the Go structs for everymessage:Product,ListProductsRequest,CreateProductRequest, and so on, plusMarshal/Unmarshal/Reset/Stringand field getters. This is what every service imports to work with request/response data at all.catalog_grpc.pb.go(grpc/go) — theCatalogServiceClientandCatalogServiceServerinterfaces,NewCatalogServiceClient,RegisterCatalogServiceServer, andUnimplementedCatalogServiceServer.catalog.pb.gw.go(grpc-ecosystem/gateway) — the reverse-proxy registration functions the API Gateway calls, driven entirely by thegoogle.api.httpannotations from the previous lesson.catalog.swagger.json(grpc-ecosystem/openapiv2) — an OpenAPI v2 document describing the same REST surface, generated with zero hand-written API docs.
3. The generated server interface — what a service implements
Section titled “3. The generated server interface — what a service implements”type CatalogServiceServer interface { ListProducts(context.Context, *ListProductsRequest) (*ListProductsResponse, error) GetProduct(context.Context, *GetProductRequest) (*Product, error) CreateProduct(context.Context, *CreateProductRequest) (*Product, error) mustEmbedUnimplementedCatalogServiceServer()}The Catalog service (Module 3) implements this interface on a plain struct and embeds catalogv1.UnimplementedCatalogServiceServer by value, not by pointer — the generated doc comment on that type is explicit about this, since embedding by pointer risks a nil-pointer dereference when a not-yet-implemented method is called. Embedding it also means adding a new RPC to the .proto later doesn’t break every existing implementation’s compilation: unimplemented methods fall through to UnimplementedCatalogServiceServer’s version, which returns a codes.Unimplemented gRPC error at runtime instead of a compile error. The service then registers itself with:
catalogv1.RegisterCatalogServiceServer(grpcServer, catalogImpl)4. The generated client — who calls it
Section titled “4. The generated client — who calls it”type CatalogServiceClient interface { ListProducts(ctx context.Context, in *ListProductsRequest, opts ...grpc.CallOption) (*ListProductsResponse, error) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) CreateProduct(ctx context.Context, in *CreateProductRequest, opts ...grpc.CallOption) (*Product, error)}
func NewCatalogServiceClient(cc grpc.ClientConnInterface) CatalogServiceClientTwo consumers use this: the grpc-gateway reverse proxy in catalog.pb.gw.go (via RegisterCatalogServiceHandlerFromEndpoint, which dials the Catalog service and wraps it in a CatalogServiceClient internally), and any other service that needs to call Catalog directly over gRPC — the Order service (Module 4) looking up a product’s current price is exactly that case. That caller dials a connection and constructs the client itself:
conn, err := grpc.NewClient("catalog:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))client := catalogv1.NewCatalogServiceClient(conn)5. Importing generated packages from service code
Section titled “5. Importing generated packages from service code”import ( catalogv1 "github.com/avetavos/shopmicro/gen/shopmicro/catalog/v1")The generated file’s actual package catalogv1 declaration already matches this alias — The Contracts → set go_package’s ;catalogv1 suffix precisely so the package wouldn’t default to v1 (the import path’s last segment) and collide with order/v1’s generated package. Writing the alias explicitly in the import line is still good practice: the path segment v1 doesn’t visually match the identifier catalogv1 a reader will see used throughout the file, so spelling it out avoids anyone having to check the generated source to find out what a bare v1.Product would even mean.
Verify
Section titled “Verify”Confirm every expected file exists:
find gen -type f | sortYou should see the eight files listed above — four under gen/shopmicro/catalog/v1/, four under gen/shopmicro/order/v1/. Spot-check the server interface directly:
grep -n "CatalogServiceServer interface" gen/shopmicro/catalog/v1/catalog_grpc.pb.goThen confirm the whole module still compiles — generated code is real Go source, so it’s covered by the same go build ./... every other lesson in this course verifies with:
go build ./...No output means success.
buf generate turned the two .proto contracts into eight files under gen/: .pb.go (Go message types), _grpc.pb.go (CatalogServiceServer/CatalogServiceClient — the interface a service implements and the interface its callers use), .pb.gw.go (the REST-to-gRPC reverse proxy the API Gateway will run), and .swagger.json (auto-generated OpenAPI docs) — one set per service. This course keeps gen/ out of Git and treats buf generate as a required build step, trading a slower first build for a tree that’s always in sync with proto/ by construction; committing gen/ instead is a legitimate alternative when a fast, toolchain-free clone-and-build matters more. Next, Catalog Service → is where CatalogServiceServer gets its first real implementation.