1. Repository模式与模块边界的关系:核心概念、边界职责、解耦原理
1.1 核心概念:Repository在架构中的定位
Repository模式是领域驱动设计(DDD)中用于封装数据访问逻辑的核心模式。在模块化架构中,Repository的职责并非单纯地“读写数据库”,而是为上层领域层提供面向集合的数据访问接口,使领域层不必关心数据的具体存储介质、查询语言或数据映射策略。
从边界角度看,Repository同时横跨两个层次:
- 领域层(核心业务逻辑)——依赖Repository接口,完成实体的持久化与重建。
- 基础设施层(数据访问实现)——实现Repository接口,封装ORM、SQL、REST等底层细节。
这种跨层边界使得任何时候修改数据访问实现,都不应影响领域层的业务规则与流程编排,从而实现模块间信任链路的最小化。
关键原则:Repository是领域层与基础设施层之间的契约,而非单纯的“数据库操作助手”。其接口语义应当面向业务对象集合(如
OrderRepository提供save(order)和findById(id)),而不是表级的CRUD(如insert()、update())。
1.2 边界职责:谁拥有Repository的定义权
在清晰的模块边界划分中,Repository接口的所有权决定了模块的依赖方向和控制权归属。下面表格对比了两种常见的所有权策略:
| 策略 | 接口定义位置 | 依赖方向 | 模块解耦度 | 典型场景 |
|---|---|---|---|---|
| 领域层定义接口 | 领域模块(Domain) | 基础设施层依赖领域层 | 高(遵从依赖倒置原则) | DDD、六边形架构、整洁架构 |
| 基础设施层定义接口 | 基础设施模块(Infrastructure) | 领域层依赖基础设施层 | 低(领域层侵入底层细节) | 快速原型、简单分层架构 |
推荐策略:将Repository接口定义在领域层,具体实现放在基础设施层。这种方案使领域层保持对数据访问契约的控制,基础设施层只负责“兑现契约”,符合模块边界解耦的最终目标——让核心业务逻辑不因数据库选型、ORM更换或API迁移而被迫修改。
1.3 解耦原理:依赖倒置与信任链路
Repository模式实现模块边界解耦的核心原理是依赖倒置原则(DIP)。其数据流、控制流与信任链路的典型模型如下:
解耦后的典型数据流与控制流拓扑:
- 请求流:应用服务 → 领域服务(含实体) → Repository接口(领域层) → Repository实现(基础设施层) → 数据存储。
- 响应流:数据存储 → ORM/DAO → Repository实现 → 领域实体(重建) → 领域服务 → 应用服务。
- 控制反转点:依赖注入容器在运行时将Repository实现注入给领域服务。领域服务不感知底层实现的存在。
信任链路方面,关键约束包括:
- 领域层只信任Repository接口返回值(领域对象),不信任任何基础设施层的类型或异常。
- 基础设施层必须将底层异常(如数据库连接超时、ORM映射错误)包装为领域层可理解的异常或可选结果(如
Option、Result),避免泄露技术细节。 - 跨模块边界的依赖只能是“领域层定义接口 + 基础设施层引用领域层”,不可反向引用。
常见反模式:在领域层直接使用ORM实体作为领域实体,或者在Repository接口中返回
DataTable、ResultSet等底层数据结构。这两种做法都会破坏模块边界,导致领域层与基础设施层高度耦合。
1.4 Repository与模块边界的结构映射
下图展示了Repository在分层架构中的边界位置、接口所有权以及数据流的隔离方式。注意基础设施层对领域层接口的单向依赖关系,以及领域层对数据存储细节的零引用。
1.5 选型权衡与风险点
在实际架构方案决策中,引入Repository模式需要权衡以下因素:
| 权衡维度 | 优势 | 劣势/风险 |
|---|---|---|
| 模块解耦度 | 领域层与数据访问技术彻底解耦,可独立演进 | 引入抽象接口,增加代码量(约10%~20%) |
| 测试便利性 | 可轻松使用Mock Repository隔离数据库进行单元测试 | 需维护接口与桩对象,测试代码量增大 |
| 团队掌握成本 | 模式成熟,文档和社区支持丰富 | 新手程序员容易将Repository退回为“DAO包装器”,丧失解耦价值 |
| 性能影响 | 可通过Repository接口批量操作优化 | 过度封装可能导致N+1查询或无法利用数据库特定特性 |
主要风险点:
- 接口膨胀:为每个领域实体都创建Repository接口,导致模块间接口数量过多,管理成本上升。建议为聚合根创建Repository,而非所有实体。
- 泄漏持久化模型:将数据库表结构映射到Repository接口参数中(如直接暴露
FindByColumnName),使领域层间接依赖底层数据库Schema。 - 事务边界模糊:多个Repository调用需要在同一事务中完成时,若领域层直接注入Repository,会导致事务控制权完全交给基础设施层,业务逻辑无法掌控原子性。
1.6 落地步骤与验收标准
落地步骤:
- 识别聚合根:在领域层中划分出聚合(Aggregate),每个聚合有且仅有一个Repository接口。
- 定义接口契约:只声明面向集合的操作(
save、findById、delete),避免暴露出底层查询细节。 - 实现基础设施层:在独立模块中编写Repository实现,使用ORM、存储过程或远程调用兑现契约。
- 注入依赖:通过DI容器将实现绑定到领域层接口,确保领域层代码无基础设施层引用。
- 验证边界:执行编译检查(领域层项目不引用Infrastructure程序集/NuGet包),确保零泄漏。
验收标准:
- 编译隔离:领域模块的编译产物不包含对任何数据访问框架或数据库驱动API的引用。
- 可选数据库验证:将Repository实现切换到内存列表或另一个数据库(如SQLite↔PostgreSQL),领域层测试用例全部通过,无需改动一行代码。
- 单元测试覆盖:领域层单元测试中,Repository全部使用Mock/stub,不依赖真实数据库。
- 架构测试:通过ArchUnit(Java)或NetArchTest(.NET)等工具强制验证依赖方向(基础设施层→领域层,领域层↛基础设施层)。
快速检验方法:删除所有基础设施模块的代码后,领域模块依然能够通过编译。如果编译失败,说明领域层存在对基础设施层实现的直接依赖,模块边界已被破坏。