Chapter 03

服务定义与代码生成

掌握 service/rpc 四种通信模式的声明,用 protoc 和 buf 工具生成多语言服务端/客户端代码

service 与 rpc 定义

在 .proto 文件中,service 块定义服务名称,其中每个 rpc 语句定义一个具体的方法。

service UserService {
  // 1. Unary RPC — 最普通的请求/响应模式
  rpc GetUser(GetUserRequest) returns (UserResponse);

  // 2. 服务端流式 — 服务端持续推送多条消息
  rpc ListUsers(ListUsersRequest) returns (stream UserResponse);

  // 3. 客户端流式 — 客户端发送多条后等待一次响应
  rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchResult);

  // 4. 双向流式 — 全双工,双方都可随时发消息
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

项目目录结构

myapp/
├── proto/                  # .proto 源文件
│   └── user/v1/
│       └── user.proto
├── gen/                    # 生成的代码(不要手动修改)
│   └── user/v1/
│       ├── user.pb.go      # 消息结构体
│       └── user_grpc.pb.go # gRPC 服务接口
├── server/                 # 服务端实现
│   └── main.go
├── client/                 # 客户端代码
│   └── main.go
├── buf.yaml                # buf 配置
├── buf.gen.yaml            # buf 代码生成配置
└── go.mod

protoc 命令生成代码

Go 代码生成

# 生成 Go 消息结构体 + gRPC 服务代码
protoc \
  --proto_path=proto \
  --go_out=gen \
  --go_opt=paths=source_relative \
  --go-grpc_out=gen \
  --go-grpc_opt=paths=source_relative \
  user/v1/user.proto

# 参数说明:
# --proto_path=proto    指定 .proto 文件搜索路径
# --go_out=gen          生成 .pb.go 文件到 gen 目录
# paths=source_relative 保持 proto 文件的相对路径结构
# --go-grpc_out=gen     生成 _grpc.pb.go 文件

Python 代码生成

# 生成 Python 消息类 + gRPC 存根
python3 -m grpc_tools.protoc \
  -I proto \
  --python_out=gen_py \
  --grpc_python_out=gen_py \
  user/v1/user.proto

# 生成文件:
# gen_py/user/v1/user_pb2.py       — 消息类
# gen_py/user/v1/user_pb2_grpc.py  — 客户端/服务端存根

生成的 Go 文件解读

// user.pb.go — 由 protoc-gen-go 生成
package userv1

// proto message → Go struct
type User struct {
  Id    int64  `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
  Name  string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
  Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
  // 不可见的内部字段(不要直接访问)
  state         protoimpl.MessageState
  sizeCache     protoimpl.SizeCache
  unknownFields protoimpl.UnknownFields
}

// user_grpc.pb.go — 由 protoc-gen-go-grpc 生成

// 客户端接口(调用方使用)
type UserServiceClient interface {
  GetUser(ctx context.Context, in *GetUserRequest,
          opts ...grpc.CallOption) (*UserResponse, error)
  ListUsers(ctx context.Context, in *ListUsersRequest,
             opts ...grpc.CallOption) (UserService_ListUsersClient, error)
}

// 服务端接口(实现方必须实现)
type UserServiceServer interface {
  GetUser(context.Context, *GetUserRequest) (*UserResponse, error)
  ListUsers(*ListUsersRequest, UserService_ListUsersServer) error
  mustEmbedUnimplementedUserServiceServer()
}

// 嵌入此结构以获得未实现方法的默认错误处理
type UnimplementedUserServiceServer struct{}

UnimplementedUserServiceServer:在实现服务端时,务必嵌入 UnimplementedUserServiceServer。这是为了向前兼容——当 .proto 文件新增 RPC 方法时,未实现的方法会返回 UNIMPLEMENTED 错误而不是编译失败,给你时间逐步迁移。

buf 工具:现代 protoc 替代品

buf 解决了 protoc 的诸多痛点:依赖管理混乱、lint 规则分散、breaking change 无法检测。

buf.yaml — 项目配置

# buf.yaml
version: v2
modules:
  - path: proto
lint:
  use:
    - DEFAULT   # 启用默认 lint 规则集
breaking:
  use:
    - FILE      # 检测文件级别的 breaking change

buf.gen.yaml — 代码生成配置

# buf.gen.yaml
version: v2
plugins:
  - remote: buf.build/protocolbuffers/go
    out: gen
    opt:
      - paths=source_relative
  - remote: buf.build/grpc/go
    out: gen
    opt:
      - paths=source_relative

常用 buf 命令

# 生成代码(替代 protoc)
buf generate

# lint 检查(发现不规范的 proto 写法)
buf lint

# 检测 breaking change(与 git 历史对比)
buf breaking --against '.git#branch=main'

# 格式化 .proto 文件
buf format -w

# 从 Buf Schema Registry 拉取依赖
buf dep update

实战:定义完整的用户服务

// proto/user/v1/user.proto
syntax = "proto3";
package user.v1;
option go_package = "github.com/myapp/gen/user/v1;userv1";

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

enum UserRole {
  USER_ROLE_UNSPECIFIED = 0;
  USER_ROLE_GUEST       = 1;
  USER_ROLE_MEMBER      = 2;
  USER_ROLE_ADMIN       = 3;
}

message User {
  string                    id         = 1;
  string                    name       = 2;
  string                    email      = 3;
  UserRole                  role       = 4;
  google.protobuf.Timestamp created_at = 5;
}

message GetUserRequest  { string id = 1; }
message UserResponse    { User   user = 1; }

message ListUsersRequest {
  int32  page_size  = 1;
  string page_token = 2;
}

message CreateUserRequest {
  string   name     = 1;
  string   email    = 2;
  UserRole role     = 3;
}

message DeleteUserRequest { string id = 1; }

service UserService {
  rpc GetUser    (GetUserRequest)    returns (UserResponse);
  rpc ListUsers  (ListUsersRequest)  returns (stream UserResponse);
  rpc CreateUser (CreateUserRequest) returns (UserResponse);
  rpc DeleteUser (DeleteUserRequest) returns (google.protobuf.Empty);
}

本章小结:service/rpc 定义是 gRPC 的服务契约,protoc 或 buf 将其转化为各语言的类型安全代码。掌握 buf 工具链可以大幅提升团队协作效率,尤其是 lint 和 breaking change 检测,是专业团队的必备工具。