Chapter 8: Code Generation — From Proto to Code
Code generation is what makes gRPC practical. You write a .proto file once, and protoc generates type-safe client stubs and server skeletons in your target language. No hand-writing HTTP handlers, no manual serialization.
The protoc Pipeline
┌──────────────┐
│ .proto file │
└──────┬───────┘
│
▼
┌──────────────┐
│ protoc │ (Protocol Buffer Compiler)
└──────┬───────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌──────────┐ ┌──────────┐
│ --cpp_out │ │--go_out │ │--py_out │ ← message code
└────────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌──────────┐ ┌──────────┐
│--grpc_out │ │grpc plugin│ │grpc plugin│ ← service/stub code
└────────────┘ └──────────┘ └──────────┘
protoc itself only generates message serialization code. For gRPC service stubs, you need a language-specific plugin:
# C++: generates .pb.h/.pb.cc (messages) + .grpc.pb.h/.grpc.pb.cc (service)
$ protoc --cpp_out=./gen --grpc_out=./gen \
--plugin=protoc-gen-grpc=$(which grpc_cpp_plugin) \
user.proto
# Go: generates .pb.go (messages) + _grpc.pb.go (service)
$ protoc --go_out=./gen --go-grpc_out=./gen \
user.proto
# Python: generates _pb2.py (messages) + _pb2_grpc.py (service)
$ python -m grpc_tools.protoc --python_out=./gen --grpc_python_out=./gen \
-I. user.proto
What Gets Generated (C++ Example)
Given this proto:
syntax = "proto3";
package example;
message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }
service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply);
}
You get these files:
hello.pb.h — Message classes
namespace example {
class HelloRequest : public google::protobuf::Message {
public:
// Field accessors
const std::string& name() const;
void set_name(const std::string& value);
std::string* mutable_name();
// Serialization
bool SerializeToString(std::string* output) const;
bool ParseFromString(const std::string& data);
size_t ByteSizeLong() const;
};
} // namespace example
hello.grpc.pb.h — Service stubs
namespace example {
class Greeter final {
public:
// --- Client stub (what you call) ---
class Stub {
public:
Status SayHello(ClientContext* ctx,
const HelloRequest& request,
HelloReply* response);
};
// Factory to create a stub
static std::unique_ptr<Stub> NewStub(std::shared_ptr<Channel> channel);
// --- Server interface (what you implement) ---
class Service : public grpc::Service {
public:
virtual Status SayHello(ServerContext* ctx,
const HelloRequest* request,
HelloReply* response);
};
};
} // namespace example
Using Generated Code
Server Implementation
class GreeterServiceImpl : public example::Greeter::Service {
Status SayHello(ServerContext* ctx,
const HelloRequest* req,
HelloReply* reply) override {
reply->set_message("Hello, " + req->name());
return Status::OK;
}
};
// Start server
ServerBuilder builder;
builder.AddListeningPort("0.0.0.0:50051", grpc::InsecureServerCredentials());
builder.RegisterService(&service);
auto server = builder.BuildAndStart();
Client Usage
// Create channel and stub
auto channel = grpc::CreateChannel("localhost:50051",
grpc::InsecureChannelCredentials());
auto stub = example::Greeter::NewStub(channel);
// Make the call
HelloRequest request;
request.set_name("World");
HelloReply reply;
ClientContext context;
Status status = stub->SayHello(&context, request, &reply);
if (status.ok()) {
std::cout << reply.message() << std::endl; // "Hello, World"
}
Python Generated Code (for comparison)
# Server
class GreeterServicer(example_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return example_pb2.HelloReply(message=f"Hello, {request.name}")
# Client
channel = grpc.insecure_channel('localhost:50051')
stub = example_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(example_pb2.HelloRequest(name="World"))
print(response.message)
💡 The Power of Code Gen
Notice: the C++ server and Python client can talk to each other with zero extra work. The .proto file guarantees they agree on the wire format. This is polyglot microservices made easy.
Build System Integration
CMake (C++)
find_package(Protobuf REQUIRED)
find_package(gRPC REQUIRED)
# Generate from .proto
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS user.proto)
grpc_generate_cpp(GRPC_SRCS GRPC_HDRS user.proto)
add_executable(server server.cc ${PROTO_SRCS} ${GRPC_SRCS})
target_link_libraries(server gRPC::grpc++ protobuf::libprotobuf)
Buf (Modern Alternative to raw protoc)
# buf.gen.yaml — declarative code generation config
version: v1
plugins:
- plugin: cpp
out: gen/cpp
- plugin: grpc-cpp
out: gen/cpp
- plugin: go
out: gen/go
opt: paths=source_relative
- plugin: go-grpc
out: gen/go
opt: paths=source_relative
# Run: buf generate
Key Takeaways
- protoc + language plugin generates both message classes and service stubs
- Server: inherit from generated Service class, override RPC methods
- Client: create a Stub from a Channel, call methods like local functions
- Same .proto → interoperable code in any supported language
- Use Buf for modern projects (replaces raw protoc invocations)