DDD - 彻底搞懂 DO 、DTO 、 Entity
创作时间:
作者:
@小白创作中心
DDD - 彻底搞懂 DO 、DTO 、 Entity
引用
CSDN
1.
https://blog.csdn.net/yangshangwei/article/details/145816198
在领域驱动设计(DDD)中,DO(Data Object)、DTO(Data Transfer Object)和Entity(领域实体)是三种不同职责的对象,它们的协作关系直接决定了系统的清晰性和可维护性。本文将深入解析这三种对象的角色定位、职责差异以及它们在系统架构中的协作关系,并指出常见的设计误区,提供最佳实践建议。
引言
在领域驱动设计(DDD)中,DO(Data Object)、DTO(Data Transfer Object)和Entity(领域实体)是三种不同职责的对象,它们的协作关系直接决定了系统的清晰性和可维护性。
1. 角色定义与职责对比
对象类型 | 定位 | 职责 | 生命周期 | 典型特征 |
---|---|---|---|---|
Entity | 领域模型核心 | 封装业务逻辑,维护领域规则,保证聚合根的一致性 | 贯穿领域层 | - 具有唯一标识(ID) - 包含业务方法 |
DO | 数据持久化对象 | 与数据库表结构直接映射,仅关注数据存储格式 | 仅在数据访问层(DAO) | - 纯数据结构 - 无业务逻辑 |
DTO | 跨层数据传输载体 | 在应用层与外部系统(如前端、其他服务)之间传输数据 | 请求/响应过程 | - 扁平化结构 - 无行为 |
2. 协作关系与转换流程
典型分层架构中的交互
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 应用层 │ │ 领域层 │ │ 基础设施层 │
│ (Application) │ │ (Domain) │ │ (Infrastructure)│
├───────────────┤ ├───────────────┤ ├───────────────┤
│ 使用 DTO │◀─────▶│ 操作 Entity │◀─────▶│ 操作 DO │
│ 接收/返回数据 │ │ 执行业务逻辑 │ │ 读写数据库 │
└───────────────┘ └───────────────┘ └───────────────┘
关键转换场景
- DO → Entity(数据持久化 → 领域模型)
- 场景:从数据库加载数据到内存领域对象
- 实现:
public class OrderRepositoryImpl implements OrderRepository {
@Override
public Order findById(OrderId id) {
OrderDO orderDO = jdbcTemplate.query(...); // 查询DO
return OrderConverter.toEntity(orderDO); // 转换为Entity
}
}
- Entity → DTO(领域模型 → 外部接口)
- 场景:向客户端返回订单详情
- 实现:
@RestController
public class OrderController {
@GetMapping("/orders/{id}")
public OrderDTO getOrder(@PathVariable String id) {
Order order = orderService.getOrder(id); // 获取Entity
return OrderAssembler.toDTO(order); // 转换为DTO
}
}
- DTO → Entity(外部请求 → 领域模型)
- 场景:处理用户下单请求
- 实现:
@PostMapping("/orders")
public void createOrder(@RequestBody OrderCreateDTO dto) {
Order order = OrderFactory.fromDTO(dto); // DTO转Entity
orderService.createOrder(order); // 调用领域服务
}
3. 关键差异与设计原则
(1) 业务逻辑归属
- Entity:必须包含领域行为
public class Order {
private OrderStatus status;
// 领域方法:取消订单
public void cancel() {
if (status == OrderStatus.SHIPPED) {
throw new DomainException("已发货订单不可取消");
}
this.status = OrderStatus.CANCELLED;
}
}
- DO/DTO:禁止包含业务逻辑
// 反模式:DTO中出现业务判断
public class OrderDTO {
public boolean isCancellable() { // ❌ 错误!逻辑应属于Entity
return status.equals("CREATED");
}
}
(2) 标识与生命周期
- Entity:通过唯一标识(ID)区分不同实例
public class OrderId {
private String value; // 领域唯一标识
}
- DO:主键与数据库表一致(可能与领域ID不同)
CREATE TABLE orders (
db_id BIGINT PRIMARY KEY, -- 数据库自增ID
order_id VARCHAR(32) -- 领域ID(唯一业务标识)
);
- DTO:通常无标识,仅传输瞬时数据
(3) 数据完整性
- Entity:强一致性,保证聚合根完整性
public class Order {
private List<OrderItem> items;
public void addItem(Product product, int quantity) {
items.add(new OrderItem(product.getId(), quantity));
this.total = calculateTotal(); // 自动维护总金额
}
}
- DTO:可接受部分数据(如分页查询结果)
public class OrderSummaryDTO {
private String orderId;
private BigDecimal totalAmount; // 不包含明细项
}
4. 常见误区与解决方案
误区1:将DO直接暴露为Entity
- 反模式:
// ❌ DO与Entity混用
@Entity
@Table(name = "orders")
public class OrderDO {
@Id
private Long dbId; // 数据库主键
private String status; // 数据库字段名直接暴露
// 无业务方法
}
- 修复方案:
// ✅ 明确分离
public class Order { // 领域Entity
private OrderId id; // 领域标识
private OrderStatus status; // 领域枚举
public void cancel() { ... } // 领域行为
}
public class OrderDO { // 持久化对象
private Long dbId;
private String orderId;
private String status;
}
误区2:DTO包含领域逻辑
- 反模式:
public class OrderDTO {
private List<OrderItemDTO> items;
// ❌ 在DTO中计算金额
public BigDecimal getTotal() {
return items.stream()
.map(item -> item.getPrice().multiply(item.getQuantity()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
- 修复方案:
// ✅ 金额计算由Entity完成
public class Order {
private Money total;
public void calculateTotal() {
this.total = items.stream()
.map(OrderItem::subtotal)
.reduce(Money.ZERO, Money::add);
}
}
5. 最佳实践总结
- 明确分层边界
基础设施层:只处理DO(
OrderDAO
操作
OrderDO
)领域层:只处理Entity(
OrderRepository
返回
Order
)应用层:协调DTO与Entity转换(
OrderService
使用
OrderDTO
与外部交互)
- 使用转换层隔离模型
工厂模式:
OrderFactory.fromDTO(dto)映射工具:MapStruct、ModelMapper
@Mapper
public interface OrderConverter {
Order toEntity(OrderDO orderDO);
OrderDO toDO(Order order);
}
- 领域ID与数据库ID分离
public class Order {
private OrderId id; // 领域标识(如订单号"ORD-2023-1001")
}
public class OrderDO {
private Long dbId; // 数据库自增ID
private String orderId; // 对应领域ID
}
- 通过防腐层隔离外部模型
// 适配外部服务的DTO
public class PaymentGatewayAdapter {
public ExternalPaymentRequest toExternalRequest(Order order) {
// 转换领域对象为第三方要求的DTO格式
}
}
总结
在DDD中,Entity是业务逻辑的载体,DO是技术实现的细节,DTO是跨边界通信的媒介。它们的核心区别在于:
Entity关心"业务是什么"(What)
DO关心"数据怎么存"(How)
DTO关心"数据怎么传"(How)
保持三者界限清晰,才能构建出高内聚、低耦合的领域模型。正如Eric Evans在《领域驱动设计》中所说: “领域模型不是数据模型,它的作用是描述系统中存在的各种概念及其关系,而不是定义如何存储这些概念。”
热门推荐
地理视角看怒江、三江并流到底是哪三条江?
PVC板材是什么材料?大神给你来科普
美国留学生如何在求职市场中脱颖而出
如何免费查看全文数据库
揭示Logo设计背后的含义:探寻品牌标志的深层内涵
2-碘酰基苯甲酸:一种重要的高价碘试剂
如何挑选适合自己车型的汽车防晒罩?
科普 | 等速肌力训练处方如何定制?
关于占空比,这些知识你可能会用的上~
反式脂肪酸的小门道,竟然这么多?
Nmap全面扫描虚拟机:功能、技术选择与效率优化
H2O的循环:认识水在地球生命与气候体系中的重要作用
如何理解金融市场的运行机制?这种运行机制有哪些关键环节?
金融学专业是学什么的?专业详解与就业前景分析
散热风扇是吹风还是吸风?配电柜电气柜机柜散热风扇的原理详解
虚拟世界系列23:现实世界和虚拟世界
3D线激光轮廓测量仪的关键参数——扫描点数
关于诚信的12个小故事,值得收藏!
英雄熟练度系统即将更新,10级后可获得永久头衔
疤痕需要经过多少年的时间才能逐渐淡化?了解淡化疤痕的过程与时间。
基本存款账户
NoSQL ❉ Key-Value数据库
如何通过手机定位查找妻子或丈夫
味道、技术与现代化
面向2030:电信运营商如何找到“新”变现方式与身份?
汽车轴承市场也内卷,头部外资拆分避拖累
大红袍的产地与保护
未来市场趋势深度解析:四大关键因素重塑市场分析格局
焊接贴片电容、电阻、二极管和三极管全总结
居民身份证换证高峰将至,换证指南赶紧收藏