实体

在 DDD 中有这样一类对象,它们拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至超出软件的生命周期。我们把这样的对象称为实体。

1. 实体的业务形态

  • 领域模型中的实体是多个属性、操作或行为的载体。在事件风暴中,我们可以根据命令、操作或者事件,找出产生这些行为的业务实体对象,进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合。
  • 实体和值对象是组成领域模型的基础单元。

2. 实体的代码形态

  • 在代码模型中,实体的表现形式是实体类,这个类包含了实体的属性方法,通过这些方法实现实体自身的业务逻辑。
  • 在 DDD 里,这些实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现,跨多个实体的领域逻辑则在领域服务中实现。

3. 实体的运行形态

  • 实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。我们可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会大不相同。但是,由于它们拥有相同的 ID,它们依然是同一个实体
  • 比如商品是商品上下文的一个实体,通过唯一的商品 ID 来标识,不管这个商品的数据如何变化,商品的 ID 一直保持不变,它始终是同一个商品。

4. 实体的数据库形态

  • 与传统数据模型设计优先不同,DDD 是先构建领域模型,针对实际业务场景构建实体对象和行为,再将实体对象映射到数据持久化对象。
  • 在领域模型映射到数据模型时,一个实体可能对应 0 个、1 个或者多个数据库持久化对象。
    • 大多数情况下实体与持久化对象是一对一。
    • 在某些场景中,有些实体只是暂驻静态内存的一个运行态实体,它不需要持久化。
    • 在有些复杂场景下,实体与持久化对象则可能是一对多或者多对一的关系。

值对象

《实现领域驱动设计》一书中对值对象的定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。在 DDD 中用来描述领域的特定方面,并且是一个没有标识符的对象,叫作值对象。

  • 值对象描述了领域中的一件东西,这个东西是不可变的,它将不同的相关属性组合成了一个概念整体。
  • 当度量和描述改变时,可以用另外一个值对象予以替换。
  • 它可以和其它值对象进行相等性比较,且不会对协作对象造成副作用。

image.png

我们可以将“省、市、县和街道等属性”拿出来构成一个“地址属性集合”,这个集合就是值对象了。

1. 值对象的业务形态

  • 实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑
  • 而值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立出来了,但在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。

2. 值对象的代码形态

  • 如果值对象是单一属性,则直接定义为实体类的属性;
  • 如果值对象是属性集合,则把它设计为 Class 类,Class 将具有整体概念的多个属性归集到属性集合,这样的值对象没有 ID,会被实体整体引用。

image.png

3. 值对象的运行形态

实体实例化后的 DO 对象的业务属性和业务行为非常丰富,但值对象实例化的对象则相对简单和乏味。除了值对象数据初始化和整体替换的行为外,其它业务行为就很少了。

值对象嵌入到实体的话,有这样两种不同的数据格式:

  • 属性嵌入
  • 序列化大对象

以属性嵌入的方式形成的人员实体对象:

image.png

以序列化大对象的方式形成的人员实体对象:

image.png

4. 值对象的数据库形态

DDD 引入值对象是希望实现从“数据建模为中心”向“领域建模为中心”转变,减少数据库表的数量和表与表之间复杂的依赖关系,尽可能地简化数据库设计,提升数据库性能。

  • 在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;
  • 在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。

5. 值对象的优势和局限

  • 优势是可以简化数据库设计,提升数据库性能。这种设计方式虽然降低了数据库设计的复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象属性值变得异常困难。
  • 值对象采用属性嵌入的方法提升了数据库的性能,但如果实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务涵义,操作起来也不方便。

实体和值对象的关系

image.png

从这个例子中,我们可以看出,同样的对象在不同的场景下,可能会设计出不同的结果。

  • 有些场景中,地址会被某一实体引用,它只承担描述实体的作用,并且它的值只能整体替换,这时候你就可以将地址设计为值对象,比如收货地址。
  • 而在某些业务场景中,地址会被经常修改,地址是作为一个独立对象存在的,这时候它应该设计为实体,比如行政区划中的地址信息维护。