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在《领域驱动设计》中所说: “领域模型不是数据模型,它的作用是描述系统中存在的各种概念及其关系,而不是定义如何存储这些概念。”
热门推荐
草原之王为何容忍鬣狗抢食?揭秘狮子与鬣狗的共生关系
什麼是定妝?定妝4大步驟與技巧手法的完全指南
南方有乔木:一部现代都市爱情小说的深度解析
服役期"军龄"可视同为缴费工龄,养老金标准更高但需注意这3点
大明朝走向灭亡的主要原因之一为何是众正盈朝?
如何影响未来AI技术的心灵哲学解析?哥德尔视角下的智能突破
不同颜色的MBTI性格类型及其特点
玉雕龙龟的来历寓意与作品欣赏
速算集大成的方法:三句话搞定一切速算
科普揭秘:金属光泽背后的原理
门店连锁经营:打造品牌力与扩张力的双赢策略
萌宠乐园互动游戏全攻略,玩转乐园,与宠物共度欢乐时光
图片图像图画的区别是什么
古从新|传承中探索 探索中发展 ——关于“客家话”吟诵的思考
豆腐与中医药的千年情缘
如何细化项目管理颗粒度
都说“春眠不觉晓”,春季如何拥有一个好睡眠?
日本留学指南:从申请到生活全方位解析
铁矿石种类的划分依据是什么?这种依据如何体现矿石特点?
揭秘中国古代四大神龙:神秘与威严的象征
人参果是热性,还是凉性
鱼际穴:人体健康的宝藏穴位
蜀中无大将误导700年!蜀汉后期高级军衔大将达12个,为刘禅护国
如何构建有吸引力的章节:结构、内容、风格与修改的指南?
如何吃鸡蛋最有营养和最健康
如何设计出高质量、有效的题目以评估知识掌握和能力水平?
如何设计出高质量、有效的题目以评估知识掌握和能力水平?
【网络中国节·清明】清明时节,再探《清明奇妙游》的文化密码
黄豆烧鸡爪:软糯入味的家常美味
「𝐌𝐁𝐓𝐈」超人氣測驗!16型人格看看你適合什麼類型的工作!