领域事件建模
领域事件的继承链如下:

事件基类 DomainEvent:
public abstract class DomainEvent {private final String _id;private final DomainEventType _type;private final Instant _createAt;}
聚合根对象事件基类,比如对于订单(Order),OrderEvent
public abstract class OrderEvent extends DomainEvent {private final String orderId;}
然后对于实际的Order事件,统一继承自OrderEvent,比如对于创建订单的OrderCreatedEvent事件:
public class OrderCreatedEvent extends OrderEvent {private final BigDecimal price;private final Address address;private final List<OrderItem> items;private final Instant createAt;}
在创建领域事件时,需要注意2点:
- 领域事件本身应该是不变的(Immutable);
- 领域事件应该携带与事件发生时相关的上下文数据信息,但是并不是整个聚合根的状态数据,例如,在创建订单时可以携带订单的基本信息,而对于产品(Product)名称更新的
ProductNameUpdatedEvent事件,则应该同时包含更新前后的产品名称:
public class ProductNameUpdatedEvent extends ProductEvent {/*** 更新前的名称*/private String oldName;/*** 更新后的名称*/private String newName;}
发布领域事件
发布领域事件的几种方式
- 应用服务(ApplicationService)发布
- 资源库发布(Repository)发布
- 事件表方式
对比分析
- 1 & 2 方式需要综合考虑 数据库更新和事件发布之间的原子性(全部成功/全部失败),该场景之下,需要引入全局事务(Global Transaction/XA Transaction),但是全局事务本身的效率比较低,同时一些技术框架不支持全局事务
事件表方式,流程如下:
- 在更新业务表的同时,将领域事件一并保存到数据库的事件表中,此时业务表和事件表在同一个本地事务中,即保证了原子性,又保证了效率。
- 在后台开启一个任务,将事件表中的事件发布到消息队列中,发送成功之后删除掉事件。(消费方需要考虑幂等性)场景如下:
接受用户请求;
- 处理用户请求;
- 写入业务表;
- 写入事件表,事件表和业务表的更新在同一个本地数据库事务中;
- 事务完成后,即时触发事件的发送(比如可以通过Spring AOP的方式完成,也可以定时扫描事件表,还可以借助诸如MySQL的binlog之类的机制);
- 后台任务读取事件表;
- 后台任务发送事件到消息队列;
- 发送成功后删除事件。

消费领域事件
- 消费方的幂等性
- 消费方有可能进一步产生事件

事件驱动架构的2种风格
- 事件通知
- 发布方发布事件
- 消费方接收事件并处理
- 消费方调用发布方的API以获取事件相关数据
- 消费方更新自身状态
- 事件携带状态转移(Event-Carried State Transfer)
- 事件溯源


