09 - 订单服务实现
订单服务是整个 Saga 流程的起点和终点,负责订单创建、状态同步和 HTTP 入口。
代码架构
订单服务遵循 Clean Architecture 分层:
ecommerce/internal/order/
├── service/ # HTTP 层:请求解析、响应构造、事件发布
│ └── handler.go
├── biz/ # 业务层:领域模型、用例、事件处理
│ ├── usecase.go
│ └── event_handler.go
└── data/ # 数据层:GORM 模型、Repository 实现
├── model.go
└── repo.goHTTP 入口:创建订单
POST /orders 的处理流程(ecommerce/internal/order/service/handler.go:34-77):
go
func (s *OrderService) HandleCreateOrder(w http.ResponseWriter, r *http.Request) {
var req CreateOrderReq
json.NewDecoder(r.Body).Decode(&req)
// 1. 创建订单(状态 = pending)
order := &biz.Order{OrderID: req.OrderID, UserID: req.UserID, Total: req.Total}
s.uc.Create(r.Context(), order)
// 2. 构造 OrderCreated protobuf 事件
event := &eventsv1.OrderCreated{
OrderId: req.OrderID, UserId: req.UserID,
Items: items, Total: req.Total,
}
// 3. 发布事件到 Kafka
payload := events.MustMarshal(event)
msg := message.NewMessage(watermill.NewUUID(), payload)
s.pub.Publish(events.TopicOrderCreated, msg)
w.WriteHeader(http.StatusCreated)
}关键设计:先写库再发消息。如果消息发布失败,订单已入库(状态 pending),可以通过定时巡检或死信队列处理。这符合 at-least-once 语义。
业务层(UseCase)
ecommerce/internal/order/biz/usecase.go:44-54 定义订单创建业务逻辑:
go
func (uc *OrderUseCase) Create(ctx context.Context, req *Order) error {
req.Status = "pending"
if req.OrderID == "" {
return fmt.Errorf("订单ID不能为空")
}
return uc.repo.Create(ctx, req)
}依赖接口 OrderRepo 而非具体实现,方便单元测试和替换存储:
go
type OrderRepo interface {
Create(ctx context.Context, o *Order) error
UpdateStatus(ctx context.Context, orderID string, status string) error
FindByOrderID(ctx context.Context, orderID string) (*Order, error)
}事件消费:状态同步
订单服务通过 Router 消费外部事件,根据 Saga 结果更新订单状态(ecommerce/internal/order/biz/event_handler.go:23-49):
| 消费的事件 | 状态变更 | 说明 |
|---|---|---|
| PaymentCompleted | pending → confirmed | 支付成功,订单确认 |
| PaymentFailed | pending → cancelled | 支付失败,取消订单 |
| InventoryInsufficient | pending → cancelled | 库存不足,取消订单 |
Handler 函数接收原始 *message.Message,自行解析 protobuf:
go
func (h *OrderEventHandler) HandlePaymentCompleted(msg *message.Message) error {
var event eventsv1.PaymentCompleted
proto.Unmarshal(msg.Payload, &event)
return h.uc.Confirm(context.Background(), event.OrderId)
}数据层
ecommerce/internal/order/data/repo.go 实现 OrderRepo 接口,使用 GORM 操作 MySQL:
go
func (r *orderRepo) Create(ctx context.Context, o *biz.Order) error {
order := &Order{
OrderID: o.OrderID, UserID: o.UserID,
Total: o.Total, Status: o.Status,
}
return r.db.WithContext(ctx).Create(order).Error
}注意数据层模型(data.Order)和业务层模型(biz.Order)是分离的——前者包含 GORM tag、表名等持久化细节,后者是纯领域对象。
main.go 组装
ecommerce/cmd/order-service/main.go 按序执行:配置加载 → DB 初始化 → Kafka 连接 → 依赖注入(repo → usecase → eventHandler)→ Router 注册 → HTTP 启动。整个文件约 95 行,结构清晰、职责分明。