DDD - 如何理解Entity与VO

简介: DDD - 如何理解Entity与VO

27cee23cc9134a878c2eacac11c1ff9c.png



概述


为了更好的理解 Entity与VO,我们需要先区分两个概念: 状态 、 标识


状态


购物中的订单状态,相比大家都熟悉哈 , 比如 发货中、 物流派送中、 商品已送达等等 。 一般订单状态都是使用一个字段来表示的,比如status, status不同的值代表不同的状态。


但是这个status就是「订单状态」吗?难不成状态就是一个字段吗?


我们联想一下生活中的例子


  • 商品已经是发货「状态」了
  • 今天「状态」不错


以 今天「状态」不错 为例,如果状态就是一个字段, status=1 状态好 status=0 状态不好 。感觉不是很合理。


我们会发现,「状态」实际上表示的是「目标对象在当前时刻所呈现出的内容」!在系统设计中中通过一个字段来表示状态只是一种简化手段!


「状态」表示的是「当前时刻所呈现出的内容」, 那么说明了「状态」是个快照/瞬态!也就是说,「目标对象」有多个「状态」,「当前状态」只是「目标对象」众多「状态」中的一个!


理解了什么是「状态」以后,我们就可以来初步区分Entity和VO了:


  • Entity在整个生命周期中,有多个「状态」,也就是说「状态」是可变的(至于变不变就看实际情况了)
  • 而VO在整个生命周期中,只有一个「状态」,也就是说「状态」不变


对于VO来说,因为「状态」是不可变的,我们就可以用其「状态」来表示VO!但是对于Entity来说,因为有多个「状态」,且「状态」是可变的,那我们如何来表示呢?


举个例子:假设同一个买家在同一个卖家那里买了两个同样的商品,那两个订单里的信息都是一样的,但是它是两个不同的订单,我们如何区分这两个订单呢?


那就不得不提到 「标识 」了


标识

说到「标识」,最先想到的一般是编程语言中的「引用」或「指针」

Order orderA = new Order("productA",...);
Order orderB = new Order("productA",...);
orderA.setProductName("productB");


orderA和orderB虽然订单信息(状态)都相同,但是这是两个不同的订单 ,. 即使改了orderA的产品名称(状态),依然还是订单A。


看似解决了「区分相同状态的不同Entity」的问题,但是没有解决Entity有多个状态的问题。因为「标识」指向的是目标对象的当前状态。


语言中的这种「标识」就是无法跨系统。比如,在分布式系统中,需要保证两个系统中的对象是同一个对象,这种「隐式标识」是做不到的。


所以「隐式标识」并不能满足我们的需求。我们需要「显示标识」,「显示标识」在现实中很常见:


身份证号码就是每个人的「显示标识」

产品编号就是产品的「显示标识」


比如 Order

public Class Order{
 orderNo // 显示标识
 product
 status
 ...
}


设置订单号以后,无论订单的状态如何变化,只要订单号不变,那么它就是同一个订单。

所以,「标识」是另一个区分Entity和VO的关键点:

  • Entity有标识
  • VO没有标识

注意标识并不一定只是一个字段,可能是多个字段的组合,这需要根据不同的业务逻辑来确定。


Entity 对比 VO


  • Entity是具有多个「状态」的对象,「状态」在其生命周期中可能会改变,通过「标识」来唯一确定这个对象
  • VO只有一个「状态」,且是在创建时就确定的,也就是说VO是不可变的

那么我们如何在系统中识别哪些对象是Entity,哪些对象又是VO呢?


如何识别


一个对象是表示成Entity还是VO,取决于系统的关注点

举个例子:

「商品」在「订单系统」中是个VO,而在「商品管理系统」中是Entity

在「商品管理系统」中,系统需要关注「商品」的「状态」,需要维护是否上架、库存多少、各种属性等信息(多种状态)。就是说在「商品管理系统」中,商品状态是可变的。所以它也有「标识」,即商品ID


「订单系统」并不关心「商品」的「状态」变化,它只关注在创建订单时,这个「商品」的当前「状态」是什么,并且在订单创建完成后,这个「商品」的「状态」就不会再改变了


在「商品管理系统」中,商品可以这样表示:

public class Product {
 id // 商品标识
 name
 desc
 status
 ...
}


而在「订单系统」中,订单是个Entity,商品是个VO,可以这么表示:

public class Order{
 orderNo // 订单标识
 product:Product
 status
 ...
}
public class Product {
 id // 这里不是标识,只是状态
 name
 desc
 status
 ...
}


注意这里的id并不是标识,这里的id实际上退化成了状态的一部分,保留这个id是为了和「商品管理系统」进行交互,通过id从商品管理系统中查询商品。当然还有其它方式,例如保存「商品管理系统」中该商品的历史URL。

相关文章
|
SQL 缓存 Java
殷浩详解DDD系列 第三讲 - Repository模式
# 第三讲 - Repository模式 **写在前面** 这篇文章和上一篇隔了比较久,一方面是工作比较忙,另一方面是在讲Repository之前其实应该先讲Entity(实体)、Aggregate Root(聚合根)、Bounded Context(限界上下文)等概念。但在实际写的过程中,发现单纯讲Entity相关的东西会比较抽象,很难落地。所以本文被推倒重来,从Repository
38056 8
|
消息中间件 SQL 存储
超详细的RabbitMQ入门,看这篇就够了!
RabbitMQ入门,看这篇就够了
219322 69
|
6月前
|
消息中间件 存储 Kafka
一文带你从入门到实战全面掌握RocketMQ核心概念、架构部署、实践应用和高级特性
本文详细介绍了分布式消息中间件RocketMQ的核心概念、部署方式及使用方法。RocketMQ由阿里研发并开源,具有高性能、高可靠性和分布式特性,广泛应用于金融、互联网等领域。文章从环境搭建到消息类型的实战(普通消息、延迟消息、顺序消息和事务消息)进行了全面解析,并对比了三种消费者类型(PushConsumer、SimpleConsumer和PullConsumer)的特点与适用场景。最后总结了使用RocketMQ时的关键注意事项,如Topic和Tag的设计、监控告警的重要性以及性能与可靠性的平衡。通过学习本文,读者可掌握RocketMQ的使用精髓并灵活应用于实际项目中。
4546 9
 一文带你从入门到实战全面掌握RocketMQ核心概念、架构部署、实践应用和高级特性
|
消息中间件 NoSQL Kafka
订单超时取消的11种方式(非常详细清楚)
订单超时取消的11种方式(非常详细清楚)
8154 5
订单超时取消的11种方式(非常详细清楚)
|
12月前
|
Prometheus 监控 Cloud Native
无痛入门Prometheus:一个强大的开源监控和告警系统,如何快速安装和使用?
Prometheus 是一个完全开源的系统监控和告警工具包,受 Google 内部 BorgMon 系统启发,自2012年由前 Google 工程师在 SoundCloud 开发以来,已被众多公司采用。它拥有活跃的开发者和用户社区,现为独立开源项目,并于2016年加入云原生计算基金会(CNCF)。Prometheus 的主要特点包括多维数据模型、灵活的查询语言 PromQL、不依赖分布式存储、通过 HTTP 拉取时间序列数据等。其架构简单且功能强大,支持多种图形和仪表盘展示模式。安装和使用 Prometheus 非常简便,可以通过 Docker 快速部署,并与 Grafana 等可
5917 2
|
Java 编译器 数据安全/隐私保护
Java 重写(Override)与重载(Overload)详解
在 Java 中,重写(Override)和重载(Overload)是两个容易混淆但功能和实现方式明显不同的重要概念。重写是在子类中重新定义父类已有的方法,实现多态;重载是在同一类中定义多个同名但参数不同的方法,提供多种调用方式。重写要求方法签名相同且返回类型一致或为父类子类关系,而重载则关注方法参数的差异。理解两者的区别有助于更好地设计类和方法。
1071 3
|
存储 消息中间件 JSON
DDD基础教程:一文带你读懂DDD分层架构
DDD基础教程:一文带你读懂DDD分层架构
|
负载均衡 监控 应用服务中间件
在Linux中,lvs/nginx/haproxy 优缺点?
在Linux中,lvs/nginx/haproxy 优缺点?
|
机器学习/深度学习 算法 算法框架/工具
e - 一个神奇的存在
本文介绍了数学常数e,即自然对数的底,约等于2.71828,由欧拉命名。e是一个无限不循环小数,可通过级数1 + 1/n!表示。e在数学、物理、工程和计算机科学等领域有广泛应用,尤其在微积分、复利、概率统计和算法分析中扮演关键角色。它是自然界和科学研究中的基本概念。
1263 1