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在《领域驱动设计》中所说: “领域模型不是数据模型,它的作用是描述系统中存在的各种概念及其关系,而不是定义如何存储这些概念。”
热门推荐
期货交易中废单产生的原因及其避免方法
如何确定软件开发目标
增速陕西第一,咸阳千亿级产业跃迁
使用电信流量卡有哪些注意事项?
《哪吒2》要出国,“急急如律令”怎么翻译?网友吵翻,DeepSeek说
核爆下的毁灭与重生:二战期间美国对日本的核攻击
代购行为可能涉及的法律风险
开放式布局和封闭式布局优缺点 开放式空间布局的特点
涨知识!PB、PE、PVC、PERT、PPR管:这次总算分清了
2025年牛肉价格或呈温和偏强态势
2024年7月餐饮供应链月报:牛肉价格创五年新低
如何在各种平台上有效添加着重号以提升写作吸引力
东西方信仰什么神?透过神话“内核”,解读中西方的文化差异
如何提高团队人员专业性
光到底是什么?当关闭手电筒光源后,光线消失还是继续传播?
工厂上班受伤怎么赔偿?一文详解工伤赔偿标准与流程
蓝光效果(探索蓝光效果对视力健康的影响及防护措施)
主板怎么选择和cpu搭配
电源配什么好多大?一文详解电源选购指南
《百家姓》:探究赵姓的首位之谜
腕式血压计最新标准的注册指导,检测与临床验证指南
什么是客户经理管理岗位
50个有趣的脑筋急转弯,看看你能答对几个?
西樵旅游攻略:探秘岭南名山的自然与人文风光
路由器附近最好别放这3样东西,可能影响网速,幸好问了宽带师傅
泥煤威士忌全面解析
从零开始掌握海钓与淡水钓鱼技巧,装备选择、时间地点全攻略
郁金香花语:从爱情到友谊,每一朵都有独特寓意
喝酒后胃不适的饮食方面注意事项有哪些
唐代科举制的历史影响是什么 促进了什么的流动