DDD领域模型在项目中的实战

一、概念理解

Domain(领域对象 ):DDD 的核心,承载业务逻辑和领域规则,是业务的 "灵魂",仅存在于领域层;
PO(持久化对象) :与数据库表一一映射,是数据持久化的载体,仅存在于基础设施层(持久化层);
DO(领域对象,狭义) :实际开发中常作为Domain 的简写 / 落地形式,等同于聚合根、实体、值对象,聚焦领域逻辑;
DTO(数据传输对象):DTO 的唯一合法流转范围是「接口层↔应用层」,这也是它的核心设计初衷:作为外部系统(前端 / 第三方服务)与应用内部的 "数据隔离层",屏蔽外部数据格式变化对内部层级的影响。DTO 仅包含外部交互的必要字段,无任何业务逻辑,入参 DTO 做参数校验(如 @NotBlank/@NotNull),出参 DTO 做数据裁剪 / 脱敏 / 格式适配(如枚举转中文、多 DO 字段合并)

二、实际项目上看完整的 RESTful 请求对象流转流程

1. 接口层:接收请求(DTO 入参)

java 复制代码
// 接收前端传来的 RequestDTO(数据传输对象)
@PostMapping("/test")
public BaseResponseDTO<List<ListedTestProduct>> getTestData(@RequestBody TestRequestDTO query) {
    BaseResponseDTO<List<ListedTestProduct>> resp = new BaseResponseDTO<>();
    return resp.setResults(testService.syncTestInfo(query));
}

输入对象:TestRequestDTO(数据传输对象,用于接口层和应用层之间传参)

职责:仅作为数据容器,不包含任何业务逻辑。
2. 应用层:接收请求并转换为领域对象(DO)

java 复制代码
public List<ListedTestProduct> syncTestInfo(TestRequestDTO request) {
    return assembler.assemble2ListedTestProduct(
        assembler.assemble(request, TestInfoDO.class).getTestDatas()
    );
}

转换动作:assembler.assemble(request, TestInfoDO.class)

底层逻辑:在IAssembler接口中,通过BeanUtils.copyProperties将TestRequestDTO的属性复制到TestInfoDO(领域对象)。

java 复制代码
default D assemble(Q req, Class<D> dClass) {
    D d = DomainFactory.create(dClass);
    BeanUtils.copyProperties(req, d);
    return d;
}

3. 领域层:执行业务逻辑并返回领域对象

java 复制代码
// TestInfoDO.getTestDatas() 方法
public List<TestInfoDO> getTestDatas() {
    List<TestInfoDO> tests = testConvertor.convert(
        testRepository.selectLatestTestData(condition(this))
    );
    // 业务逻辑
    }
    return tests ;
}

核心职责:TestInfoDO(领域对象)封装了业务逻辑,负责数据过滤和计算。

流转结果:返回处理后的List。

4. 应用层:领域对象转换为出参对象

java 复制代码
// assembler.assemble2ListedTestProduct 方法
public List<ListedProduct> assemble2ListedTestProduct(List<TestInfoDO> tests) {
    List<ListedProduct> pList = Lists.newLinkedList();
    tests.forEach(test-> {
        // 业务逻辑映射
        pList.add(...);
    });
    return pList;
}

转换动作:将领域对象TestInfoDO转换为给前端的出参对象ListedProduct(可以看作是一种特殊的 DTO)。

  1. 接口层:封装响应并返回
java 复制代码
// 封装为 BaseResponseDTO 返回给前端
return resp.setResults(testService.syncInfo(query));

输出对象:BaseResponseDTO<List>(统一响应格式,包含业务结果和元信息)。

三、完整流转链路总结:

请求正向流转(接口层→基础设施层)+ 响应反向流转(基础设施层→接口层)

plaintext 复制代码
外部系统(前端/第三方)
  ↓↑ (JSON/HTTP协议)
接口层:RequestDTO ↔ ResponseDTO (仅此处用DTO)
  ↓↑ (Assembler转换器转换)
应用层:RequestDTO → 领域对象DO / 领域对象DO → ResponseDTO
  ↓↑ (直接传递/领域层内部处理,无转换)
领域层:领域对象DO(聚合根/实体/值对象)
  ↓↑ (Convertor转换器转换)
基础设施层:领域对象DO → PO / PO → 领域对象DO
  ↓↑ (MyBatis映射,直接交互)
数据库/缓存(MySQL/Redis)

各层对象职责明确: DTO(跨外部传输)、DO(承载业务逻辑)、PO(数据库持久化)三者各司其职,不交叉、不替代;
转换动作统一管控: 接口层↔应用层的转换由Assembler负责,领域层↔基础设施层的 DO/PO 互转由Convertor负责(如MapStruct 自动生成的 Convertor),转换逻辑集中管理,不散落 在业务代码中。

四、DDD 对象转换的核心规范

结合你的项目和 DDD 最佳实践,我帮你整理了一套可以直接落地的对象转换规范:

  1. 分层隔离原则
    禁止跨层传递对象:接口层不能直接接收领域对象,领域层也不能返回 DTO。
    层间转换必须通过转换器:所有对象转换都要通过专门的Assembler或Converter来完成,避免在业务代码中写硬编码的属性映射。
    示例:项目中TestAssembler就是很好的实践,它统一了DTO ↔ DO ↔ 出参对象的转换逻辑。
  2. 转换器职责单一原则
    一个转换器负责一类对象的转换:比如TestAssembler只负责和TestInfoDO相关的转换。
    转换逻辑集中管理:避免在Service或Controller中零散地写BeanUtils.copyProperties。
  3. 领域对象纯粹性原则
    领域对象只承载业务逻辑:不能包含任何和数据传输、持久化相关的代码。
    禁止在领域对象中直接依赖 DTO/PO:项目中TestInfoDO只依赖领域层的TestRepository。
  4. DTO 设计原则
    入参 DTO(RequestDTO):仅包含接口需要的字段,避免冗余。比如TestRequestDTO只定义了查询所需的参数。
    出参 DTO(ResponseDTO):仅返回前端需要的字段,避免数据泄露。比如ListedTestProduct只包含前端展示的字段。
    统一响应包装:所有接口返回都用BaseResponseDTO统一包装,包含状态码、消息和结果。
  5. 转换工具选择原则
    简单映射用BeanUtils:对于属性名基本一致的简单转换,可以用BeanUtils.copyProperties。
    复杂映射手动实现:对于需要类型转换、字段拼接的复杂场景,必须手动写转换逻辑。
    当进行对象之间的转换时, 也可以用MapStruct(下述有简单介绍)。

五、MapStruct

MapStruct 是一款基于注解的 Java 类型安全映射工具,通过在编译期自动生成映射实现类,替代手动编写繁琐的 Bean 属性拷贝代码。MapStruct 是编译期工具,而非运行时框架,编译时(mvn compile/IDE 编译),处理器会扫描带 @Mapper 注解的接口,自动生成具体的实现类(如 UserMapperImpl);

用法:

(1)依赖

xml 复制代码
<!-- MapStruct 核心注解 -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version> <!-- 稳定版,兼容大部分场景 -->
</dependency>
相关推荐
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-整体架构优化设计方案(续)
java·数据库·人工智能·spring boot·架构·领域驱动
勤奋的小王同学~2 小时前
SpringMVC
java·spring·mvc
牙牙要健康2 小时前
【open3d】Windows 下编译 Open3D C++ 源码完整教程
开发语言·c++·windows
笨蛋不要掉眼泪2 小时前
RAG知识库核心API架构全解析:从文档加载到向量检索的完整流程
java·spring boot·redis·ai·架构
不染尘.2 小时前
二叉树相关题目
开发语言·数据结构·c++·算法
女王大人万岁2 小时前
Go标准库 sync 详解
服务器·开发语言·后端·golang
qq_411262422 小时前
短时间串口发送网络端怎么接收不到
开发语言·php
静谧空间2 小时前
java登录验证码CaptchaConfig
java·开发语言
Imxyk2 小时前
力扣:632. 最小区间(贪心)
java·数据结构·算法