领域事件建模

领域事件的继承链如下:

image.png

事件基类 DomainEvent:

  1. public abstract class DomainEvent {
  2. private final String _id;
  3. private final DomainEventType _type;
  4. private final Instant _createAt;
  5. }

聚合根对象事件基类,比如对于订单(Order),OrderEvent

  1. public abstract class OrderEvent extends DomainEvent {
  2. private final String orderId;
  3. }

然后对于实际的Order事件,统一继承自OrderEvent,比如对于创建订单的OrderCreatedEvent事件:

  1. public class OrderCreatedEvent extends OrderEvent {
  2. private final BigDecimal price;
  3. private final Address address;
  4. private final List<OrderItem> items;
  5. private final Instant createAt;
  6. }

在创建领域事件时,需要注意2点:

  • 领域事件本身应该是不变的(Immutable);
  • 领域事件应该携带与事件发生时相关的上下文数据信息,但是并不是整个聚合根的状态数据,例如,在创建订单时可以携带订单的基本信息,而对于产品(Product)名称更新的ProductNameUpdatedEvent事件,则应该同时包含更新前后的产品名称:
  1. public class ProductNameUpdatedEvent extends ProductEvent {
  2. /**
  3. * 更新前的名称
  4. */
  5. private String oldName;
  6. /**
  7. * 更新后的名称
  8. */
  9. private String newName;
  10. }

发布领域事件

发布领域事件的几种方式

  1. 应用服务(ApplicationService)发布
  2. 资源库发布(Repository)发布
  3. 事件表方式

对比分析

  1. 1 & 2 方式需要综合考虑 数据库更新和事件发布之间的原子性(全部成功/全部失败),该场景之下,需要引入全局事务(Global Transaction/XA Transaction),但是全局事务本身的效率比较低,同时一些技术框架不支持全局事务
  2. 事件表方式,流程如下:

    1. 在更新业务表的同时,将领域事件一并保存到数据库的事件表中,此时业务表和事件表在同一个本地事务中,即保证了原子性,又保证了效率。
    2. 在后台开启一个任务,将事件表中的事件发布到消息队列中,发送成功之后删除掉事件。(消费方需要考虑幂等性)场景如下:
      1. 代码中先发布事件,成功后再从事件表删除事件
      2. 发布消息成功,事件删除也成功,皆大欢喜;
      3. 如果消息发布不成功,那么代码中不会执行事件删除逻辑,就像事情没有发生一样,一致性得到保证;
      4. 如果消息发布成功,但是事件删除失败,那么在第二次任务执行时,会重新发布消息,导致消息的重复发送。然而,由于我们要求了消费方的幂等性,也即消费方多次消费同一条消息是ok的,整个过程的一致性也得到了保证。

        发布领域事件的流程

  3. 接受用户请求;

  4. 处理用户请求;
  5. 写入业务表;
  6. 写入事件表,事件表和业务表的更新在同一个本地数据库事务中;
  7. 事务完成后,即时触发事件的发送(比如可以通过Spring AOP的方式完成,也可以定时扫描事件表,还可以借助诸如MySQL的binlog之类的机制);
  8. 后台任务读取事件表;
  9. 后台任务发送事件到消息队列;
  10. 发送成功后删除事件。

image.png

消费领域事件

  1. 消费方的幂等性
  2. 消费方有可能进一步产生事件

image.png

事件驱动架构的2种风格

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

image.png

image.png