DDD中聚合根与聚合根之间通信的一些问题的思考
1. 聚合根之间能相互引用吗?
2. 聚合根之间如果相互引用了,则会造成一个可怕的后果,那就是:很容易导致取出一个聚合时会级联取出很多直接或间接引用到的其他聚合根,到最后可能会取出整个对象树;
3. 那聚合根与聚合根之间就不应该相互引用了吗?我的建议是:是的。但是可以只存储引用聚合根的ID;这样就可以建立聚合根与聚合根之间的关系;
4. 那么如何实现聚合根与聚合根之间的通信呢?方法有两个:1)如果是经典的DDD设计,那么应该让领域服务来完成多个聚合根之间的通信,领域服务知道该如何以面向过程的方式如何先调用第一个聚合根做事情,然后再调用第二个聚合根做事情,以此类推。这种方法实际上是一个面向过程的思维,对象实际上已经沦落为被操纵的数据了;2)因为聚合根内不允许注入仓储、服务,并且也不能直接引用其他聚合根,那么如果交互呢?可以通过领域事件实现,即在聚合中如果做了什么操作,本来该调用其他聚合根做事情的地方触发一个领域事件出来,然后其他的领域对象监听该事件,从而完成对象之间的通信。通过这种方法,我们可以在整个领域模型中减少很多领域服务。那么问题是,如果实现这种发送事件与监听事件的机制呢?在领域模型中引入事件总线的设计是一种方法。采用这种设计,就意味着整个领域模型有一个中央事件处理器(事件总线),领域模型中所有的聚合根之间的交互都是通过:事件源生成事件然后传递给事件总线,然后事件总线广播该事件给所有的监听者。但是由于事件监听者是一个个的聚合根,那么如何获取聚合根呢?这是一个问题。另外一个方法也是采用生产者消费者的模式,只不过不是采用事件总线模式,而是让每个聚合根本身就具有发送并广播事件的功能,我们可以在聚合根基类中(交给框架实现)统一实现这个功能,但也会遇到同一个问题,如何以及何时注册事件监听者?
其实我对比了,经典的DDD的领域服务的方式以及事件的方式,其实我觉得从语义上来说,领域服务更能体现业务含义,代码可读性更好。因为它把整个业务过程放在一个服务中完成,我们一看就知道整个业务过程发生了什么;但缺点是扩展性,当一个业务操作需要增加一些步骤或减少一些步骤时,我们必须修改领域服务,但幸好也只需要修改一处即可,即领域服务。而如果是通过事件方式,那么当需求变更时,我们要做的仅仅是增加或移除事件监听者即可,所以可扩展性自然要好很多,但缺点也显而易见,即代码可读性差,我们通常不能完整的知道整个业务操作涉及到哪些领域对象。
以上讨论的都是针对领域模型中聚合根与聚合根之间的通信,不涉及领域模型与其他层之间的通信。我认为聚合根与聚合根之间的通信要难于领域模型与其他层之间的通信。因为领域模型与其他层之间通信时,往往只需要通过IOC创建出一个仓储实例或基础框架层的某个服务的实例即可;但是如果是聚合根与聚合根之间的通信,那么我们为了能够得到监听者聚合根实例,必须要有一个根据事件源聚合根中所引用的目标聚合根的ID找到目标聚合根的过程,而我们希望这个寻找目标聚合根的过程是透明的,这个有点像LazyLoad的概念了,第一次只获取“一个可以找到目标引用对象的ID”,等需要引用到目标对象时,才根据该ID获取目标引用对象。
上面思考了很多,其实我也只是想探索一种即代码可读性好,有方便扩展的聚合根与聚合根之间通信的解决方案,不知道大家遇到这个问题如何解决的?