Table of contents
Open Table of contents
定义
领域事件是系统里已经发生的一个事实,用过去时描述。它的本质作用是解耦聚合之间的协作。
没有领域事件时的耦合问题
fn checkout(cart_id: CartId) {
let cart = cart_repo.get(cart_id);
let order = Order::create(cart.items);
order_repo.save(order);
payment_service.charge(order.total); // 直接调用
inventory_service.deduct(cart.items); // 直接调用
cart_repo.delete(cart_id); // 直接调用
}
CheckoutService 必须知道支付、库存、购物车三个上下文的存在,改任何一个都要改这里。
有领域事件后
fn checkout(cart_id: CartId) {
let cart = cart_repo.get(cart_id);
let order = Order::create(cart.items);
order_repo.save(order);
event_bus.publish(OrderCreated { order_id, items, total });
// 完了,不管别人怎么处理
}
// 支付上下文自己监听
fn on_order_created(event: OrderCreated) {
payment_service.charge(event.total);
}
// 库存上下文自己监听
fn on_order_created(event: OrderCreated) {
inventory_service.deduct(event.items);
}
// 购物车上下文自己监听
fn on_order_created(event: OrderCreated) {
cart_repo.clear(event.customer_id);
}
CheckoutService 只负责创建订单和发事件,不知道谁在听、有几个人在听。以后加”发邮件通知买家”的需求,加一个 listener 就行,不碰结算代码。
领域事件就是聚合之间的通信协议:“我做完了我的事,谁关心谁自己来拿。“
业界实现
大厂的领域事件 = 分布式消息队列。概念完全一样,基础设施不同。
| 公司 | 消息队列 | 说明 |
|---|---|---|
| 阿里/淘宝 | RocketMQ(自研后开源) | 为了解决订单→支付→库存→物流的事件驱动而造的 |
| 京东 | JMQ(自研)+ Kafka | 订单履约链路全靠消息驱动 |
| 美团 | Kafka + 自研 Mafka | 外卖下单→商家接单→骑手派单,每步都是事件 |
| 海外 | Kafka / Pulsar / SQS | Amazon、Uber、Netflix 都是事件驱动架构 |
下单后淘宝页面显示”订单已创建”,但支付、扣库存、通知商家是异步发生的——这就是领域事件在起作用。偶尔看到”库存扣减中”或者下单成功但几秒后告诉你”库存不足已取消”,这就是最终一致性的外在表现。
简化版 vs 生产版
简化版(进程内):
OrderCreated → 进程内函数调用 → 同步执行
生产版(分布式):
OrderCreated → 序列化成消息 → 发到 RocketMQ/Kafka
→ 支付服务消费这条消息(独立进程、可能不同机器)
→ 库存服务消费这条消息(独立进程、可能不同机房)
→ 失败了可以重试、可以回溯、消息不丢
选型建议
单体应用用进程内 event bus 就够了。将来拆服务时把 event bus 换成消息队列,业务代码几乎不用改——发布/监听的接口一样,只是传输层不同。这也是领域事件这个抽象的价值:不绑定具体基础设施。