关于DDD中聚合的思考
最近看了www.domaindrivendesign.org网站上的一篇关于如何设计聚合的文章,受益良多,让我对DDD中的聚合有了一些新的理解。
因为发现这篇文章中的观点和jdon上的关于如何设计聚合的观点有些出入,所以必须拿出来和大家讨论一下。
1)文章中介绍,聚合应该尽量小;
2)一些对象之所以成为一个聚合,不仅仅是简单的因为这些对象是具有紧密相关的关联关系,更加重要的是因为这些对象之间需要满足某个不变性(Invariants)规则;这单对理解聚合来说我认为实在是太重要了。
下面听我仔细分析一下:
首先为什么聚合应该尽量小?文中主要是从并发操作的角度来分析的。
大家都知道聚合代表一个完整的概念,是一个持久化存储的单元,聚合内的所有对象要么一起取出来,要么一起删除,一个聚合被保存时,内部的所有子对象的所有状态也要同时全部一起保存,只要其中任何一个子对象有改变,都应该视为整个聚合已被别人修改过,从而会引起整个聚合不能被保存。聚合也就是基于这样的前提才能确保其内部一致性和不变性的。那么假设一个聚合很大,包含了很多子对象,那么当该聚合被持久化时,很可能会保存不了,因为它在被持久化时在并发的情况下几乎总是会发现数据库中的状态比它新,也就是说该聚合已经被别人先修改过了。聚合越大,不能保存的概率越高。这种问题其实很常见,比如一个论坛中的帖子和回复,帖子和回复在一个聚合中,帖子是聚合根,一个帖子包含多个回复。这点大家应该都认同(我以前也这样觉得的,但现在有了新的看法)。在这个例子中,假设多个人同时对一个帖子进行回帖,就很容易产生很多人回不了帖子的情况。原因很简单,就是我们把帖子和回复看成了一个聚合,一个整体了。那么怎么解决这种问题呢?上面那篇文章中提到说,可以设计尽量小的聚合,那么怎么才算小,如何判断当前聚合的大小是否合适呢?为了回答这个问题,就要引起下面我要谈的了。
聚合设计的原则应该是聚合内各个有相互关联的对象之间要保持不变性!这点我们平时太忽视了,我们平时设计聚合时,一般只考虑到了对象之间的关系,比如看其是否能独立存在,是否必须依赖与某个其他对象而存在。举例来说,还是拿上面的帖子和回复的例子来说明。大家都知道回复离开帖子没有意义,因为我们都觉得回复是对帖子的回复,如果连帖子都不存在了,那回复还有什么存在的必要吗?另外,一个帖子有多个回复。鉴于以上的分析,我们很自然的会设计一个聚合,并把帖子作为聚合根,回复作为其子Entity。但这样的设计其实已经会导致我上面一段分析的问题了。那么我们错在那里呢?我认为根本原因就是我们在分析设计聚合时只是片面的分析对象的关系而完全忽视了对象之间的“不变性”规则!那么帖子和回复之间的不变性规则是什么呢?答案是没有不变性规则。如果一定要勉强的找出一个不变性规则,那就是任何一个回复都必须有一个对应的帖子,否则回复没有意义。但是反过来,帖子不会对其内部包含的回复有任何不变性要求。那么这样看来,从不变性的角度来说,回复是可以独立于帖子的。那么从关系的角度去理解是不是也是这样的呢?再举个例子,一个论坛一般有三个典型的元素(版块【Section】,帖子【Thread】,回复【Post】),这三个元素的关系是:一个Section关联多个Thread,一个Thread关联多个Post,奇怪的是,我们为什么常常认为Thread可以独立于Section而单独存在,而Post就不能独立于Thread单独存在了呢?原因你可能会说,因为Section只是对Thread的某个归类,而Post是因为Thread而产生的,虽然关系都是1:N,但是关系的强度不同。确实,从关系的强弱来看,确实如上面所说。但是关系的强弱就可以代表不变性了吗?我认为的不变性应该是这样的,比如a=b+3,假设我有一个规则说a不能大于100,那么就意味着b不能大于97,这就是不变性。所谓的不变性就是说某个规则不能被打破。试问帖子和回复之间有什么不变性吗?我认为,只要我能确保某个回复对应的帖子是一个不变的值对象,我就能确保帖子和回复之间永远保持不变性,因为回复的帖子永远是那个它当初回得那个帖子。
那么我的结论是什么呢,就是设计聚合时,除了要考虑聚合内对象之间的关系外,更重要的还要考虑这些对象之间的不变性是什么?如果没有不变性,对象何必聚合在一起呢?
恭听大家的意见和批评,其实我有时候知道自己可能有想法上的问题,但是我的确在动脑子,我从来不相信权威,只相信自己的思考结果。再贴一张jdon上的论坛的模型图,其实我看的比较费解,感觉和我上面的认为相差很大,尤其是我看不到不变性规则方面的描述。还请banq解释一下,我其实不太希望看到直接最后的结果模型图,而更喜欢看到分析的过程,这才是对大家最有帮助的。banq不要生气啊,呵呵。