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在《领域驱动设计》中所说: “领域模型不是数据模型,它的作用是描述系统中存在的各种概念及其关系,而不是定义如何存储这些概念。”
热门推荐
独生子女津贴:是否需要扣除个税?
先兆流产你需要了解哪些真相!预防先兆流产,这些准备要做好!
15号钢材质分析,15号钢交货状态,15号钢力学性能
LPR年内第三次调整 将有哪些影响?专家解读
51单片机的室内灯光数据采集和控制
护肤品使用全攻略:从种类功效到保存期限
条形码图片生成与优化指南:教你轻松制作高清条码
岗位胜任力领导测评怎么进行?
【生态遗传学】探究遗传变异对生态适应的影响
陕南地区适合的树种元宝枫
一杯咖啡 一份甜品 一本书:年轻人打开阅读新方式
如何进行高效的医疗器械产品设计?如何满足市场需求与法规要求?
揭秘“教科书级”的高端地下车库
学习八字命理:首先应掌握哪些基础理论
高分子材料智能制造技术专业就业方向与就业岗位有哪些
正常呼吸次数一分钟为多少次
不同年龄段的正常呼吸频率及预防异常方法
Pioneer的深层含义:从西部开拓到科技创新
世界上最古老的10所大学,竟然个个是名校
职称新规来袭!临床工作量成门槛,医生懵了?
当心!这些药物可能引起肝功能异常
长期使用人工甜味剂会有哪些潜在风险?
常吃菠菜,眼睛更亮!
深刻反省自己的句子:名言篇与名人篇
机械键盘中的茶轴、黑轴、红轴和青轴怎么选?有哪些区别?
国家开放大学需准备哪些报名材料?是线上报名吗?
又一座城市文化新地标!“雄安印象”展览今天正式对外开放
润滑脂粘度选择指南:如何为您的设备选择最佳润滑方案
乒乓球比赛规则与英语术语解析
一般离婚同意调解后多久下判决书