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