供应链场景下 EventStore 的价值与自研构建
供应链场景下 EventStore 的价值与自研构建
在 Keep 的供应链 DDD 改造实践中,多层级业务单据的状态依赖库存变更,若「改库」与「发事件」分离,容易导致上下游状态不一致。采用 EventStore「先持久化事件、再驱动下游」是解决该问题的典型思路。本文从为何用、价值、如何自研三方面整理,并辅以事件流 Mermaid 图。
一、为什么要用 EventStore?
1.1 问题本质
库存变更会牵涉多层业务单据,上层单据状态依赖底层仓储单据状态:
- 采购入库单「入库完成」→ 采购单「已完成」
- 销售出库单「出库完成」→ 销售发货单「已发货」
若采用「先改库再发 MQ」或「先发事件再改库」:
- 库已更新、事件没发出去 → 下游状态永远不更新
- 事件已发、本地事务回滚 → 下游已按事件改了状态,与真实库存不一致
本质是:本地事务与事件投递不在同一原子边界内。
1.2 多单据状态依赖示意
1 | flowchart TB |
EventStore 的思路:把「发生了什么」先作为事件持久化,再基于事件驱动下游。事件与写库纳入同一套一致性模型(如先落事件再改库、或同事务),下游只消费「已持久化」的事件,避免上述两类不一致。
二、使用 EventStore 的价值
| 价值 | 说明 |
|---|---|
| 状态一致性 | 上层单据状态由「已落库的领域事件」驱动,事件必先持久化再投递,避免发信与本地事务割裂。 |
| 可追溯 / 可审计 | 事件即流水,谁在何时因何单据触发何种库存变更,有据可查;出问题可回溯、对账。 |
| 解耦 | 库存上下文只发布「库存已扣减 / 已入库」等事件,订单、采购、售后通过订阅更新自身状态,不直接强依赖库存表或 RPC。 |
| 可复用 | 文中提到「沉淀出较通用的事件组件 EventStore,后续在 Keep 电商内部快速推广复用」。 |
| 可扩展 | 事件持久化后可重放、对账、离线分析,甚至向 Event Sourcing 演进。 |
三、EventStore 自研构建思路
文中未给出完整实现细节,仅给出「发布领域事件」「订阅组注册」「在订阅组中声明订阅事件」及「领域事件异常处理」等使用方式。下面结合通用实践,给出可落地的自研构建思路与事件流。
3.1 核心原则
- 事件先持久化、再投递:事件表与业务表同库同事务,要么一起成功要么一起回滚。
- 至少一次投递:通过扫表或 Outbox 把待发送事件推到 MQ,发送失败可重试。
- 消费幂等:下游按事件 ID + 消费者组做幂等,避免重复消费导致状态错乱。
3.2 事件流总览
1 | sequenceDiagram |
3.3 事件表与发布
- 事件表字段(示意):事件 ID、聚合根类型/ID、事件类型、payload、状态(待发送 / 已发送)、创建时间等。
- 发布流程:领域层在库存变更后调用「发布接口」;应用层在同一事务内写业务表并插入事件行;事务提交后,事件行处于「待发送」状态。
3.4 投递与订阅
1 | flowchart LR |
- 投递:定时任务或 Binlog 解析扫描「待发送」事件,推送到 MQ;发送成功后更新为「已发送」,失败则保留待发送便于重试。
- 订阅:按订阅组管理消费者;在组内声明本组关心的事件类型(文中「在订阅组中声明订阅事件」);消费时用事件 ID + 组 ID 做幂等。
3.5 异常处理(对应文中「黄色部分」)
- 发送失败:事件仍为待发送,下次扫表再发;可加告警与最大重试次数。
- 消费失败:重试、死信、告警;保证不丢事件、不重复生效(幂等)。
- 监控:对「已持久化但长期未成功投递」的事件做监控与补偿。
四、小结
- 为何用:多层级单据状态依赖库存变更,「改库」与「发事件」分离易导致不一致;EventStore 用「事件先持久化再驱动」把二者绑在同一套一致性边界内。
- 价值:状态一致、可追溯、解耦、组件可复用、便于重放与扩展。
- 如何自研:事件表与业务同事务写入 → 扫表/Outbox 推 MQ → 按订阅组 + 事件类型消费 → 幂等 + 异常与重试;领域层只依赖「事件发布接口」,技术细节在基础设施层实现。
把 EventStore 作为「领域事件持久化与投递」的单一职责组件,是供应链等多单据、强一致场景下常见且可复用的做法。