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)。其数据流、控制流与信任链路的典型模型如下:

解耦后的典型数据流与控制流拓扑:
  1. 请求流:应用服务 → 领域服务(含实体) → Repository接口(领域层) → Repository实现(基础设施层) → 数据存储。
  2. 响应流:数据存储 → ORM/DAO → Repository实现 → 领域实体(重建) → 领域服务 → 应用服务。
  3. 控制反转点:依赖注入容器在运行时将Repository实现注入给领域服务。领域服务不感知底层实现的存在。

信任链路方面,关键约束包括:

  • 领域层只信任Repository接口返回值(领域对象),不信任任何基础设施层的类型或异常。
  • 基础设施层必须将底层异常(如数据库连接超时、ORM映射错误)包装为领域层可理解的异常或可选结果(如Option、Result),避免泄露技术细节。
  • 跨模块边界的依赖只能是“领域层定义接口 + 基础设施层引用领域层”,不可反向引用。
常见反模式:在领域层直接使用ORM实体作为领域实体,或者在Repository接口中返回DataTable、ResultSet等底层数据结构。这两种做法都会破坏模块边界,导致领域层与基础设施层高度耦合。

1.4 Repository与模块边界的结构映射

下图展示了Repository在分层架构中的边界位置、接口所有权以及数据流的隔离方式。注意基础设施层对领域层接口的单向依赖关系,以及领域层对数据存储细节的零引用。

Repository 模式模块边界与依赖关系 领域层 (Domain Layer) 应用服务/领域服务 实体 (Entity) / 值对象 Repository 接口 (定义在领域层) 依赖注入 (DI Container) 基础设施层 (Infrastructure Layer) Repository 实现 (Impl) (依赖领域层接口) ORM/DAO/HttpClient 数据库/外部存储 依赖契约 模块边界:领域层只依赖接口,基础设施层实现接口并引用领域层。

1.5 选型权衡与风险点

在实际架构方案决策中,引入Repository模式需要权衡以下因素:

权衡维度 优势 劣势/风险
模块解耦度 领域层与数据访问技术彻底解耦,可独立演进 引入抽象接口,增加代码量(约10%~20%)
测试便利性 可轻松使用Mock Repository隔离数据库进行单元测试 需维护接口与桩对象,测试代码量增大
团队掌握成本 模式成熟,文档和社区支持丰富 新手程序员容易将Repository退回为“DAO包装器”,丧失解耦价值
性能影响 可通过Repository接口批量操作优化 过度封装可能导致N+1查询或无法利用数据库特定特性

主要风险点:

  1. 接口膨胀:为每个领域实体都创建Repository接口,导致模块间接口数量过多,管理成本上升。建议为聚合根创建Repository,而非所有实体。
  2. 泄漏持久化模型:将数据库表结构映射到Repository接口参数中(如直接暴露FindByColumnName),使领域层间接依赖底层数据库Schema。
  3. 事务边界模糊:多个Repository调用需要在同一事务中完成时,若领域层直接注入Repository,会导致事务控制权完全交给基础设施层,业务逻辑无法掌控原子性。

1.6 落地步骤与验收标准

落地步骤:

  1. 识别聚合根:在领域层中划分出聚合(Aggregate),每个聚合有且仅有一个Repository接口。
  2. 定义接口契约:只声明面向集合的操作(save、findById、delete),避免暴露出底层查询细节。
  3. 实现基础设施层:在独立模块中编写Repository实现,使用ORM、存储过程或远程调用兑现契约。
  4. 注入依赖:通过DI容器将实现绑定到领域层接口,确保领域层代码无基础设施层引用。
  5. 验证边界:执行编译检查(领域层项目不引用Infrastructure程序集/NuGet包),确保零泄漏。

验收标准:

  • 编译隔离:领域模块的编译产物不包含对任何数据访问框架或数据库驱动API的引用。
  • 可选数据库验证:将Repository实现切换到内存列表或另一个数据库(如SQLite↔PostgreSQL),领域层测试用例全部通过,无需改动一行代码。
  • 单元测试覆盖:领域层单元测试中,Repository全部使用Mock/stub,不依赖真实数据库。
  • 架构测试:通过ArchUnit(Java)或NetArchTest(.NET)等工具强制验证依赖方向(基础设施层→领域层,领域层↛基础设施层)。
快速检验方法:删除所有基础设施模块的代码后,领域模块依然能够通过编译。如果编译失败,说明领域层存在对基础设施层实现的直接依赖,模块边界已被破坏。