何为聚合
在领域模型中,一些实体或者值对象具有强而有力的业务关联关系,于是这些对象就组成了一个聚合,聚合内部的业务实体之间必须保证状态一致性。从技术角度来看,聚合是数据修改与持久化的基本单元,聚合内数据修改必须由聚合根统一组织,以确保每次数据修改都按照聚合内统一的业务规则来完成
聚合为什么存在
在问题域中,不同的业务实体之间存在一些强制的业务规则。例如:订单(Order)总金额=订单行(OrderItem)明细金额之和;一个投保单必然包含投保标的......
为了保证上面的业务一致性,所以DDD中就有了聚合。聚合就是为了保证业务一致性的。
使用聚合碰到的问题:
在DDD中聚合设计的过于臃肿庞大,成为了一个对象树。“聚合根”就是最上层的对象,里面包含许多子对象的数据组或者集合。聚合保存时经常出现事务失败的情况;聚合内部子对象的修改导致整个聚合都要随之变更。
在《实现领域驱动设计》中,也举了这么一个列子:
在scrum核心领域中使用聚合。作者给出了两种聚合的表示形态
形态一:
Product被建模成一个非常大的聚合。此时Product作为一个根(root)对象而存在,它包含了所有的BacklogItem,Release和Sprint。
形态二:
将形态一中,一个大的Product聚合拆分成4个相对较小的聚合:Product、BacklogItem、Release、Sprint,它们都关联值对象ProductID。
很明显,形态二设计的更加合理。Product与BacklogItem、Release、Sprint根本不是强聚合关系。因为Product与BacklogItem、Release、Sprint不具有相同的生命周期,一个Product可以拥有多个Sprint,当Product存在时,并不是每一个Sprint都已经存在,第二个Sprint估计还不存在,反之,第二个Sprint结束了,Product很可能还没有完结。Product与BacklogItem、Release、Sprint就是松散的聚合关系,对于松散聚合关系应该设计成第二种形态。
形态三:
额外补充一下,大家都学过面相对象的分析与设计。大家会设计成形态三的样子。Product拥有自己的ID,BacklogItem、Release、Sprint则是关联一个ProductID。形态三与形态二都不承认“强聚合”关系的存在。组合与聚合都是特殊的关联关系 ,组合强调了整体与部分的生命周期是一致的,而聚合(弱聚合)的整体和部分之间在生命周期上没有什么必然的联系。有很多文章讲解他们区别,感兴趣的读者可以再温习一下。
如何设计聚合
原则一:强聚合设计成对象树的形态,也就是形态一;弱聚合则设计成多个聚合根引用主实体ID的形态——形态二。
学过OOA的开发应该知道“组合”与“聚合”的概念。组合就是一种强聚合,只有这种强聚合才能设计成对象树的形态(形态一),因为强聚合要保证一致性。反之,则建议拆解成多个聚合根,也就是形态二
1、如何区分“组合”与“聚合”:
组合是强聚合,内部实体具有不可违背的业务一致性关系,而且内部实体之间具有一致的生命周期。
例如在电商领域中,订单(order)与订单行(orderItem)就是强聚合,订单是聚合根,多个订单行形成子实体集合;订单(Order)总金额=订单行(orderItem)明细金额之和。但是在员工(Employee)与员工异动记录,则是弱聚合关系. 组织内部的一个员工(Employee)可以有多条“员工异动记录”,一个“员工异动记录”必然属于一个员工,但是员工异动记录与员工不具有相同的生命周期。
原则二:一个聚合内必须保证数据强一致性
这个也就是模型中真正的不变条件,是我们的业务规则的体现,我们在程序设计中会使用事务一致性来保证。思考一下,在程序设计中,是否需要“事务一致性“,如果没有,则应该怀疑这个聚合存在的合理性。
同时,在一个事务中,建议只修改一个聚合实例。
原则三:引用其他聚合的ID,而不是直接引用聚合
如何你明白形态三的设计,这一点应该很好理解。弱聚合设计成关联关系,你直接关联主实体的ID,而不是关联主实体。
在BacklogItem中,直接存在属性值:productID,releaseID,sprintID。而不是直接存在对象product、release、sprint
实践举例
订单与订单行
背景:在大集团采购中,组织会批量采购原材料。例如某建筑公司,会批量采购水泥、钢筋、砖块、砂浆,该建筑公司会下单给可信任的供应商,形成一个大的订单, 而且订单总金额=Sum(订单子项金额)
在建模设计中,订单总金额=Sum(订单子项金额)是一个强制性约束,而且订单行必然属于一个订单,订单行都填写完毕后,该订单才是完整的;
系统在保存时,必须保证订单与订单行的一致性,所以这是一个强聚合。强聚合设计成对象树的形态,也就是形态一.
员工与年度考核记录
员工与自己的年度考核记录是弱聚合关系。一个员工可以有多个年度考核记录;员工每考核一次就多一条考核记录,但是考核记录与员工不具有相同的生命周期。弱聚合则设计成多个聚合根引用主实体ID的形态——形态二,我更加倾向于形态三。
总结
聚合的设计体现了一个开发者领域建模的能力,只要大家分清强聚合与弱聚合,基本就可以舍弃掉不需要的臃肿聚合设计。而强聚合则是实体间具有不可违背的业务一致性关系,而且内部实体之间具有一致的生命周期。