一、引言
在前几篇文章中,我们逐步构建起了DDD战术设计的核心武器库:
-
实体和值对象构成了领域模型的基本细胞;
-
领域服务协调跨实体的业务逻辑;
-
防腐层守护着领域边界,抵御外部概念的污染。
然而,这些细胞不能孤立存在。一个订单需要关联客户、商户、商品列表、收货地址;一个用户需要聚合账户信息、配送地址、角色权限。如何管理这些对象之间的复杂关系?如何保证跨多个对象业务操作的数据一致性?如何让外部调用者不必关心对象图的内部结构?
这正是聚合(Aggregate)、聚合根(Aggregate Root)、仓库(Repository)和工厂(Factory)要回答的问题。
它们是保护领域模型的最后一道,也是最关键的一道防线。
二、聚合:整体与部分的统一体
1. 从个体到系统
实体和值对象体现的是个体的能力——账户可以存钱、取钱;订单项包含商品和数量。
聚合体现的是这些个体的系统工作能力——一个订单聚合协调着客户、商户、商品列表、收货地址,共同完成下单、支付、发货的完整流程。
两个典型聚合示例:
java
// 订单聚合
public class Order {
private Account client;
private Account merchant;
private List<Product> products;
private Address address;
// …
}
// 用户聚合
public class User {
private String accountId;
private String username;
private String password;
private List<Address> allAddress;
private List<Role> 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. 仓库与工厂的分工
| 职责 | 创建新的聚合实例 | 检索/持久化已有聚合 |
| 输入 | 原始数据、参数 | 聚合根ID、规格 |
| 输出 | 聚合根 | 聚合根 |
| 状态 | 瞬时 | 持久化 |
二者共同覆盖了聚合的完整生命周期:工厂负责“生”,仓库负责“活”与“存”。
五、聚合、仓库、工厂如何协同保护领域模型?
文档的小结给出了明确答案:
聚合、仓库、工厂这些概念的实际意义——保护领域模型。
这种保护体现在三个层面:
1. 封装与一致性(聚合)
聚合将相关对象封装在边界内,通过聚合根统一对外提供服务。内部状态不会暴露给外部,一致性规则在聚合方法内集中实施——这是对领域模型静态结构的保护。
2. 创建完整性(工厂)
工厂确保聚合在诞生之初就是完整、合法的。不完整的聚合无法被创建——这是对领域模型生命周期的保护。
3. 持久化隔离(仓库)
仓库将领域模型与数据库、缓存等技术实现彻底解耦。业务代码不再出现 EntityManager、JdbcTemplate、RedisTemplate——这是对领域模型纯洁性的保护。
三者结合,构成了一道从创建、运行到持久化的完整防御链。
六、小结
文档原汁原味的小结:
聚合与聚合根设计的作用
-
将复杂的对象关系分解为以聚合根为中心的星状结构;
-
保证聚合内数据的一致性;
-
极大简化系统整体关系图。
通过仓库和工厂实现聚合的设计
-
工厂负责聚合的创建,保证创建过程的完整性;
-
仓库负责聚合的检索与持久化,隔离基础设施变化。
聚合、仓库、工厂的实际意义——保护领域模型
-
静态结构:聚合封装内部对象;
-
动态行为:聚合根统一对外;
-
生命周期:工厂创建,仓库存取;
-
最终目标:无论技术如何变化,领域模型始终稳定、可演进。
七、思考与拓展
1. 实体与值对象的关联,与聚合的关联有什么不同?如何区分?
-
实体与值对象的关联是细粒度、非独立的——值对象属于实体,没有独立生命周期;
-
聚合内部实体之间是整体与部分的关系——部分的生命周期完全由整体管理;
-
聚合之间的关联是通过ID引用的弱关联——彼此不持有对方的引用,不保证强一致性。
区分的关键:
是否构成整体与部分?部分离开整体是否还有业务意义? 如果是,则是聚合关系;否则可能是实体-值对象关系,或独立的聚合间引用。
2. 仓库和工厂并不是DDD独有的,你的项目中,这一层是如何设计的?
-
很多项目中的 DAO/Manager 其实扮演了仓库的角色,但通常接口暴露了太多技术细节(如 save(Entity) 直接对应数据库 insert/update);
-
DDD 仓库的接口应该完全使用领域语言:findByUserId(UserId userId) 而非 selectByCondition(Map params);
-
工厂往往被省略,直接在服务层使用 new + setter——这正是聚合不完整、业务规则泄露的源头。
3. 仓库与缓存如何有效结合?如何设计高效的缓存一致性仓库实现?
这是一个极具工程价值的问题:
-
缓存位置:仓库实现内部可封装多级缓存(本地缓存 + Redis);
-
缓存粒度:以聚合根ID为键,缓存整个聚合的序列化快照;
-
缓存一致性:
-
只读查询:先读缓存,未命中则加载DB并回填;
-
写操作:先更新DB,再删除缓存(避免更新缓存带来的并发一致性问题);
-
最终一致性:可容忍短暂不一致的场景,使用 @Cacheable 配合过期时间;
-
-
进阶方案:基于领域事件的缓存失效——聚合状态变更时发布事件,监听器清除相关缓存。
八、结语
聚合、聚合根、仓库、工厂——这四个概念在DDD战术设计中紧密耦合,却又各司其职。它们共同回答了三个核心问题:
-
领域模型应该长成什么样子?(聚合与聚合根)
-
领域模型如何诞生?(工厂)
-
领域模型如何被找到和保存?(仓库)
如果说实体和值对象是领域模型的“血肉”,领域服务是“肌肉”,防腐层是“免疫系统”,那么聚合、仓库、工厂就是领域模型的“骨架”——它们支撑起整个模型的结构,让模型在面对复杂业务和技术变迁时依然挺拔。

