DDD中聚合根与聚合根之间通信的一些问题的思考

1. 聚合根之间能相互引用吗?
2. 聚合根之间如果相互引用了,则会造成一个可怕的后果,那就是:很容易导致取出一个聚合时会级联取出很多直接或间接引用到的其他聚合根,到最后可能会取出整个对象树;
3. 那聚合根与聚合根之间就不应该相互引用了吗?我的建议是:是的。但是可以只存储引用聚合根的ID;这样就可以建立聚合根与聚合根之间的关系;
4. 那么如何实现聚合根与聚合根之间的通信呢?方法有两个:1)如果是经典的DDD设计,那么应该让领域服务来完成多个聚合根之间的通信,领域服务知道该如何以面向过程的方式如何先调用第一个聚合根做事情,然后再调用第二个聚合根做事情,以此类推。这种方法实际上是一个面向过程的思维,对象实际上已经沦落为被操纵的数据了;2)因为聚合根内不允许注入仓储、服务,并且也不能直接引用其他聚合根,那么如果交互呢?可以通过领域事件实现,即在聚合中如果做了什么操作,本来该调用其他聚合根做事情的地方触发一个领域事件出来,然后其他的领域对象监听该事件,从而完成对象之间的通信。通过这种方法,我们可以在整个领域模型中减少很多领域服务。那么问题是,如果实现这种发送事件与监听事件的机制呢?在领域模型中引入事件总线的设计是一种方法。采用这种设计,就意味着整个领域模型有一个中央事件处理器(事件总线),领域模型中所有的聚合根之间的交互都是通过:事件源生成事件然后传递给事件总线,然后事件总线广播该事件给所有的监听者。但是由于事件监听者是一个个的聚合根,那么如何获取聚合根呢?这是一个问题。另外一个方法也是采用生产者消费者的模式,只不过不是采用事件总线模式,而是让每个聚合根本身就具有发送并广播事件的功能,我们可以在聚合根基类中(交给框架实现)统一实现这个功能,但也会遇到同一个问题,如何以及何时注册事件监听者?


其实我对比了,经典的DDD的领域服务的方式以及事件的方式,其实我觉得从语义上来说,领域服务更能体现业务含义,代码可读性更好。因为它把整个业务过程放在一个服务中完成,我们一看就知道整个业务过程发生了什么;但缺点是扩展性,当一个业务操作需要增加一些步骤或减少一些步骤时,我们必须修改领域服务,但幸好也只需要修改一处即可,即领域服务。而如果是通过事件方式,那么当需求变更时,我们要做的仅仅是增加或移除事件监听者即可,所以可扩展性自然要好很多,但缺点也显而易见,即代码可读性差,我们通常不能完整的知道整个业务操作涉及到哪些领域对象。

以上讨论的都是针对领域模型中聚合根与聚合根之间的通信,不涉及领域模型与其他层之间的通信。我认为聚合根与聚合根之间的通信要难于领域模型与其他层之间的通信。因为领域模型与其他层之间通信时,往往只需要通过IOC创建出一个仓储实例或基础框架层的某个服务的实例即可;但是如果是聚合根与聚合根之间的通信,那么我们为了能够得到监听者聚合根实例,必须要有一个根据事件源聚合根中所引用的目标聚合根的ID找到目标聚合根的过程,而我们希望这个寻找目标聚合根的过程是透明的,这个有点像LazyLoad的概念了,第一次只获取“一个可以找到目标引用对象的ID”,等需要引用到目标对象时,才根据该ID获取目标引用对象。

上面思考了很多,其实我也只是想探索一种即代码可读性好,有方便扩展的聚合根与聚合根之间通信的解决方案,不知道大家遇到这个问题如何解决的?

2011年11月12日 13:19 "@tangxuehua"的内容
聚合根之间能相互引用吗?
2. 聚合根之间如果相互引用了
3. 那聚合根与聚合根之间就不应该相互引用了吗?
4.那么如何实现聚合根与聚合根之间的通信呢

代表不同聚合边界的聚合根之间当然不能相用,但是聚合边界内可以有两个聚合根,比如轿车和发动机都可以是聚合根,两者可以相互引用。

谈到聚合根之间的通信交互,这就涉及到动作行为模式,而动作行为事件的发生都是讲究场景的,没有莫名其妙发生的事情,事实发生都蕴含一定场景背景因素。

我总结出一张推到图:实体 ==> 场景 ===> 交互/通信/动作/事件,这实际就是DDD + DCI + Events 架构的浓缩。

我们不能再象过去基于数据库编程那样,要么是静态数据,要么满天飞的函数调用,它们之间没有场景约束的。

有了场景,我们就自然知道聚合根实体如何通信,他们是在场景这个地方相互通信。本来他们之间没有关系,因为一个特定的目标或任务走在了一起,就如同夫妻是两个男女是在婚姻或繁衍后代这个场景下走在了一起,但是这不代表他们之间就有天然的引用关系。


[该贴被banq于2011-11-14 10:42修改过]

2011年11月12日 13:19 "@tangxuehua"的内容
我们通常不能完整的知道整个业务操作涉及到哪些领域对象。 ...


scala作者在采访中说过一句话:我们代码易读性,并不是操作过程易读,而是逻辑易读(意思是这样,当时是问作者引入特殊特性的和精简目的)。与其去关注涉及什么领域对象,我更愿意去理解这个业务逻辑是一个什么谓词。话说,事件会不知道领域对象么?
[该贴被SpeedVan于2011-11-14 14:05修改过]

经过进一步的一些学习,我觉得可以这样来实现聚合根之间的通信:
如果是经典的DDD,则通过领域服务来协调聚合根做事情;
如果是DCI,则在场景中协调聚合根扮演角色,然后做事情;

聚合根之间通过ID方式引用,而不是通过指针引用。
原因如下:
1)ID同样可以起到表示对象关系的作用;
2)使用ID关联可以天生让聚合更轻巧,节省不必要的内存,提高性能和可伸缩性;
3)使用ID关联可以避免取出一个聚合时,整个数据库被拖出来的风险,当然这是在没有LazyLoad支持的情况下才会发生;
4)使用ID关联的聚合不会对ORM等持久化机制有特殊要求,比如必须支持LazyLoad特性等;
5)ID是值对象,具有不变性,而引用则不是。