4. gRPC 服务定义:Service定义、RPC方法类型与方法签名
在 gRPC 契约体系中,Service 定义是核心入口。它通过 Protocol Buffers(Protobuf)的 IDL 语言,将远程方法的名称、参数类型、返回类型以及通信模式一次性声明清楚。本章将深入解析 Service 定义的结构、四种 RPC 方法类型以及方法签名的设计规范。
4.1 Service 定义基础结构
一个完整的 gRPC 服务定义包含 service 关键字、服务名称以及一个或多个 rpc 方法声明。每个方法必须指定请求消息类型和响应消息类型。
核心语法:
service 服务名称 {
rpc 方法名称(请求消息类型) returns (响应消息类型);
}
以下是一个典型的用户管理服务定义:
syntax = "proto3";
package user.v1;
// 用户服务 —— 契约校验的起点
service UserService {
// 获取单个用户信息
rpc GetUser(GetUserRequest) returns (GetUserResponse);
// 批量查询用户
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
// 更新用户资料
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
}
// 请求与响应消息定义
message GetUserRequest {
uint64 user_id = 1;
}
message GetUserResponse {
string name = 1;
string email = 2;
int32 age = 3;
}
message ListUsersRequest {
int32 page_size = 1;
int32 page_number = 2;
}
message ListUsersResponse {
repeated User users = 1;
int32 total_count = 2;
}
message UpdateUserRequest {
uint64 user_id = 1;
string name = 2;
string email = 3;
}
message UpdateUserResponse {
bool success = 1;
}
4.2 四种 RPC 方法类型
gRPC 基于 HTTP/2 的流式能力,定义了四种通信模式。理解这些模式是契约校验的前提,因为不同的模式决定了校验的时机与粒度。
| 方法类型 | 请求模式 | 响应模式 | 典型场景 | 校验特点 |
|---|---|---|---|---|
| Unary(一元) | 单条消息 | 单条消息 | 查询、更新、删除等标准 CRUD | 请求到达时一次性校验,响应返回前一次性校验 |
| Server Streaming(服务端流) | 单条消息 | 多条消息流 | 订阅通知、日志推送、大数据集分批返回 | 请求一次性校验;每条响应消息独立校验 |
| Client Streaming(客户端流) | 多条消息流 | 单条消息 | 文件上传、批量数据提交、传感器数据汇聚 | 每条请求消息独立校验;响应一次性校验 |
| Bidirectional Streaming(双向流) | 多条消息流 | 多条消息流 | 实时聊天、游戏状态同步、金融行情推送 | 每条请求和每条响应消息均独立校验 |
校验策略提示: 在流式模式中,契约校验必须逐消息执行。不能因为前一条消息校验通过就跳过后续消息的校验,因为流中的每条消息都可能携带不同的字段组合或非法值。
4.3 四种方法类型的 Protobuf 声明语法
在 .proto 文件中,通过 stream 关键字来标识流式方向。以下代码展示了四种类型的完整声明:
service StreamService {
// 1. Unary —— 无 stream 关键字
rpc UnaryCall(RequestMessage) returns (ResponseMessage);
// 2. Server Streaming —— 响应前加 stream
rpc ServerStreamingCall(RequestMessage) returns (stream ResponseMessage);
// 3. Client Streaming —— 请求前加 stream
rpc ClientStreamingCall(stream RequestMessage) returns (ResponseMessage);
// 4. Bidirectional Streaming —— 请求和响应前都加 stream
rpc BidirectionalStreamingCall(stream RequestMessage) returns (stream ResponseMessage);
}
常见错误: 初学者容易混淆
stream 的位置。记住口诀:“谁流,谁前加 stream”。客户端流在请求参数前加,服务端流在返回类型前加,双向流两边都加。
4.4 方法签名与参数设计规范
方法签名由 方法名称、请求消息类型 和 响应消息类型 三部分组成。良好的签名设计能显著提升契约的可读性和校验效率。
4.4.1 命名规范
- 动词 + 名词 结构:
GetUser、CreateOrder、DeleteFile - 避免缩写:
GetUsr不如GetUser清晰 - 统一前缀:同一服务内的方法名应保持风格一致
- 不要重载:gRPC 不支持方法重载,同名方法只能出现一次
4.4.2 参数与返回值设计原则
| 设计原则 | 说明 | 反例 | 正例 |
|---|---|---|---|
| 每个方法独立请求/响应消息 | 不要复用同一个消息类型作为多个方法的参数 | rpc GetUser(CommonRequest) |
rpc GetUser(GetUserRequest) |
| 请求消息只包含必要字段 | 避免将 UI 展示字段混入请求 | message GetUserRequest { string page_title = 10; } |
只保留 user_id 等业务字段 |
| 响应消息包含明确的状态 | 使用标准字段或错误模型表达结果 | 仅返回 bool success |
返回 User user + string error_message |
| 流式方法使用 repeated 字段 | 流式响应中每条消息应包含一批数据 | 每条流消息只返回一个 int32 | message Chunk { repeated Item items = 1; } |
4.5 四种 RPC 方法类型的交互流程
下图展示了客户端与服务端之间在四种模式下的消息交换顺序,帮助理解契约校验的触发点。
4.6 契约校验在方法类型中的映射
不同的 RPC 方法类型决定了校验拦截器(Interceptor)的执行策略。下表总结了每种类型对应的校验触发时机:
| 方法类型 | 请求校验时机 | 响应校验时机 | 校验器实现建议 |
|---|---|---|---|
| Unary | 客户端发送请求后,服务端处理前 | 服务端返回响应前,客户端接收前 | 同步校验,返回拒绝或接受 |
| Server Streaming | 同 Unary,仅一次 | 每条响应消息发送前独立校验 | 使用 ServerStreamObserver 包装器逐条校验 |
| Client Streaming | 每条请求消息到达时独立校验 | 同 Unary,仅一次 | 使用 StreamObserver 在 onNext 中校验 |
| Bidirectional Streaming | 每条请求消息到达时独立校验 | 每条响应消息发送前独立校验 | 双向均使用 StreamObserver 逐条校验 |
契约校验核心原则: 无论哪种方法类型,校验逻辑都应基于
.proto 文件中声明的消息结构自动生成,避免在业务代码中手写重复的字段检查。这是“契约驱动校验”的精髓所在。
4.7 方法签名中的字段编号与兼容性
Protobuf 的字段编号(field number)是方法签名中不可见但至关重要的部分。一旦发布,字段编号不应修改,否则会破坏契约兼容性。
- 1-15 号字段:占用 1 字节编码,用于频繁访问的字段
- 16-2047 号字段:占用 2 字节编码,用于扩展字段
- 19000-19999:保留编号,Protobuf 内部使用,不可自定义
- 删除字段时:使用
reserved关键字标记,防止未来复用
message User {
reserved 2, 15, 20 to 30; // 已删除的字段编号
reserved "phone", "fax"; // 已删除的字段名称
uint64 user_id = 1;
string name = 3; // 注意:编号 2 已被 reserved
string email = 4;
}
契约破坏风险: 如果直接删除字段而不使用
reserved,未来新添加的字段可能复用旧编号,导致新旧版本解析错乱。契约校验工具应能检测到这种不兼容变更。
4.8 本章小结
Service 定义是 gRPC 契约校验的蓝图。通过本章的学习,你应该掌握:
- 如何使用
service和rpc关键字定义服务与方法 - 四种 RPC 方法类型的声明语法与交互流程
- 方法签名的命名规范与参数设计原则
- 不同方法类型下契约校验的触发时机与实现策略
- 字段编号的兼容性管理