cd ..
2025-12-0412 min42 views

为什么DDD架构需要依赖倒置?

#Architecture#Domain Driven Design#Spring Boot#Java#Dependency Inversion Principle

在 DDD(领域驱动设计)中,基础层(Infrastructure Layer)领域层(Domain Layer) 的依赖关系是整个架构设计的核心难点,也是最容易出错的地方。

传统的分层架构(Layered Architecture)和 DDD 架构(往往采用六边形架构/洋葱架构)在这里有本质的区别。

这就是著名的 依赖倒置原则(Dependency Inversion Principle, DIP) 在架构层面的体现。


1. 传统架构 vs. DDD 架构的依赖方向

❌ 传统分层架构(依赖向下)

在传统的 MVC 或三层架构中,依赖关系通常是自上而下的:

  • Controller (依赖) -> Service (依赖) -> DAO/Infrastructure

问题:这意味着你的业务逻辑(Service)直接依赖于具体的数据库技术(DAO/Infra)。

  • 如果你想把 MySQL 换成 MongoDB,你需要修改 Service 代码。
  • 如果你想对 Service 做单元测试,你很难剥离数据库环境。
✅ DDD 架构(依赖倒置/依赖向内)

DDD 要求领域层(Domain)必须是纯净的,不依赖任何外部技术细节。

  • Domain Layer:定义接口(Interface)。
  • Infrastructure Layer:实现接口(Implementation)。

核心规则基础层依赖于领域层,而不是领域层依赖于基础层。


2. 它是如何工作的?(代码演示)

让我们通过“用户仓储(Repository)”这个经典例子来看依赖是如何倒置的。

第一步:在 Domain Layer(领域层)定义“契约”

领域层只关心“我要存取数据”,不关心“怎么存取”。

package com.example.domain.repository; // 属于领域层

import com.example.domain.model.User;

// 这是一个接口!不依赖任何 Spring、MyBatis 包
public interface UserRepository {
    User findById(String userId);
    void save(User user);
}
第二步:在 Infrastructure Layer(基础层)提供“实现”

基础层引用领域层,负责具体的技术落地。

package com.example.infrastructure.persistence; // 属于基础层

import com.example.domain.repository.UserRepository; // 依赖领域层
import com.example.domain.model.User;
import org.springframework.stereotype.Repository;

// 这里依赖了 Spring Data JPA 或 MyBatis
@Repository
public class UserRepositoryImpl implements UserRepository {
    
    private final JpaUserDao jpaUserDao; // 具体的 JPA DAO

    public UserRepositoryImpl(JpaUserDao jpaUserDao) {
        this.jpaUserDao = jpaUserDao;
    }

    @Override
    public User findById(String userId) {
        // 将 JPA 实体转换为 领域实体 (POJO)
        UserEntity entity = jpaUserDao.findById(userId).orElse(null);
        return UserMapper.toDomain(entity); 
    }

    @Override
    public void save(User user) {
        // 将 领域实体 转换为 JPA 实体并保存
        UserEntity entity = UserMapper.toEntity(user);
        jpaUserDao.save(entity);
    }
}

3. 为什么要这样做?(核心收益)

如果你按照“基础层依赖领域层”来做,你会获得以下巨大的好处:

A. 领域纯净性 (Domain Purity)

领域模型(Entity, Value Object)不需要引入 javax.persistenceorg.apache.ibatis 等注解。它是纯粹的 Java POJO。

  • 好处:业务逻辑代码极其干净,任何人(甚至非技术人员)都能读懂核心逻辑,而不受 SQL 语句干扰。
B. 可测试性 (Testability)

当你测试领域服务(Domain Service)时,因为依赖的是 UserRepository 接口:

  • 好处:你可以轻松 Mock 一个 InMemoryUserRepository不需要启动数据库,不需要 Spring 上下文,单元测试运行速度飞快。
C. 技术可替换性 (Swapability)
  • 好处:今天用 MySQL,明天想把某些数据迁移到 Elasticsearch。你只需要在基础层新写一个 ElasticUserRepositoryImpl 实现领域层的接口,领域层的一行代码都不用改

4. 常见的一个“妥协”陷阱

虽然理论上领域层应该完全纯净,但在 Java 开发(尤其是使用 Spring Boot + JPA)的实际操作中,存在一个著名的争议点:注解污染

理想情况(纯洁派):

  • Domain Entity 是纯 POJO。
  • Infra Layer 有单独的 DB Entity(带 @Entity, @Table 注解)。
  • 需要一个 Mapper 在两者之间转换(如上面的代码示例)。
  • 缺点:代码量翻倍,转换繁琐。

现实情况(实用派):

  • 直接在 Domain Entity 上加 @Entity 注解。
  • 缺点:领域层依赖了 JPA 包(依赖泄露),破坏了纯净性。
  • 结论:如果你的业务逻辑不是极度复杂,且确定未来 5 年不会换掉 JPA,这种妥协是可以接受的,能减少大量样板代码。但要时刻警惕不要在实体里写 SQL 逻辑。

总结

  • 控制流(运行方向):Controller -> Service -> RepositoryImpl (指向数据库)。
  • 依赖关系(编译方向):Infrastructure -> Domain (指向核心)。

这种反转,就是为了保护最重要的资产——业务逻辑,让它不随技术的更迭而频繁变动。

/** Comments(0)*/

Loading comments...
为什么DDD架构需要依赖倒置? | UbanillxのDevLog