Lesson 3 · Foundations

Services & codegen

From service { rpc … } to a Go interface you implement.

Your win: declare a service, and explain exactly what make gen-proto-go produces — and the surprising truth that this repo generates with protoc, not buf.

In plain English Plain English: you write the service contract once in .proto, then code generation gives both sides the same typed API to implement and call.

A service is a set of RPC methods

Add a service block to your .proto; each rpc line is one method with a request and a response message.1

proto/spike/v1/email.proto
service EmailModifierService {
    rpc SendEmail(SendEmailRequest) returns (SendEmailResponse);
    rpc BulkSendEmails(BulkSendEmailsRequest) returns (BulkSendEmailsResponse);
}

What codegen produces

A code generator turns that into Go. For each service you get three things:

XServiceServer

An interface with one Go method per rpc. You implement this — it's your handler type.

RegisterXServiceServer

A function to attach your implementation to a running grpc.Server.

XServiceClient

The stub — callers use it to invoke the methods remotely (Lesson 1's f.client).

⚠️ In this repo: protoc, not buf generate make gen-proto-go runs protoc (via proto/gen_go.sh in Docker) with the protoc-gen-go (messages), protoc-gen-go-grpc (service + client), and gateway plugins, writing to pkg/manabuf/. buf is used only for linting & breaking-change checks (make lint-proto). The repo rule is explicit: don't run local buf generate.

Registration wires your implementation in (`SetupGRPC` calls the generated registrar):

cmd/server/spike/init_grpc.go:10
spb.RegisterEmailModifierServiceServer(server, emailModifierSvc)  // your impl
// callers elsewhere: spb.NewEmailModifierServiceClient(conn)
Anchor — the Unimplemented twist Our codegen passes require_unimplemented_servers=false (proto/gen_go.sh), so your server type does not need to embed UnimplementedXServer — though some transport structs still do it for forward-compatibility (recall Go course, Lesson 8 on embedding). Without that flag, forgetting the embed is a compile error.
Do this next

gRPC — Go Quick start (protoc plugins) + Buf docs

The quickstart shows installing protoc-gen-go/-go-grpc and generating stubs — the same plugins our gen_go.sh runs. Buf docs cover the lint/breaking rules we do use.

grpc.io/docs/languages/go/quickstart
buf.build/docs (lint & breaking)

Common mistake Common mistake: assuming buf generates the code here because buf is in the repo. In this codebase buf is for linting and breaking checks, not codegen.

Check yourself (from memory)

Q1. In this repo, Go stubs are generated by…

protoc through proto/gen_go.sh. A common wrong assumption is that buf generates — here it only lints.

Q2. In this repo, buf is used for…

make lint-proto — style + breaking-change detection (guarding the field-number rule from Lesson 2). Not codegen.

Q3. Codegen produces, for each service, a…

An XServiceServer interface (you implement), a Register… func, and an XServiceClient stub.
What does make gen-proto-go produce from a service, and what do you write?
recall, then click to reveal
It runs protoc (not buf) via proto/gen_go.sh to generate, per service: the message structs, an XServiceServer interface, a RegisterXServiceServer function, and an XServiceClient stub — under pkg/manabuf. You write the Go type that implements the server interface (the handler methods). buf only lints / breaking-checks the proto.
Want to see the exact protoc flags in gen_go.sh, or how the gateway plugin fits in? Ask me.

1. gRPC — service definition; Go quickstart.