✂️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>

✨ 优势与收益

  1. 清晰的架构边界:编译期强制的依赖方向让"不该依赖的东西依赖不进来"
  2. 高可测试性:Domain 纯 POJO,单测无需 Spring 容器
  3. 技术无关性:替换 ORM / MQ / RPC 框架只影响 infrastructure 与 acl
  4. 高可维护性:业务规则集中在 domain,改动半径可控
  5. 易扩展:新增外部系统接入只需新增 acl 实现

🎉 总结

本文给出了一种基于 SpringBoot 的 DDD 领域框架落地方案,通过严格的分层约束与依赖方向管理,实现了业务逻辑与技术实现的彻底分离。此套架构不仅提升了代码的可维护性与可测试性,更为系统的长期演进打下了坚实基础。实际项目可按业务规模适度调整(如合并 acl 到 infrastructure、或在 domain 下引入子限界上下文),但依赖指向 domain业务规则只在 domain 这两条底线应始终坚持。

posted @ 2026-04-28 20:12  丿似锦  阅读(57)  评论(0)    收藏  举报