Skip to content

Latest commit

 

History

History
66 lines (48 loc) · 3.02 KB

从事务脚本到领域驱动2.md

File metadata and controls

66 lines (48 loc) · 3.02 KB

从事务脚本到领域驱动(2)——领域事件

DDD要求一个事务只允许修改一个聚合根。为什么要有如此严格的限制?让我们来考虑一个简单的场景,我们有2个聚合根分别是Role(角色)Page(页面权限),一个 Role可以拥有有0..n个Page。当用户在页面权限维护功能模块里删除一个 Page 实例时,需要级联删除 Role 与该 Page 实例的关联数据。传统的做法是让 PageService 依赖 RoleService,在 deletePage(pageId) 方法内调用 roleService.removeAllPage(pageId) 方法来移除所有对已删除 Page 实例的关联。页面权限维护功能模块的作者怎么知道到底有哪些模块关联了 Page 实例? 随着系统的演进,当其他模块也需要关联 Page 时,还要及时通知页面权限维护功能模块的作者增加新的删除代码,这违反了Open-Close原则。

我们可以使用领域事件以响应式编程的方式解决这个问题。要实现领域事件,有许多框架可供选择,Spring框架自带的事件机制是最简单的。

PageRepository delete()方法里发出页面权限已删除事件:

public class PageRepository ... {
  public void delete(String id) {
    pageMapper.deleteById(id);
    domainEvents.publish(PageDeleted.now(id));
  }
}

RoleApplicationService 里响应页面权限已删除事件:

public class RoleApplicationService {
  /** 响应页面已删除领域事件. */
  @EventListener
  void handlePageDeleted(PageDeleted pageDeleted) {
    this.roleRepository.deleteAllOwnedPages(pageDeleted.getPageId());
  }
}

PageDeleted是一个携带了关键信息的值对象:

/** 页面相关领域事件. */
public interface PageEvent extends DomainEvent {
  String getPageId();

  default String getAggregateId() {
    return getPageId();
  }

  @Value
  class PageDeleted implements PageEvent {
    @NonNull String eventId = MybatisUtil.nextId().toString();
    @NonNull Instant when;
    @NonNull String pageId;

    public static PageDeleted now(String pageId) {
      return new PageDeleted(Instant.now(), pageId);
    }
  }
}

其他选择

  • Spring框架自带的消息机制是同步的,想要更好的性能可以使用 greenrobot/EventBusguava/EventBus

  • projectreactor 是一个相对简单且完备的响应式框架,Spring的亲儿子。而且有针对 Kafka 和 RabbitMQ 的桥接。

  • 提到响应式框架当然少不了老牌的 RxJava

  • 在微服务之间传递事件消息需要使用消息队列,例如 RabbitMQ Kafka

事件溯源(EventSourcing)

事件溯源 是一个挺大的话题,目前还没有实际尝试过。