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在《领域驱动设计》中所说: “领域模型不是数据模型,它的作用是描述系统中存在的各种概念及其关系,而不是定义如何存储这些概念。”
热门推荐
劳动合同到期,员工不续签,该如何处理
明朝时期,为何全世界都不敢惹明朝,大明海军究竟有多强大?
优化 Windows 11 设置以增强视频播放性能
书香致远 文心筑梦——各地图书馆阅读推广活动丰富多彩
《秘密潜入》第一关和第二关详细攻略
炎炎夏日,你给眼睛做好防晒了吗?这份太阳镜挑选攻略请收好
斗罗大陆觉醒武魂素材图,探索奇幻世界的力量之源
关爱地贫患者,我们一直行动「手」护地贫「红」线系列主题活动在惠州
Excel文件大小不一样的原因及优化方法
明治维新为什么会成功?这场改革如何重塑日本历史?
牧野之战:中国历史上以少胜多的经典战例
如何在未经运营商许可的情况下解锁你的手机
“躯体化症状”上热搜!不舒服又查不出?有6种常见表现
函授大专,你们觉得可以吗?
光子的静质量为什么必须是零?
高速公路限速标准更改,一刀切变为灵活调整,龟速行车也会受处罚
什么是搜索引擎?有什么作用?
蔡崇信最新万字谈阿里巴巴及个人经历
感觉对方不开心怎么办?
广州海底捞毁三观的“3个孕妇白嫖”事件:新型上瘾,正在毁掉你
瓦房店自驾游攻略:一日游路线及详细指南
如何保护自己的隐私权不被侵犯
食品脱氧剂误食后的处理方法
探寻东坡肉:解锁杭帮美食瑰宝,品味千年东坡韵味与醇厚肉香
中国海军为何选择055大驱与052D并行发展?
萝卜丝炒粉条:一道美味可口的下饭菜
车载导航一体机应该如何进行升级?升级过程中需要注意哪些问题?
中医临床思维虚拟仿真系统:提升未来中医临床执业医师队伍综合实力
怎么批量制作家庭成员excel表
院士直言:中药不能根治癌症,咱不能吹牛!中医治疗癌症靠谱吗?