领域驱动设计:实体、值对象以及如何区分? - jannikwempe


DDD 可以分为战略设计和战术设计,其中战术设计是关于 DDD 的构建块。这篇文章将介绍 DDD 的基本构建块:实体和值对象 (VO)。实体和 VO 是 DDD 中表达模型的两个构建块。它们是领域驱动设计的起点(除了服务和领域事件)。
  
实体
这就是 Eric Evans 在 DDD 书中介绍实体的方式:
许多对象根本上不是由它们的属性定义的,而是由连续性和同一性的线索定义的。
这句话已经介绍了 Entity 的主要特征:连续性(也常被称为具有生命周期)和身份。
主要由其身份定义的对象称为实体。
当您想到一个实体时,想象一下需要随时间跟踪并且其属性可能会随时间变化的东西。为了能够跟踪某物,您需要一种方法来识别对象并回答“这是同一个对象吗?”的问题。时间过去后。某个事物是否是实体的一个非常强的指标是类似于status属性(如pending、active、 或inactive)或属性前缀(如current或 )last。
即使不同对象的属性相同,对象也必须是可区分的。想想一个应用程序,用于管理在大学注册不同课程的学生Student。如果Student的属性email甚至name改变,还是同一个学生。定义一个学生Student的意义很重要。也许它会只是有一个通用的id. 
您必须回答“是什么使一个对象成为同一个对象?”的问题。从领域的角度来看。因此,定义实体的身份需要了解域。
警惕要求按属性匹配对象的要求。
这意味着“如果属性相同,则对象应该相同”这样一个强有力的指标,可能表明它根本不是一个实体。
实体也称为“引用对象”。我喜欢这个术语,因为我的脑海中有一个图像,其中一个指针(一个箭头)随着时间的推移引用了同一个对象。
 
值对象 (VO)
许多对象没有概念标识。这些对象描述了事物的一些特征。
VO 没有身份标识。它们由它们的属性而不是标识符定义。您可以将 VO 视为实体的复杂属性。
表示域的描述方面而没有概念标识的对象称为值对象。值对象实例化来表示的设计元素,我们只对关心什么他们,而不是谁或哪个他们。
回想一下实体部分中的学生示例。在Student可能有一个Address,其中包括street,streetNumber,postcode,和city。在我们的应用程序域中,地址Address将是一个 VO。更改其中一个属性将使其成为不同的地址。地址更像是实体的复杂属性类型。
不关心对象的标识使我们可以自由地简化设计并优化性能。我们现在可以共享 VO 的实例,因为它们本质上就像一个包含一些信息的复杂属性。但是为了做到这一点,VO 必须是不可变的。更改 VO 属性必须导致创建一个新实例,而不是修改现有实例。
再次回到学生的例子。想想住在同一个地址的两个学生。他们可以共享同一个AddressVO实例,但是如果其中一个学生移动到其他地方,我们不想改变该实例,因为这也会影响另一个学生。相反,我们最终创建了一个新Address实例(或者重用该地址的现有实例,如果它存在的话)。
共享 VO 实例如下图所示。它还展示了 VO 就像一个复杂的属性,因为在这种情况下它最终在数据库中的同一行中。

不要将人工 ID 附加到 VO。VO 也不在数据库中它们自己的表中表示。如果你这样做,你不仅最终会失去 VO 的性能优势和降低复杂性,而且还会混淆模型,迫使所有对象进入同一个模型。(banq注:如果ID是数据库主键,也可以作为值对象的值或属性放入值对象,不能过于教条)
由于 VO 的上述好处,您应该将 VO 作为默认值,并且仅在需要时才将身份分配给某物(从而使其成为实体)。
(在 JPA 中,注释@Embeddable(1:1) 和@ElementCollection(1:n) 用于 VO)。
 
区分实体和值对象
这篇文章重点介绍实体和 VO,因为它们相似但不同。我经常问自己一个问题:“这是实体还是值对象?”。两者都对实际数据进行建模,并且最常保存在数据库中。但正如我们已经了解到的,实体和 VO 之间存在差异。
下表展示了实体和值对象之间的主要区别:

  • 比较:实体是使用标识ID来比较指定;值对象使用结构比较,结构属性的值如果相同则是同一个。
  • 可变性:实体是可变的,值对象不可变。
  • 生命周期:实体有,值对象无。

以下问题有助于识别实体或 VO:
  • 我可以用另一个具有相同属性的对象替换该对象吗?

如果答案是肯定的,则它是一个值对象。如前所述,VO 就像一种复杂的数据类型。您也可以将原始数据类型视为整数作为 VO。那么,你的对象在某种程度上像一个整数吗?
  • 随着时间的推移,我是否必须跟踪某些事情?

如果答案是,它是一个实体。允许随时间跟踪信息的实体。而 VO 就像一个时间点的快照。
如果某物是实体或 VO 取决于您的域。它可以是一个域中的一个实体,另一个域中的一个 VO。因此,像“地址是值对象”这样的语句。简直是错误的。一个例子可能是金钱。在电子商务中,申请资金就像 VO,因为它只代表购买东西的能力。100 美元就是 100 美元。但是在处理银行抢劫案时,您可能会关心特定的钞票及其唯一标识它的序列号。
 
结论
实体和值对象是 DDD 的几个构建块中的两个,它们属于 DDD 的战术设计部分。它们都具有存储信息的目的,并且大多数情况下它们最终会出现在数据库中,但存在差异,我指出了这一点。值对象应该是您的首选,因为它们不那么复杂并且可以提供更好的性能。如果您需要身份的概念,请使用实体。