领域事件作为ddd的核心组件之一,在ddd框架中处于一个重要地位,也是开发中非常常用的功能之一。
在基于Spring boot的框架开发中,Spring自带的事件可以很好地作为领域事件的基础(功能齐全且自带事务支持)。但开发框架过程中,遇到了一个设计难点。即:领域事件的发布时机。
1、抛开编程语言,从自然的角度出发,领域事件应该在触发该事件的业务发生时发布。例如:
UserAggregateRoot userAggr = createUser();
eventPublisher.publishEvent(new UserRegisteredEvent(userAggr));
但在实际项目中,上述方式显然有很大问题,因为此时业务发生在聚合根中,而该聚合根还未保存到数据库。而监听事件方会触发数据库更新,这将导致数据出现严重错误。
2、那么把领域事件的发布放到保存之后是否可行?例如:
User user = userRepository.save(userAggr);
eventPublisher.publishEvent(new UserRegisteredEvent(user));
在功能上,该方式可行。但是,它不符合ddd分层设计。
ddd分层设计思路是,领域层处理主要业务逻辑,服务层处理业务编排,仓储层处理数据持久化。领域事件的发布显然属于业务编排范围,但在这个方案中,事件发布代码放在了仓储层,这显然会给开发带来很大的混乱,所以这个方式也不好。
3、把领域事件暂时存储到公共变量中,等触发了持久化动作后,统一发布事件。例如:
UserAggregateRoot userAggr = createUser();
threadLocalEvents.add(new UserRegisteredEvent(userAggr));
...
userRepository.save(userAggr);
eventPublisher.publishEvent(threadLocalEvents);
该方式可行,但感觉仍不是最好的方案。
4、在参考了很多资料后,最终决定把领域事件存储到聚合根中,等触发了持久化动作后,统一发布事件。例如:
UserAggregateRoot userAggr = createUser();
public void createUser(){
this.addEvent(new UserRegisteredEvent(this));
}
...
userRepository.save(userAggr);
最后由框架来实现eventPublisher.publishEvent(userAggr.getEvents());
该方案的优点是:没有违反ddd分层设计,且代码结构清晰。