关于DDD中聚合的思考

11-11-29 tangxuehua
最近看了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不要生气啊,呵呵。

         

17
tangxuehua
2011-11-29 00:18
jdon的登录功能很有问题啊为什么我都登录了,但是右上角还是显示为注册和登录?还有,为什么我不能编辑帖子了呢?这种这么明显的问题还希望尽早处理掉啊,否则对初次来这个论坛的人印象很差的,感觉非常业余。

xmuzyu
2011-11-29 00:58
楼主提出的不变性的设计很好,在面向对象设计中有两个基本原则:高内聚,松耦合,DDD中只讲了聚合的重要性,但是没有对如何设计聚合给出详细的描述。如果聚合设计的不恰当,聚合根聚合了不该聚合的东西那么久必然导致低内聚,高耦合。至于聚合的边界是什么,我目前想到的就是生命周期以及楼主所提及的不变性。聚合根一般生命周期是要长于被聚合的对象,不能说聚合根老大都挂啦,被聚合的小的们还活着,另外一方面聚合根内部的状态在整个聚合根的生命周期内都要保证不变性。

banq
2011-11-29 08:38
2011年11月29日 00:01 "@tangxuehua"的内容
从不变性的角度来说,回复是可以独立于帖子的。 ...

回复贴和帖子之间的结构关系一旦创建应该是不变的。另外,正如你所说,很多人都在回复,这种回复事件会影响帖子的状态ThreadState(当前贴的最新回复时间和最新贴被不断更新),因此ThreadState和ForumState是独立于聚合根外面存在的,这个案例类似DDD中订单和价格那个案例。

不变性的理解有多个角度:比如生命周期一致,或者业务规则约束,不变性是让数据成为有层次有边界的重要依据,这是区别于数据表扁平式数据结构的重要地方。

希望多多交流,共同进步。

tangxuehua
2011-11-29 10:32
按照banq的设计,ForumState和ThreadState是不包含在帖子聚合内的,那么它们看你的设计应该是值对象,因为只包含了一些关于版块或帖子的统计信息。

但是按照我的理解,值对象只是一个值,没有独立存在的意义,它必须用于描述某个Entity才有意义。那么为什么banq你能把ForumState,ThreadState放到聚合之外呢?

它们到底是以什么样的方式存在着呢?据我的理解,能独立存在具有独立生命周期的一定是聚合根实体。

另外,其实我对是否应该设计ForumState和ThreadState这两个对象也表示怀疑,这两个对象本质上只是一些附加信息,就是对Forum以及Thread的一些统计信息,其实这些信息即便不在

领域模型中维护,照样可以通过Forum,Thread,Post三者之间的1:N的关系而统计出来。所以,理论上这三个状态对象可以完全不用设计。所以我不会把这三个对象看作是设计的重点。

相反,我们要考虑的主要点是帖子和回复之间,如果banq也赞同帖子和回复是属于同一个聚合的话,并且帖子是聚合根的话。那么帖子和回复应该就是一个整体,是一个持久化的最小单元。

那么banq是如何解决我上面所说的多用户情况下的并发问题的呢?其实我的设计是,应该把帖子和回复都设计为独立的聚合,它们各自都是聚合根,即可。

猜你喜欢
4Go 1 2 3 4 下一页