跳转到内容

资源库(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 章(仓储与聚合持久化)