如何通过Spring Crud/Jpa Repository合规实现领域驱动设计(DDD)
我完全理解你现在的困惑——DDD的分层规范和Spring Data JPA的便捷性好像天生有点冲突,对吧?别担心,这里有两种贴合DDD原则的解决方案,帮你平衡规范和开发效率:
解决DDD领域层仓储接口与Spring JPA依赖的矛盾
核心问题在于:DDD要求领域层必须保持纯净,不能依赖基础设施层的框架代码,但Spring的JpaRepository属于基础设施层的Spring Data组件,直接让领域层的仓储接口继承它会把框架依赖带入领域层,破坏了分层原则。
方案1:领域层定义纯净仓储接口,基础设施层实现(最贴合DDD规范)
这是DDD推荐的标准做法,严格分层,隔离业务与技术细节:
- 第一步:在领域层定义完全不依赖Spring的纯净仓储接口,只暴露业务需要的方法:
// 领域层代码 - 无任何Spring框架依赖 public interface CustomerRepository { Customer findById(Long id); void save(Customer customer); List<Customer> findAll(); // 这里只放领域业务需要的查询方法,比如findByEmail(String email)等 }
- 第二步:在基础设施层创建Spring Data JPA的实现类,继承
JpaRepository并实现领域层的CustomerRepository:
// 基础设施层代码 - 依赖Spring Data JPA @Repository public class JpaCustomerRepository extends JpaRepository<Customer, Long> implements CustomerRepository { // 基础CRUD方法不需要重复实现,JpaRepository已经提供 // 如果领域层有自定义查询方法,在这里用Spring Data的规则实现(比如方法名、@Query注解) }
- 第三步:领域服务中注入领域层的
CustomerRepository接口,而非具体的JPA实现,保持领域层的独立性:
// 领域层代码 - 领域服务 @Service public class CustomerService { private final CustomerRepository customerRepository; // 构造注入,依赖领域层抽象接口 public CustomerService(CustomerRepository customerRepository) { this.customerRepository = customerRepository; } // 业务逻辑方法示例 public Customer registerCustomer(Customer customer) { // 先做业务校验,比如邮箱是否已存在 // ... return customerRepository.save(customer); } }
方案2:用@NoRepositoryBean做折中(减少代码重复)
如果觉得定义两层接口太繁琐,可以用Spring Data的@NoRepositoryBean来简化,同时尽量遵守DDD原则:
- 在领域层定义一个通用的领域仓储标记接口(或包含通用业务方法),标注
@NoRepositoryBean:
// 领域层代码 @NoRepositoryBean public interface DomainRepository<T, ID> { T findById(ID id); void save(T entity); // 其他领域通用的持久化方法... }
- 在基础设施层创建继承
JpaRepository和DomainRepository的具体仓储接口:
// 基础设施层代码 @Repository public interface CustomerRepository extends JpaRepository<Customer, Long>, DomainRepository<Customer, Long> { // 自定义查询方法,比如findByEmail(String email) }
- 领域服务中优先注入
DomainRepository<Customer, Long>,如果需要调用Spring Data的特定方法,再注入CustomerRepository(但尽量避免在领域层使用框架特定方法)
关键原则提醒
- 领域层只关心业务规则和领域概念,绝对不能出现任何与持久化框架相关的代码(比如Spring的注解、JPA的API)
- 基础设施层负责实现领域层定义的抽象,处理具体的技术细节(不管是JPA、MyBatis还是MongoDB,都不影响领域层)
- 遵循依赖倒置原则:高层模块(领域层)不依赖低层模块(基础设施层),二者都依赖抽象(领域层的仓储接口)
内容的提问来源于stack exchange,提问作者user1188867
相关产品推荐
相关产品推荐

