4. gRPC 服务定义:service 关键字与四种 RPC 模式
在领域驱动设计中,gRPC 服务定义是契约边界的核心载体。通过 service 关键字,我们将领域能力暴露为远程可调用的方法集合,并严格定义请求与响应的消息结构。gRPC 支持四种 RPC 模式,分别对应不同的领域交互场景。
4.1 service 关键字:定义领域服务契约
在 .proto 文件中,service 关键字用于声明一个 gRPC 服务。每个服务包含一组 RPC 方法,每个方法指定输入消息类型和输出消息类型。这是领域服务在分布式系统中的显式边界。
syntax = "proto3";
package order.v1;
// 订单领域服务 —— 定义订单相关的远程调用契约
service OrderService {
// 创建订单(一元 RPC)
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
// 查询订单列表(服务端流式 RPC)
rpc ListOrders(ListOrdersRequest) returns (stream Order);
// 批量提交订单(客户端流式 RPC)
rpc BatchSubmitOrders(stream Order) returns (BatchSubmitResponse);
// 实时订单状态跟踪(双向流式 RPC)
rpc TrackOrderStatus(stream OrderStatusRequest) returns (stream OrderStatusEvent);
}
message CreateOrderRequest {
string user_id = 1;
repeated OrderItem items = 2;
}
message CreateOrderResponse {
string order_id = 1;
OrderStatus status = 2;
}
message Order {
string order_id = 1;
string user_id = 2;
repeated OrderItem items = 3;
OrderStatus status = 4;
}
message OrderItem {
string product_id = 1;
int32 quantity = 2;
double price = 3;
}
message ListOrdersRequest {
string user_id = 1;
int32 page_size = 2;
string page_token = 3;
}
message BatchSubmitResponse {
int32 success_count = 1;
int32 fail_count = 2;
repeated string failed_order_ids = 3;
}
message OrderStatusRequest {
string order_id = 1;
}
message OrderStatusEvent {
string order_id = 1;
OrderStatus new_status = 2;
string timestamp = 3;
}
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_CONFIRMED = 2;
ORDER_STATUS_SHIPPING = 3;
ORDER_STATUS_DELIVERED = 4;
ORDER_STATUS_CANCELLED = 5;
}
领域设计提示:每个
service 应映射一个限界上下文中的聚合根或领域服务。例如 OrderService 对应订单上下文,PaymentService 对应支付上下文。避免将多个上下文的 RPC 方法混入同一个 service 定义中。
4.2 四种 RPC 模式详解
gRPC 基于 HTTP/2 的流式能力,提供了四种通信模式。下表对比了它们的核心特征与适用场景:
| 模式 | 关键字 | 请求 | 响应 | 典型领域场景 |
|---|---|---|---|---|
| 一元 RPC | rpc Method(Request) returns (Response) |
单个消息 | 单个消息 | 创建订单、查询用户信息、支付确认 |
| 服务端流式 RPC | rpc Method(Request) returns (stream Response) |
单个消息 | 消息流 | 订单列表分页、日志导出、数据同步 |
| 客户端流式 RPC | rpc Method(stream Request) returns (Response) |
消息流 | 单个消息 | 批量上传、文件分片上传、传感器数据聚合 |
| 双向流式 RPC | rpc Method(stream Request) returns (stream Response) |
消息流 | 消息流 | 实时聊天、订单状态推送、协同编辑 |
4.3 四种 RPC 模式的通信流程
下图展示了四种 RPC 模式在客户端与服务端之间的消息交互时序:
4.4 一元 RPC:最基础的领域操作
一元 RPC 是最简单的模式,客户端发送一个请求消息,服务端返回一个响应消息。它天然适合领域驱动设计中的命令(Command)和查询(Query)操作。
- 命令操作:如
CreateOrder、CancelOrder,请求包含执行命令所需的参数,响应返回执行结果。 - 查询操作:如
GetOrder、GetUserProfile,请求包含查询条件,响应返回查询结果。
Go 服务端实现示例:
func (s *orderServer) CreateOrder(ctx context.Context, req *pb.CreateOrderRequest) (*pb.CreateOrderResponse, error) {
// 领域逻辑:创建订单聚合根
order, err := s.domainService.CreateOrder(ctx, req.UserId, req.Items)
if err != nil {
return nil, status.Errorf(codes.Internal, "创建订单失败: %v", err)
}
return &pb.CreateOrderResponse{
OrderId: order.ID,
Status: pb.OrderStatus_ORDER_STATUS_PENDING,
}, nil
}
4.5 服务端流式 RPC:推送数据集合
当服务端需要返回大量数据或持续推送事件时,服务端流式 RPC 是理想选择。客户端发送一个请求,服务端通过流多次发送响应消息。
- 分页查询:如
ListOrders,服务端逐页返回订单数据,客户端按需消费。 - 事件推送:如
SubscribeOrderEvents,服务端持续推送订单状态变更事件。
领域设计建议:服务端流式 RPC 适合读模型的批量返回。在 DDD 中,这通常对应查询模型(Query Model)或领域事件(Domain Event)的订阅。注意流式响应应支持取消和超时控制。
4.6 客户端流式 RPC:批量提交与聚合
客户端流式 RPC 允许客户端发送多条消息,服务端在接收完所有消息后返回一个聚合响应。这适用于批量操作场景。
- 批量上传:如
BatchSubmitOrders,客户端逐条发送订单,服务端批量处理并返回统计结果。 - 数据聚合:如
CollectSensorData,客户端持续发送传感器数据,服务端聚合后返回分析结果。
注意:客户端流式 RPC 要求服务端在接收完所有消息前不能发送响应。如果处理时间较长,建议在响应中包含进度信息,或考虑使用双向流式 RPC 实现更灵活的交互。
4.7 双向流式 RPC:实时双向通信
双向流式 RPC 允许客户端和服务端独立地发送和接收消息流。双方可以按任意顺序读写,实现真正的全双工通信。
- 实时状态跟踪:如
TrackOrderStatus,客户端发送订单 ID 流,服务端返回状态变更事件流。 - 聊天系统:客户端和服务端互相发送消息,实现实时对话。
- 协同编辑:多个客户端通过服务端同步编辑操作。
Go 双向流服务端实现示例:
func (s *orderServer) TrackOrderStatus(stream pb.OrderService_TrackOrderStatusServer) error {
for {
req, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
// 模拟状态变更事件推送
for _, event := range s.domainService.GetStatusEvents(req.OrderId) {
if err := stream.Send(event); err != nil {
return err
}
}
}
}
4.8 模式选择决策树
在领域驱动设计中,选择正确的 RPC 模式直接影响契约边界的清晰度和系统性能。以下决策树可辅助选择:
4.9 流式 RPC 的 Go 实现要点
在 Go 中实现流式 RPC 时,需要注意以下关键点:
| 模式 | 服务端接口 | 客户端调用 | 关键方法 |
|---|---|---|---|
| 服务端流式 | ServerStreamingServer |
ClientStreamingClient |
Send() / Recv() |
| 客户端流式 | ClientStreamingServer |
ClientStreamingClient |
Recv() / SendAndClose() |
| 双向流式 | BidiStreamingServer |
BidiStreamingClient |
Recv() / Send() |
流式 RPC 避坑指南:
- 服务端流式 RPC 中,
Send()可能阻塞,需考虑背压(backpressure)控制。 - 客户端流式 RPC 中,客户端必须调用
CloseAndRecv()告知服务端流结束。 - 双向流式 RPC 中,
Recv()和Send()可以在不同 goroutine 中并发执行,但需注意同步。 - 所有流式 RPC 都应处理
context.Context的超时和取消信号。
4.10 领域驱动设计中的模式映射
将四种 RPC 模式映射到 DDD 战术模式,可以更清晰地表达领域意图:
- 一元 RPC → 领域服务方法:对应聚合根上的命令方法或查询方法,如
Order.Create()。 - 服务端流式 RPC → 领域事件订阅:对应领域事件的发布-订阅模式,服务端作为事件源推送事件。
- 客户端流式 RPC → 批量领域命令:对应批量处理多个领域命令,如批量创建订单。
- 双向流式 RPC → 领域事件流:对应实时事件流处理,客户端订阅特定事件,服务端持续推送。
总结:选择 RPC 模式时,应从领域交互的语义出发,而非技术偏好。一元 RPC 是最简单的契约,应作为默认选择;流式 RPC 仅在数据量、实时性或批量处理需求明确时引入。清晰的契约边界是领域驱动设计与 gRPC 结合的核心价值。