用途
该技能用于围绕异步事件设计系统时参考。触发场景包括出现「event-driven architecture」「EDA」「event broker」「Kafka」「publish-subscribe」等表述,或需要设计由事件驱动、松耦合、可独立扩展的系统。
核心思路:组件通过事件代理发出并响应事件来通信,而不是彼此直接调用。生产者不知道谁消费,消费者不知道谁生产。
适用场景
- 需要松耦合、可独立扩展的组件。
- 实时数据处理、流式处理或分析管道。
- 业务工作流天然呈现「X 发生了,现在 Y 作出反应」的形态。
- 下游消费者可以增加或移除,而生产者无需知晓。
不适用场景
- 有严格事务一致性要求——事件是最终一致的。
- 同步请求-响应是自然的通信形态——强行异步会引入延迟与复杂度。
- 组件少、全部运行在同一进程内的小系统——事件属于过度设计,直接调用更简单。
两种拓扑(Richards & Ford)
- 代理拓扑:组件向主题发出事件,订阅者消费,没有中心协调者。适合每个步骤都是小反应的协同式工作流。
- 中介拓扑:中心中介编排工作流,向事件处理器发送命令。适合工作流有确定顺序、需要有人掌握整体流程的场景。
事件与命令
- 事件描述已经发生的事情,用过去时(OrderPlaced、PaymentSettled)。生产者陈述事实,订阅者自行决定是否响应。领域事件是模型层面的同一概念,在 EDA 中同一类事件经由代理流动。
- 命令是要求做某事的祈使请求(PlaceOrder、SettlePayment),面向一个特定处理器。事件是扇出,命令是定向到一个目标。
协同与编排
- 协同:各组件独立响应事件,整体工作流从各自的局部决策中涌现。去中心化、易扩展,但难以看清全貌。
- 编排:由协调者(中介、Saga、工作流引擎)告诉每个组件下一步做什么。中心化、更易推理,但复杂度集中在一处。
代理选型
- 持久日志主题(Kafka):事件保留一段时间,订阅者可重放。适合分析、审计、后加入的消费者。
- 带投递保证的队列(RabbitMQ、SQS):事件投递一次后消失。适合工作队列模式。
- 进程内总线(Spring ApplicationEventPublisher):事件留在单个 JVM 内,成本低,适合单体内部的解耦。
模式纪律
事件模式是事件驱动系统的公共 API,应像对待任何外部契约一样严格版本化:只做向后兼容的变更;新增字段带默认值;废弃字段保留到消费者完成迁移。
决策启发式
- 参与者集合稳定且步骤不超过 4 步时用协同;步骤增多或补偿逻辑不简单时用编排。
- 按实际需要选代理:需要重放选基于日志;一次性投递选队列;进程内选内存总线。
- 单体内部也可以做事件驱动(进程内事件),做 EDA 不一定需要 Kafka。
- 从第一天就搭配模式注册表。模式失控漂移是事件驱动系统腐化最快的原因。
反模式
- 把命令伪装成事件的「事件驱动」(如 UpdateOrderEvent)——本质仍是 RPC,只是多了延迟。必须使用过去时命名。
- 事件当作方法调用:生产者发出事件后立刻监听某个特定消费者的响应,是伪装成异步的同步 RPC。若确实需要请求-响应,就用真正的请求-响应。
- 模式混乱:生产者未经协调就修改事件载荷,消费者静默出错。
- 没有重放策略:关键事件丢失后无法恢复。应有意识地选择代理的保留策略。
- 12 步工作流使用协同:没人能理清整体,应升级为编排。
相关主题
- 微服务架构:事件驱动是微服务常见通信风格之一(另一种是 REST/gRPC)。
- Saga:通过事件/命令编排分布式事务。
- 事件溯源:事件是状态的事实来源,而不仅是通信机制。
- CQRS:常与 EDA 搭配,写入产生事件,读取从事件派生的物化视图投影。
- 领域事件:模型层面的事件概念,常成为线上传输的事件。
参考资料
- Richards & Ford, Fundamentals of Software Architecture, 第 14 章(Event-Driven Architecture Style)。
- Adam Bellemare, Building Event-Driven Microservices(O'Reilly)。
- Khononov, Learning DDD, 第 15 章(Event-Driven Architecture)。