一、引言在前几篇文章中我们逐步构建起了DDD战术设计的核心武器库实体和值对象构成了领域模型的基本细胞领域服务协调跨实体的业务逻辑防腐层守护着领域边界抵御外部概念的污染。然而这些细胞不能孤立存在。一个订单需要关联客户、商户、商品列表、收货地址一个用户需要聚合账户信息、配送地址、角色权限。如何管理这些对象之间的复杂关系如何保证跨多个对象业务操作的数据一致性如何让外部调用者不必关心对象图的内部结构这正是聚合Aggregate、聚合根Aggregate Root、仓库Repository和工厂Factory要回答的问题。它们是保护领域模型的最后一道也是最关键的一道防线。二、聚合整体与部分的统一体1. 从个体到系统实体和值对象体现的是个体的能力——账户可以存钱、取钱订单项包含商品和数量。聚合体现的是这些个体的系统工作能力——一个订单聚合协调着客户、商户、商品列表、收货地址共同完成下单、支付、发货的完整流程。两个典型聚合示例java// 订单聚合 public class Order { private Account client; private Account merchant; private ListProduct products; private Address address; // ... } // 用户聚合 public class User { private String accountId; private String username; private String password; private ListAddress allAddress; private ListRole roles; // ... }2. 聚合的核心作用保证数据一致性聚合最核心的职责是在实现共同的业务逻辑时保证数据的一致性。当用户修改默认收货地址时User聚合必须确保allAddress列表中只有一个地址被标记为默认当订单状态变更为“已支付”时Order聚合需要同时更新订单状态、支付记录、库存锁定状态。这种一致性不是数据库层面的ACID事务而是领域模型内部的不变规则Invariants。聚合通过将相关对象封装在一个边界内确保所有变更都通过聚合根进行从而本地保证一致性极大降低了分布式事务的复杂度。3. 如何识别聚合——整体与部分的关系最实用的判断方法判断聚合关系最有效的方式就是去探讨下是否符合整体与部分的关系。订单与订单项整体与部分 ✅用户与收货地址整体与部分 ✅商品与商品类目整体与部分 ❌商品可以独立存在类目更多是一种分类标签如果某个对象离开了整体就没有独立存在的业务意义它大概率应该是整体的一部分——这既是聚合的划分原则也是对象导向设计中“组合优于继承”的体现。三、聚合根聚合的对外门户1. 为什么需要聚合根如果外部应用可以直接访问聚合内部的任意对象那么聚合的封装性荡然无存外部代码可能绕过业务规则直接修改内部状态聚合内的一致性规则无人保障。解决方案每个聚合必须指定一个唯一的实体作为聚合根Aggregate Root。聚合根是外部访问聚合的唯一入口所有对聚合内部对象的操作都必须通过聚合根进行。2. 聚合根的选择原则聚合根必须是实体有唯一标识聚合根通常是对外暴露的主要业务对象如Order、User聚合根负责创建、存储、删除聚合内的其他对象外部系统只能持有聚合根的ID不能持有内部对象的引用。文档中强调每个聚合中应确定唯一的聚合根实体。在订单聚合中Order就是聚合根在用户聚合中User是聚合根。外部服务只需知道订单ID或用户ID即可通过仓库获取整个聚合然后通过聚合根暴露的方法执行业务操作。3. 聚合与聚合根的价值极大简化对象关系图没有聚合时系统内的对象关系图是一张巨大的网状结构——订单关联用户用户关联角色角色关联权限……任何变更都可能引发雪崩式的影响范围分析。有了聚合与聚合根网状关系被分解为一组以聚合根为中心的星状结构。外部系统只需与聚合根打交道内部对象的生命周期完全由聚合根管理。整个系统的复杂度被降解到聚合内部而聚合之间仅通过聚合根ID进行引用。这是DDD应对复杂业务最重要的战术手段。四、仓库与工厂聚合的生命周期管理1. 工厂封装复杂创建逻辑聚合内部往往包含多个对象它们之间存在复杂的关联关系和不变量约束。客户端直接new出这些对象并组装极易遗漏关键步骤破坏一致性。工厂Factory负责封装聚合的创建过程确保创建出的聚合始终处于有效状态。简单聚合可直接在聚合根中提供静态工厂方法复杂聚合可抽取独立的Factory类。工厂将“如何使用聚合”与“如何构建聚合”分离客户端无需关心聚合内部的装配细节。2. 仓库提供聚合的检索与持久化仓库Repository是聚合在存储端的映射。它为领域层提供了如同集合Collection一样的接口用于获取或保存聚合根。示意图text仓库 Order Product Address ... ← 领域层只依赖Repository接口 ↑ ↑ ↑ └────────┴───────┘ ↓ DB/Redis ← 基础设施层实现仓库的核心价值是隔离领域层与数据访问技术领域层只依赖Repository接口具体的实现JPA、MyBatis、Redis、甚至文件系统被封装在基础设施层当存储技术变更时领域层纹丝不动。3. 仓库与工厂的分工工厂Factory仓库Repository职责创建新的聚合实例检索/持久化已有聚合输入原始数据、参数聚合根ID、规格输出聚合根聚合根状态瞬时持久化二者共同覆盖了聚合的完整生命周期工厂负责“生”仓库负责“活”与“存”。五、聚合、仓库、工厂如何协同保护领域模型文档的小结给出了明确答案聚合、仓库、工厂这些概念的实际意义——保护领域模型。这种保护体现在三个层面1. 封装与一致性聚合聚合将相关对象封装在边界内通过聚合根统一对外提供服务。内部状态不会暴露给外部一致性规则在聚合方法内集中实施——这是对领域模型静态结构的保护。2. 创建完整性工厂工厂确保聚合在诞生之初就是完整、合法的。不完整的聚合无法被创建——这是对领域模型生命周期的保护。3. 持久化隔离仓库仓库将领域模型与数据库、缓存等技术实现彻底解耦。业务代码不再出现EntityManager、JdbcTemplate、RedisTemplate——这是对领域模型纯洁性的保护。三者结合构成了一道从创建、运行到持久化的完整防御链。六、小结文档原汁原味的小结聚合与聚合根设计的作用将复杂的对象关系分解为以聚合根为中心的星状结构保证聚合内数据的一致性极大简化系统整体关系图。通过仓库和工厂实现聚合的设计工厂负责聚合的创建保证创建过程的完整性仓库负责聚合的检索与持久化隔离基础设施变化。聚合、仓库、工厂的实际意义——保护领域模型静态结构聚合封装内部对象动态行为聚合根统一对外生命周期工厂创建仓库存取最终目标无论技术如何变化领域模型始终稳定、可演进。七、思考与拓展1. 实体与值对象的关联与聚合的关联有什么不同如何区分实体与值对象的关联是细粒度、非独立的——值对象属于实体没有独立生命周期聚合内部实体之间是整体与部分的关系——部分的生命周期完全由整体管理聚合之间的关联是通过ID引用的弱关联——彼此不持有对方的引用不保证强一致性。区分的关键是否构成整体与部分部分离开整体是否还有业务意义如果是则是聚合关系否则可能是实体-值对象关系或独立的聚合间引用。2. 仓库和工厂并不是DDD独有的你的项目中这一层是如何设计的很多项目中的DAO/Manager其实扮演了仓库的角色但通常接口暴露了太多技术细节如save(Entity)直接对应数据库insert/updateDDD 仓库的接口应该完全使用领域语言findByUserId(UserId userId)而非selectByCondition(Map params)工厂往往被省略直接在服务层使用new setter——这正是聚合不完整、业务规则泄露的源头。3. 仓库与缓存如何有效结合如何设计高效的缓存一致性仓库实现这是一个极具工程价值的问题缓存位置仓库实现内部可封装多级缓存本地缓存 Redis缓存粒度以聚合根ID为键缓存整个聚合的序列化快照缓存一致性只读查询先读缓存未命中则加载DB并回填写操作先更新DB再删除缓存避免更新缓存带来的并发一致性问题最终一致性可容忍短暂不一致的场景使用Cacheable配合过期时间进阶方案基于领域事件的缓存失效——聚合状态变更时发布事件监听器清除相关缓存。八、结语聚合、聚合根、仓库、工厂——这四个概念在DDD战术设计中紧密耦合却又各司其职。它们共同回答了三个核心问题领域模型应该长成什么样子聚合与聚合根领域模型如何诞生工厂领域模型如何被找到和保存仓库如果说实体和值对象是领域模型的“血肉”领域服务是“肌肉”防腐层是“免疫系统”那么聚合、仓库、工厂就是领域模型的“骨架”——它们支撑起整个模型的结构让模型在面对复杂业务和技术变迁时依然挺拔。