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.persistence 或 org.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...