聚合
领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。
你可以这么理解,聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。
byMe:
聚合:
按照一个功能/模块/业务对项目进行划分,将符合同一个功能/模块/业务的实体,值对象找出来,聚合到同一个领域内。
聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的。
聚合属于战略设计上的一个概念,不直接对应代码实现,聚合在代码中的体现是聚合根。
聚合根
负责按照聚合的逻辑,将符合同一个业务逻辑的Entity,VO,聚合到一个class中。
如何设计聚合
![](https://img.haomeiwen.com/i18721752/171d1e1475cac84e.png)
第 1 步:
采用事件风暴,根据业务行为,梳理出在投保过程中发生这些行为的所有的实体和值对象,比如投保单、标的、客户、被保人等等。
第 2 步:
从众多实体中选出适合作为对象管理者的根实体,也就是聚合根。判断一个实体是否是聚合根,你可以结合以下场景分析:是否有独立的生命周期?是否有全局唯一 ID?是否可以创建或修改其它对象?是否有专门的模块来管这个实体。图中的聚合根分别是投保单和客户实体。
第 3 步:
根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象。构建出 1 个包含聚合根(唯一)、多个实体和值对象的对象集合,这个集合就是聚合。在图中我们构建了客户和投保这两个聚合。
第 4 步:
在聚合内根据聚合根、实体和值对象的依赖关系,画出对象的引用和依赖模型。这里我需要说明一下:投保人和被保人的数据,是通过关联客户 ID 从客户聚合中获取的,在投保聚合里它们是投保单的值对象,这些值对象的数据是客户的冗余数据,即使未来客户聚合的数据发生了变更,也不会影响投保单的值对象数据。从图中我们还可以看出实体之间的引用关系,比如在投保聚合里投保单聚合根引用了报价单实体,报价单实体则引用了报价规则子实体。
第 5 步:
多个聚合根据业务语义和上下文一起划分到同一个限界上下文内。这就是一个聚合诞生的完整过程了。
聚合的一些设计原则
《实现领域驱动设计》一书中对聚合设计原则的描述,原文是有点不太好理解的,我来给你解释一下。
1. 在一致性边界内建模真正的不变条件。
聚合用来封装真正的不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性,边界之外的任何东西都与该聚合无关,这就是聚合能实现业务高内聚的原因。
byMe: 聚合内的业务规则一般不变
聚合内的业务规则一般不变,实体和值对象都遵从于该业务逻辑。任何外界变化不会影响到聚合,以实现聚合的高聚合特性。
2. 设计小聚合
如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。而小聚合设计则可以降低由于业务过大导致聚合重构的可能性,让领域模型更能适应业务的变化。
byMe: 小聚合
尽量简单化聚合,防止聚合过于庞大,聚合越小,业务重构的成本越低。
3. 通过唯一标识引用其它聚合
聚合之间是通过关联外部聚合根 ID 的方式引用,而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理,容易导致聚合的边界不清晰,也会增加聚合之间的耦合度。
byMe: 防止聚合之间直接引用,而是通过唯一标识引用其他聚合
如果直接引用其他聚合对象,会增加耦合度,通过唯一标识聚合其他聚合根。
4. 在边界之外使用最终一致性
聚合内数据强一致性,而聚合之间数据最终一致性。在一次事务中,最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的方式异步修改相关的聚合,实现聚合之间的解耦(相关内容我会在领域事件部分详解)。
byMe: 同一个聚合内数据强一致性,聚合之间数据最终一致性。
5. 通过应用层实现跨聚合的服务调用
为实现微服务内聚合之间的解耦,以及未来以聚合为单位的微服务组合和拆分,应避免跨聚合的领域服务调用和跨聚合的数据库表关联。
byMe: 通过应用层实现跨聚合的服务调用
对于跨聚合之间的调用,放在应用层中,在聚合之间再加一层。
上面的这些原则是 DDD 的一些通用的设计原则,还是那句话:“适合自己的才是最好的。”在系统设计过程时,你一定要考虑项目的具体情况,如果面临使用的便利性、高性能要求、技术能力缺失和全局事务管理等影响因素,这些原则也并不是不能突破的,总之一切以解决实际问题为出发点。
网友评论