✂️DDD领域设计

📖 引言
在微服务架构盛行的今天,如何设计出清晰、可维护、可扩展的系统架构是每个开发者都需要思考的问题。领域驱动设计(DDD)提供了一套完整的理论体系,而 SpringBoot 则是落地这一理念的优秀技术载体。本文将详细介绍如何基于 SpringBoot 构建符合 DDD 原则的领域框架,并给出一套可直接参考的多模块工程模板。
🏗️ 架构总览
分层示意
框架采用五层划分,以 bootstrap 作为启动装配入口:

模块职责速览
| 模块 | 职责 | 禁止事项 |
|---|---|---|
| api | 对外提供的接口定义(Dubbo/Feign) | 任何业务逻辑实现;依赖其他模块;使用枚举 |
| domain | 应用核心领域:实体、聚合根、值对象、命令、查询、领域服务、领域事件、仓储接口、防腐抽象 | 依赖任何技术框架 |
| app | 实现 api 层接口,协调领域对象与中间件;MQ Consumer / Task / Dubbo 接口实现 / 参数校验 / 权限校验 / 数据转换 | 核心业务规则;依赖基础设施与防腐层的实现(只依赖 domain 抽象) |
| infrastructure | 实现 domain 的仓储接口,数据持久化(DB / Redis / ES / MQ 等) | 业务逻辑 |
| acl | 防腐层,实现 domain 中的防腐抽象,是与其他服务交互的唯一出口 | 将下游 DTO 透传至 domain |
| bootstrap | 程序启动入口,技术组件装配与服务配置 | 包含业务逻辑 |
依赖方向
| 模块 | 依赖 |
|---|---|
| api | 不依赖任何内部模块 |
| domain | 不依赖任何内部模块 |
| app | api、domain |
| infrastructure | domain |
| acl | domain |
| bootstrap | api、app、domain、infrastructure、acl |
依赖始终指向 domain:确保领域核心稳定、可测试、技术无关。APP 层只依赖 domain 的抽象;infrastructure 与 acl 的实现在运行时由 bootstrap 通过 Spring 容器注入,符合依赖倒置原则(DIP)。
🎯 核心设计原则
在进入代码之前,先明确支撑本框架的四条原则:
- 依赖倒置(DIP):Domain 定义接口,Infrastructure / ACL 提供实现;高层策略稳定,低层细节可替换。
- 单一职责(SRP):每个模块的职责清晰可描述,边界靠编译期依赖强制。
- 开闭原则(OCP):通过接口抽象支持多实现,新需求以扩展类落地,而非修改核心。
- 领域驱动(DDD):以领域模型为中心,聚合根维护业务一致性;通过领域事件解耦跨聚合、跨服务的协作。
两条底线始终不可妥协:依赖方向指向 domain;业务规则只写在 domain。
📂 工程结构
进入细节前,先给出一张完整的工程地图。后续代码片段都可在此结构中定位:
ddd-springboot-project/
├── pom.xml
├── api/
│ └── src/main/java/com/example/api/
│ ├── UserApi.java
│ ├── Result.java
│ ├── dto/
│ │ ├── UserDTO.java
│ │ ├── UserCreateCommand.java
│ │ └── UserUpdateCommand.java
│ └── query/
│ └── UserQuery.java
├── domain/
│ └── src/main/java/com/example/domain/
│ ├── model/
│ │ ├── User.java
│ │ ├── UserId.java
│ │ ├── UserName.java
│ │ ├── Email.java
│ │ └── Password.java
│ ├── service/
│ │ └── UserDomainService.java
│ ├── repository/
│ │ └── UserRepository.java
│ ├── event/
│ │ ├── UserCreatedEvent.java
│ │ └── DomainEventPublisher.java
│ └── acl/
│ └── ExternalAuthService.java
├── app/
│ └── src/main/java/com/example/app/
│ ├── UserAppServiceImpl.java
│ ├── assembler/
│ │ └── UserAssembler.java
│ └── mq/
│ └── UserMessageConsumer.java
├── infrastructure/
│ └── src/main/java/com/example/infrastructure/
│ ├── persistence/
│ │ ├── UserRepositoryImpl.java
│ │ ├── mapper/UserMapper.java
│ │ ├── po/UserPO.java
│ │ └── converter/UserConverter.java
│ └── mq/
│ └── RocketMQConfig.java
├── acl/
│ └── src/main/java/com/example/acl/
│ ├── ExternalAuthServiceImpl.java
│ ├── client/AuthClient.java
│ ├── dto/
│ │ ├── AuthRequest.java
│ │ ├── AuthResponse.java
│ │ └── UserDetailResponse.java
│ └── assembler/AuthAssembler.java
└── bootstrap/
└── src/main/
├── java/com/example/bootstrap/
│ ├── Application.java
│ ├── config/
│ │ ├── DomainConfig.java
│ │ ├── DatabaseConfig.java
│ │ ├── WebConfig.java
│ │ └── MQConfig.java
│ └── controller/
│ └── UserController.java
└── resources/
├── application.yml
└── logback-spring.xml
🧩 模块详细设计
以下按依赖方向自底向上展开:先讲被依赖的底层模块(api、domain),再讲依赖它们的上层模块(app、infrastructure、acl),最后以 bootstrap 完成装配。
1. API 模块 —— 纯净的接口契约
职责:定义对外暴露的服务契约(Dubbo、Feign、HTTP)。
约束:
- 禁止任何接口逻辑实现
- 禁止依赖其他内部模块,第三方依赖也应尽量收敛
- 禁止使用枚举(原因:Dubbo / Feign 反序列化时,消费方若与生产方枚举不一致会直接抛异常,兼容性风险高;推荐用字符串 + 字典常量替代)
- 不引入
ResponseEntity等 Web 技术相关类型,改用自定义Result<T>
// Result.java —— 通用返回包装
public class Result<T> implements Serializable {
private boolean success;
private String code;
private String message;
private T data;
// getter/setter 省略
}
// UserApi.java
public interface UserApi {
/** 创建用户 */
Result<UserDTO> createUser(UserCreateCommand command);
/** 查询用户详情 */
Result<UserDTO> getUserDetail(String userId);
/** 更新用户信息 */
Result<Void> updateUser(UserUpdateCommand command);
}
// DTO / Command 定义
public class UserDTO implements Serializable {
private String userId;
private String userName;
private String email;
private String status; // 使用字符串而非枚举
// getter/setter 省略
}
public class UserCreateCommand implements Serializable {
private String userName;
private String email;
private String password;
// getter/setter 省略
}
public class UserUpdateCommand implements Serializable {
private String userId;
private String userName;
private String email;
// getter/setter 省略
}
2. Domain 模块 —— 核心领域层
职责:承载所有业务规则,是应用的"价值核心"。
约束:不依赖任何外部框架与技术实现,确保可被纯 JVM 单元测试覆盖。
// 聚合根
public class User {
private UserId userId;
private UserName userName;
private Email email;
private Password password;
private UserStatus status;
private LocalDateTime createTime;
public User(UserId userId, UserName userName, Email email,
Password password, UserStatus status, LocalDateTime createTime) {
this.userId = userId;
this.userName = userName;
this.email = email;
this.password = password;
this.status = status;
this.createTime = createTime;
}
public void changePassword(String oldPassword, String newPassword) {
if (!this.password.matches(oldPassword)) {
throw new DomainException("原密码错误");
}
this.password = Password.create(newPassword);
}
public void activate() {
if (this.status != UserStatus.INACTIVE) {
throw new DomainException("只有未激活的用户才能激活");
}
this.status = UserStatus.ACTIVE;
}
// getter 省略
}
// 领域服务
public class UserDomainService {
private final UserRepository userRepository;
public UserDomainService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User createUser(String userName, String email, String password) {
if (userRepository.existsByUserName(userName)) {
throw new DomainException("用户名已存在");
}
if (userRepository.existsByEmail(email)) {
throw new DomainException("邮箱已存在");
}
return new User(
UserId.nextId(),
UserName.of(userName),
Email.of(email),
Password.create(password),
UserStatus.INACTIVE,
LocalDateTime.now()
);
}
}
// 仓储接口(由 infrastructure 实现)
public interface UserRepository {
User findById(UserId userId);
void save(User user);
boolean existsByUserName(String userName);
boolean existsByEmail(String email);
}
// 防腐抽象接口(由 acl 实现)
public interface ExternalAuthService {
boolean validateToken(String token);
UserInfo getUserInfo(String token);
}
3. APP 模块 —— 应用服务层
职责:实现 api 层接口,承担参数校验、权限校验、事务边界、流程编排与数据转换;MQ Consumer、定时任务等触发入口也位于此层。
约束:
- 不写核心业务规则(业务规则属于 domain)
- 只依赖 domain 与 api,不依赖基础设施与防腐层的实现类
// UserAppServiceImpl.java
@Service
public class UserAppServiceImpl implements UserApi {
private final UserDomainService userDomainService;
private final UserRepository userRepository;
private final UserAssembler userAssembler;
private final DomainEventPublisher eventPublisher;
public UserAppServiceImpl(UserDomainService userDomainService,
UserRepository userRepository,
UserAssembler userAssembler,
DomainEventPublisher eventPublisher) {
this.userDomainService = userDomainService;
this.userRepository = userRepository;
this.userAssembler = userAssembler;
this.eventPublisher = eventPublisher;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<UserDTO> createUser(UserCreateCommand command) {
// 1. 参数校验
ValidationUtils.validate(command);
// 2. 调用领域服务创建聚合根(业务规则在 domain 内)
User user = userDomainService.createUser(
command.getUserName(),
command.getEmail(),
command.getPassword()
);
// 3. 持久化聚合根
userRepository.save(user);
// 4. 发布领域事件
eventPublisher.publish(new UserCreatedEvent(user.getUserId()));
// 5. 转换并返回 DTO
return Result.ok(userAssembler.toDTO(user));
}
}
@Transactional放在方法级而非类级,可以精确控制事务边界,避免只读方法也被纳入事务。
4. Infrastructure 模块 —— 基础设施层
职责:实现 domain 定义的仓储接口,完成 DB / Redis / ES / MQ 等技术集成。
约束:不写业务规则;所有对外类型通过 Converter 转换为 domain 对象,不让 PO 泄漏到 domain。
// 仓储实现
@Repository
public class UserRepositoryImpl implements UserRepository {
private final UserMapper userMapper;
private final UserConverter userConverter;
public UserRepositoryImpl(UserMapper userMapper, UserConverter userConverter) {
this.userMapper = userMapper;
this.userConverter = userConverter;
}
@Override
public User findById(UserId userId) {
UserPO userPO = userMapper.selectById(userId.getValue());
return userConverter.toDomain(userPO);
}
@Override
public void save(User user) {
UserPO userPO = userConverter.toPO(user);
if (userMapper.existsById(user.getUserId().getValue())) {
userMapper.updateById(userPO);
} else {
userMapper.insert(userPO);
}
}
@Override
public boolean existsByUserName(String userName) {
return userMapper.existsByUserName(userName);
}
@Override
public boolean existsByEmail(String email) {
return userMapper.existsByEmail(email);
}
}
// MyBatis Mapper
@Mapper
public interface UserMapper {
UserPO selectById(String userId);
int insert(UserPO userPO);
int updateById(UserPO userPO);
boolean existsById(String userId);
boolean existsByUserName(String userName);
boolean existsByEmail(String email);
}
5. ACL 模块 —— 防腐层
职责:实现 domain 中定义的防腐抽象接口,屏蔽外部系统的协议差异与模型差异。
约束:
- 下游的 DTO / Response 不得透传到 domain,必须经 Assembler 转换
- 网络异常需捕获并转换为领域可识别的业务异常
// 防腐层实现
@Component
public class ExternalAuthServiceImpl implements ExternalAuthService {
private final AuthClient authClient;
private final AuthAssembler authAssembler;
public ExternalAuthServiceImpl(AuthClient authClient, AuthAssembler authAssembler) {
this.authClient = authClient;
this.authAssembler = authAssembler;
}
@Override
public boolean validateToken(String token) {
try {
AuthResponse response = authClient.validateToken(token);
return response != null && AuthResponse.CODE_SUCCESS.equals(response.getCode());
} catch (RestClientException e) {
throw new ExternalServiceException("认证服务调用失败", e);
}
}
@Override
public UserInfo getUserInfo(String token) {
try {
UserDetailResponse response = authClient.getUserDetail(token);
return authAssembler.toUserInfo(response);
} catch (RestClientException e) {
throw new ExternalServiceException("用户信息服务调用失败", e);
}
}
}
// 外部服务客户端
@Component
public class AuthClient {
private final RestTemplate restTemplate;
public AuthClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public AuthResponse validateToken(String token) {
return restTemplate.postForObject(
"http://auth-service/validate",
new AuthRequest(token),
AuthResponse.class
);
}
public UserDetailResponse getUserDetail(String token) {
return restTemplate.getForObject(
"http://auth-service/user?token=" + token,
UserDetailResponse.class
);
}
}
6. Bootstrap 模块 —— 应用启动入口
职责:程序启动、技术组件装配、全局配置(Web / MQ / 数据源 / 缓存 / 监控等)。
约束:不包含任何业务逻辑。
// Application.java
@SpringBootApplication
@ComponentScan(basePackages = {
"com.example.bootstrap",
"com.example.app",
"com.example.infrastructure",
"com.example.acl"
// 注意:domain 包不参与 Spring 扫描,保持技术无关。
// domain 中需要作为 Bean 使用的领域服务,由 bootstrap 或 app 显式 @Bean 装配。
})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 示例:领域服务装配
@Configuration
public class DomainConfig {
@Bean
public UserDomainService userDomainService(UserRepository userRepository) {
return new UserDomainService(userRepository);
}
}
// 示例:数据源配置
@Configuration
public class DatabaseConfig {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
📦 Maven 多模块配置
<!-- 父 POM -->
<project>
<modules>
<module>api</module>
<module>domain</module>
<module>app</module>
<module>infrastructure</module>
<module>acl</module>
<module>bootstrap</module>
</modules>
</project>
<!-- API 模块:零内部依赖 -->
<project>
<artifactId>api</artifactId>
<dependencies/>
</project>
<!-- Domain 模块:零内部依赖,保持技术无关 -->
<project>
<artifactId>domain</artifactId>
<dependencies/>
</project>
<!-- APP 模块 -->
<project>
<artifactId>app</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>api</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>domain</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
</dependencies>
</project>
<!-- Infrastructure 模块(以 MyBatis 为例) -->
<project>
<artifactId>infrastructure</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>domain</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
</dependencies>
</project>
<!-- ACL 模块 -->
<project>
<artifactId>acl</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>domain</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
</dependencies>
</project>
<!-- Bootstrap 模块 -->
<project>
<artifactId>bootstrap</artifactId>
<dependencies>
<dependency><groupId>com.example</groupId><artifactId>api</artifactId></dependency>
<dependency><groupId>com.example</groupId><artifactId>app</artifactId></dependency>
<dependency><groupId>com.example</groupId><artifactId>domain</artifactId></dependency>
<dependency><groupId>com.example</groupId><artifactId>infrastructure</artifactId></dependency>
<dependency><groupId>com.example</groupId><artifactId>acl</artifactId></dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
✨ 优势与收益
- 清晰的架构边界:编译期强制的依赖方向让"不该依赖的东西依赖不进来"
- 高可测试性:Domain 纯 POJO,单测无需 Spring 容器
- 技术无关性:替换 ORM / MQ / RPC 框架只影响 infrastructure 与 acl
- 高可维护性:业务规则集中在 domain,改动半径可控
- 易扩展:新增外部系统接入只需新增 acl 实现
🎉 总结
本文给出了一种基于 SpringBoot 的 DDD 领域框架落地方案,通过严格的分层约束与依赖方向管理,实现了业务逻辑与技术实现的彻底分离。此套架构不仅提升了代码的可维护性与可测试性,更为系统的长期演进打下了坚实基础。实际项目可按业务规模适度调整(如合并 acl 到 infrastructure、或在 domain 下引入子限界上下文),但依赖指向 domain 与 业务规则只在 domain 这两条底线应始终坚持。

浙公网安备 33010602011771号