你是否在思考:订单、库存、支付等微服务如何高效协作 ?
本文以一个完整的 订单事件流系统 为案例,带你用 Golang + Watermill 亲手实现 事件驱动架构。
你将通过实战掌握:
- Watermill 的核心概念:Publisher、Subscriber、Pub/Sub
- 如何用 Outbox 模式保证事务一致性
- 消息流在订单创建到库存预留的落地过程
文章内容从案例出发,边做边学,让你快速上手 Watermill 并理解其设计哲学。
一、案例概览
我们要实现的系统很简单,但涵盖了事件流的核心流程:
- 订单服务:用户下单 → 写入数据库 → 发布订单事件
- 库存服务:订阅订单事件 → 执行库存预留 → 可以再发布其他事件
- 消息中间件:Watermill 管理消息流,可用 GoChannel(本地测试)或 Kafka/NATS(生产)
💡 核心价值:事件驱动架构解耦了服务,Outbox 模式保证数据库事务与事件发布一致性。
二、Watermill 核心概念
在实践中理解概念会更清晰,我们通过案例说明:
| 概念 | 案例中作用 | 说明 |
|---|---|---|
| Publisher | 订单服务发布订单事件 | 将事件推送到消息队列 |
| Subscriber | 库存服务订阅订单事件 | 消费事件并处理库存逻辑 |
| Topic | order_topic |
消息分类,支持多订阅 |
| Pub/Sub | GoChannel(本地)/Kafka | Watermill 提供统一接口,屏蔽底层中间件差异 |
| Outbox | 数据库表 outbox |
保证事务与事件一致,避免消息丢失 |
Tip:Watermill 的理念是 消息流优先,通过统一 API 可以无缝替换不同的消息中间件。
三、案例实战
1️⃣ 创建订单事件(Publisher)
css
logger := logging.NewGoLogger()
publisher, _ := gochannel.NewPublisher(gochannel.Config{}, logger)
// 构建订单事件
msg := message.NewMessage("1", []byte(`{"order_id":"o-1","total":100}`))
// 发布事件
publisher.Publish("order_topic", msg)
fmt.Println("Order event published!")
解读:
NewMessage:构建消息对象,包含唯一 ID 和 payloadPublish:发布到order_topic,下游订阅者会收到
Watermill 中 Publisher 是事件的源头,负责生成和发送消息。
2️⃣ 消费订单事件(Subscriber)
go
logger := logging.NewGoLogger()
subscriber, _ := gochannel.NewSubscriber(gochannel.Config{}, logger)
// 订阅订单事件
messages, _ := subscriber.Subscribe("order_topic")
for msg := range messages {
fmt.Printf("Inventory reserved for order: %s\n", string(msg.Payload))
}
解读:
Subscribe:订阅 topic- 消费循环:处理业务逻辑,例如库存预留
Watermill 的 Subscriber 让事件消费变得统一和可扩展。
3️⃣ Outbox 模式保证一致性
为什么需要 Outbox?
在微服务中,如果你先写数据库再发送消息,可能出现:
- 数据库提交成功,但消息未发送 → 下游无法感知事件
- 消息发送成功,但数据库事务回滚 → 数据不一致
Outbox 解决方案:
- 在事务内写入
outbox表(消息 + 元数据) - Forwarder 读取 outbox → 发布消息 → 标记已处理
- 保证消息与业务操作的原子性
四、实践心得
-
从案例理解概念
- Publisher、Subscriber、Topic、Pub/Sub、Outbox 都有了直观理解
-
GoChannel 适合本地开发
- 轻量、无需外部依赖
- 生产环境需 Kafka/NATS/Redis 支持跨进程通信
-
事件流解耦业务
- 订单服务不关心库存逻辑
- 增加新消费者不会影响现有服务
-
可扩展性强
- 支持并行处理、高并发、异步重试
🔥 实战小贴士:可以在 Forwarder 或 Subscriber 上加中间件实现幂等、重试、日志追踪,进一步提升生产级系统稳定性。
五、运行示例
- 启动 Postgres:
bash
docker-compose up -d
docker exec -it <container> psql -U demo -d demo -f /migrations/0001_create_tables.sql
- 启动服务(两个终端):
arduino
go run ./cmd/order
go run ./cmd/inventory
- 创建订单测试:
rust
curl -X POST http://localhost:8080/orders \
-H 'Content-Type: application/json' \
-d '{"order_id":"o-1","user_id":"u-1","items":[{"sku":"sku-1","qty":2}],"total":100}'
库存服务应打印:
css
Inventory reserved for order: o-1
六、总结
通过这个案例,你可以快速掌握 Watermill 的核心概念及实战应用:
- Publisher/Subscriber:消息的生成与消费
- Pub/Sub 模型:事件解耦和异步处理
- Outbox 模式:数据库事务与消息发布一致性
Watermill 的理念是 统一接口屏蔽底层消息队列差异,可以用 GoChannel 本地测试,也可以轻松切换 Kafka/NATS/Redis Streams 进行生产部署。
🔗 源码地址
- GitHub: github.com/louis-xie-p...
- Gitee: gitee.com/louis_xie/w...