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 方法类型的交互流程

下图展示了客户端与服务端之间在四种模式下的消息交换顺序,帮助理解契约校验的触发点。

四种 RPC 方法类型交互流程 Unary Server Streaming Client Streaming Bidirectional 客户端 客户端 客户端 客户端 服务端 服务端 服务端 服务端 请求 响应 请求 响应1 响应2 响应3 请求1 请求2 请求3 响应 请求1 响应1 请求2 响应2 说明:箭头方向表示消息流向。Unary 一次请求一次响应;Server Streaming 一次请求多次响应;Client Streaming 多次请求一次响应;Bidirectional 双方独立并发收发。

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 方法类型的声明语法与交互流程
  • 方法签名的命名规范与参数设计原则
  • 不同方法类型下契约校验的触发时机与实现策略
  • 字段编号的兼容性管理