知识卡片
发件箱模式与工作流引擎替代方案:至少一次语义
内容
发件箱模式(outbox pattern)解决的是这样一个问题:一个服务要把业务逻辑结果存进关系型数据库、同时在事件总线上发一条事件,但数据库和事件总线是两种不同资源,无法共用同一个ACID事务——而业务上你需要的是原子性:”业务逻辑完成且事件已发送”和”两者都没发生”,不允许出现”只完成一半”的中间态。经典实现是把要发布的事件写进领域数据所在数据库的另一张”发件箱”表,这样写业务结果和写事件就能在同一个数据库事务里原子完成;再由一个独立的调度机制读取发件箱表、真正把事件发布出去,成功后从表里删除。这个方案有两个关键特征:一是事件保证会被发出,但可能会有延迟(这本身就是一种最终一致性);二是在某些故障场景下(比如调度器发布完事件、却在提交发件箱表修改前崩溃),事件有可能被发布两次,这种语义叫”至少一次”(at-least-once)——保证消息不丢,但不保证不重复。工作流引擎可以直接替代整套发件箱基础设施:把”执行业务逻辑并提交结果”和”发布事件”表达成同一个流程模型里的两个任务,工作流引擎会保证这两个任务以原子方式最终都被执行——如果中途任何一步崩溃,引擎会持久化已完成的状态(比如记住业务逻辑已完成、事件还没发),从正确的任务重新开始,同样实现”至少一次”语义,还能扩展到两个以上任务,而不需要单独搭建发件箱表和调度器,同时还能顺带获得工作流工具自带的监控和运维能力。
结构图:
flowchart TD
subgraph 传统发件箱["传统发件箱模式"]
A1[业务逻辑+写发件箱表] -->|同一DB事务,原子| A2[(发件箱表)]
A2 --> A3[独立调度器读取并发布事件]
A3 -->|成功后删除| A2
end
subgraph 工作流引擎替代["工作流引擎替代方案"]
B1[任务1:执行业务逻辑并提交] --> B2[任务2:发布事件到总线]
B1 -.引擎持久化状态,崩溃后从正确任务重启.-> B2
end
传统发件箱 -.语义相同.-> C[至少一次 at-least-once]
工作流引擎替代 -.语义相同.-> C
参考来源
- 位置:《流程自动化实战:系统架构和软件开发视角》第9章《工作流引擎与集成挑战》"9.2.4 使用发件箱模式链接资源"(源文件:_epub-src/EPUB/xhtml/Section0001_0013.xhtml)
- 结论依据:原文详述发件箱表如何借助同一数据库事务实现业务逻辑与事件写入的原子性、调度器崩溃可能导致事件重复发布的"至少一次"语义,以及工作流引擎如何用两个任务的流程模型替代整套发件箱基础设施并保持相同语义,直接支撑本卡片的结构图与解释。
- 原始内容:只有在数据库事务成功后,才会使用某种调度机制执行事件发布……这种事务语义被称为至少一次(at-least-once)……工作流引擎将在正确的任务上重新开始。这就实现了所有任务执行至少一次的语义,与发件箱表结果一致。