Skip to content

09 - 订单服务实现 ​

订单服务是整个 Saga 流程的起点和终点,负责订单创建、状态同步和 HTTP 入口。

代码架构 ​

订单服务遵循 Clean Architecture 分层:

ecommerce/internal/order/
├── service/       # HTTP 层:请求解析、响应构造、事件发布
│   └── handler.go
├── biz/           # 业务层:领域模型、用例、事件处理
│   ├── usecase.go
│   └── event_handler.go
└── data/          # 数据层:GORM 模型、Repository 实现
    ├── model.go
    └── repo.go

HTTP 入口:创建订单 ​

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):

消费的事件状态变更说明
PaymentCompletedpending → confirmed支付成功,订单确认
PaymentFailedpending → cancelled支付失败,取消订单
InventoryInsufficientpending → 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 行,结构清晰、职责分明。