资源库(Repository)
用途:决定持久化接口该怎么定义、查询该写在哪一层时查阅
定义
「定义」小节链接资源库为聚合提供“集合式”的存取抽象:接口定义在领域层,语义是“按标识取回聚合”“把聚合存回去”;实现放在基础设施层,负责 ORM、SQL、缓存与事务细节。
它与 DAO 的区别在于抽象层次:DAO 面向表,Repository 面向聚合。
由来与要解决的问题
「由来与要解决的问题」小节链接如果领域对象直接依赖表结构与 SQL 方言,业务模型就会被存储设计绑架:换存储、拆表、加分库分表都要改模型;单元测试必须起数据库。Repository 把存储细节隔离在边界外,让模型可以独立演进与测试。
Eric Evans 的原始定义强调“集合语义”:仓储表现得像内存中该类型聚合的集合,只是这个集合被持久化了。
接口设计规则
「接口设计规则」小节链接| 规则 | 说明 |
|---|---|
| 一个聚合一个仓储 | 不为聚合内部对象单独建仓储 |
| 只按聚合根存取 | findById(id): Aggregate,不返回部分对象图 |
| 返回领域模型 | 不返回 DTO、不返回持久化对象 |
| 查询方法克制 | 只保留领域真正需要的查找(existsByX、findActiveByOwner),复杂查询另设查询服务 |
| 不含业务规则 | 仓储里不写“状态为 X 才更新”这类判断 |
| 接口不泄漏技术细节 | 不出现分页 SQL 方言、事务注解、表名 |
实现形态
「实现形态」小节链接| 形态 | 做法 | 注意点 |
|---|---|---|
| 关系型 + ORM | 根表与成员表一起加载,Assembler 转换 | 防 N+1:显式 join fetch 或批量加载 |
| 关系型 + 手写 SQL | MyBatis 类映射,SQL 可控 | 映射代码与模型易漂移,需测试守护 |
| 文档数据库 | 一个聚合一文档,天然匹配 | 受文档大小限制,无界集合需拆 |
| 事件存储 | 由事件流重建聚合,附加快照 | 需要版本化事件 Schema 与重放策略 |
| 缓存前置 | 仓储内组合缓存与库 | 缓存失效必须与写路径一致,避免脏读假象 |
开源参考:Spring Data JPA、MyBatis / MyBatis-Plus、JOOQ、Axon Framework(事件存储型仓储)、MongoDB 文档模型。
与查询侧的分工
「与查询侧的分工」小节链接写路径通过聚合根与仓储保证不变式;读路径不需要聚合,应当用查询服务直接面向展示需求拼装数据(投影表、宽表、搜索引擎都可以是数据源)。把两者混在一个仓储里,会得到一堆“为某个页面定制的多表 join 方法”,仓储随即失去意义。
这一分工是 CQRS 的最小可用形态,不需要事件溯源也能落地。
常见误区
「常见误区」小节链接| 误区 | 表现 | 修正 |
|---|---|---|
| 仓储返回 DTO | 领域层间接依赖前端结构 | DTO 归查询服务或接口层 |
| 通用 CRUD 仓储暴露给业务 | save(entity) 谁都能调,规则被绕过 |
聚合根方法承担变更,仓储只在应用服务中被调用 |
| 为聚合内部实体建仓储 | 内部对象可被单独修改 | 只能通过根访问 |
| 把分页/动态条件塞进仓储接口 | 接口被 UI 需求驱动频繁变更 | 复杂检索独立建模 |
| 事务写在仓储实现里 | 一个用例多次提交,一致性破裂 | 事务边界在应用服务 |
与其他概念的关系
「与其他概念的关系」小节链接- 聚合:仓储面向聚合,一致性边界与存取边界应重合。
- 实体与值对象:仓储负责把持久化结构还原成完整聚合(含值对象)。
- 分层与依赖倒置:仓储接口是领域层拥有、基础设施层实现的典型倒置点。
延伸阅读
「延伸阅读」小节链接- Eric Evans《领域驱动设计》第 6 章(Repository 模式)
- Martin Fowler《企业应用架构模式》第 4 章(Repository 与 Record Mapper、Unit of Work)
- Vaughn Vernon《实现领域驱动设计》第 12 章(仓储与聚合持久化)