仓储接口定义规范

仓储接口是领域层与基础设施层之间的契约边界,其定义规范直接决定数据层的解耦程度。本章围绕方法命名、参数约束、返回类型、异常声明与异步支持五个维度,阐述如何构建稳定、可测试、与实现无关的仓储接口。

1. 边界与分层

仓储接口位于领域层,实现位于基础设施层。领域层依赖倒置,只依赖接口抽象,不依赖任何ORM、数据库驱动或缓存客户端。接口规范必须禁止泄漏任何基础设施类型(如ResultSet、Session、SQLException)。

核心原则:接口的签名和返回值中不应出现任何不属于领域层的类型。 一旦出现Pageable、Sort等框架类型,即构成对基础设施的隐式耦合。

2. 方法命名规范

方法名应使用统一领域语言,避免暴露数据源实现细节。推荐前缀约定:

操作类型推荐前缀示例禁止示例
查询单条findfindById、findByEmailgetUserById、queryUser
查询集合find 或 listfindByStatus、listActivesearchUsers、loadAll
保存/更新savesave、saveAllinsert、update、upsert
删除deletedeleteById、deleteremove、erase
计数countcountByStatusgetCount、total

3. 参数约束

  • 参数类型:只接受领域值对象、实体ID或领域内建类型(如EmailAddress、Money)。禁止使用Map、String裸参、基础设施DTO。
  • 不可变性:所有入参应视为只读,接口实现不应修改参数对象。
  • 可选性:使用java.util.Optional显式表示可能为空或可选的参数(如Optional sortField)。
  • 分页与排序:如需分页,应使用领域层定义的PageRequest值对象,而非框架Pageable。

4. 返回类型规范

场景推荐返回类型风险
查询单条可能不存在Optional<T>返回null导致NPE,调用方无法强制检查
查询多条List<T> 或不可变集合返回null或可变集合导致副作用
保存后返回T 或 void返回void则调用方无法获取ID,需权衡
批量操作int影响行数 或 void暴露实现细节(如JPA的批处理限制)

重要: 永远不要返回null,优先使用空集合或Optional。集合应通过Collections.unmodifiableList包装成不可变。

5. 异常声明

仓储接口应定义统一的异常基类,所有实现层异常包装到该基类下:

public abstract class RepositoryException extends RuntimeException {
    // 可选:错误码、上下文信息
}

public class EntityNotFoundException extends RepositoryException { }
public class ConcurrencyConflictException extends RepositoryException { }
  • 接口方法不应抛出任何基础设施异常(如SQLException、DataAccessException)。
  • 如需检查型异常,可定义领域异常并在接口throws中声明;但推荐使用运行时异常简化调用。
  • 异常应携带上下文(如实体ID、操作类型),便于调用方日志记录。

6. 异步支持

异步接口设计需考虑调用方与实现方的线程模型解耦。推荐两种模式:

模式返回类型适用场景风险
Future模式CompletableFuture<T>Java 8+,调用方可组合、超时控制需要线程池管理,避免阻塞
响应式模式Mono<T> / Flux<T>高吞吐、背压场景引入响应式框架,增加复杂度
// 接口定义示例
public interface UserRepository {
    Optional findById(UserId id);
    List findByStatus(Status status);
    User save(User user);
    void deleteById(UserId id);

    // 异步变体
    CompletableFuture> findByIdAsync(UserId id);
    CompletableFuture> findByStatusAsync(Status status);
}
选型权衡: 如果团队熟悉响应式编程且项目有高并发要求,可使用Mono/Flux;否则CompletableFuture风险更低,迁移成本小。接口应同时提供同步和异步版本,避免强制调用方改变编程模型。

7. 数据流与控制流

  • 信任链路: 领域服务 → 仓储接口(契约) → 仓储实现(ORM/存储层)。接口规范确保调用方不越界访问实现细节。
  • 数据流方向: 领域对象 → 接口参数 → 实现映射为数据源操作 → 返回领域对象。
  • 控制流反转: 接口定义异常和异步类型,实现方必须适配,不可将底层异常透传。
风险点:
  1. 命名中混入数据库术语(如findByColumnName)导致接口与表结构耦合。
  2. 参数使用了Map或Object,丧失类型安全。
  3. 异常基类中泄漏实现细节(如OptimisticLockException直接来自JPA)。
  4. 异步方法返回Future但实现中调用get()阻塞当前线程,破坏异步意图。

8. 落地步骤与验收标准

步骤具体任务输出产物
1定义仓储接口命名约定文档,所有开发同学Review命名规范.md
2定义领域异常基类及子类(至少3个:NotFound、Conflict、Generic)RepositoryException.java
3选择异步返回类型(建议先从CompletableFuture开始)接口模板
4编写第一个仓储接口(如UserRepository)并配套Mock测试接口+单元测试
5代码审查:检查接口中是否有基础设施依赖审查checklist

验收标准:

  • 仓储接口中无任何import来自javax.persistence、org.hibernate、org.springframework.data等基础设施包。
  • 所有方法返回值不含null,集合返回不可变。
  • 异常全部继承自RepositoryException,且不在方法签名中抛出基础设施异常。
  • 单元测试可使用Mockito完全Mock接口,无需启动数据库。
仓储接口定义规范体系 领域层仓储接口 方法命名规范 参数约束 返回类型规范 异常声明 异步支持 说明:五个规范维度共同构成仓储接口的契约约束, 任何维度的背离都会导致领域层与基础设施层耦合。 验收标准:接口无基础设施引用,异常统一基类,返回无null,异步不阻塞

9. 选型权衡总结

维度推荐策略替代方案风险对比
方法命名find/save/delete 前缀get/insert/removeget暗示必有值,易导致误用
参数类型领域值对象原始类型或String原始类型丢失语义,参数字段变更需改接口
返回类型Optional / 不可变集合null / 可变集合null引发NPE,可变集合导致调用方误改
异常运行时异常 + 领域基类检查型异常 / 无异常检查型异常强制调用方处理,可能泄露实现细节
异步CompletableFuture响应式框架响应式学习成本高,调试困难

通过严格定义仓储接口规范,团队可以建立清晰的信任链路:领域层只依赖契约,基础设施层只负责实现契约,双方通过接口签名隔离。这为后续替换存储技术(如从MySQL切换到DynamoDB)提供了安全的边界。