DDD - 值对象是实体的一种风格

3

我看过一些实现了领域驱动设计(DDD)的项目,它们使用值对象来代表实体。这些值对象通常被称为EmployeeDetailEmployeeDescriptorEmployeeRecord等。有时会包含实体ID,有时则不包含。

这是一种模式吗?如果是,它有什么名称? 使用案例有哪些? 它们是值对象、参数对象还是其他什么东西? 它们被引用于领域模型中(作为属性),还是像方法的参数和返回值一样“漂浮”在外?

更进一步地...

我想知道是否可以将任何聚合定义为ID + BODY (详情、描述等) + METHODS (行为)

public class Employee {
    private EmployeeID id;
    private EmployeeDetail detail; //the "body"
}

我能否像这样设计我的聚合对象,以避免在使用此类对象时出现代码重复?

这样做的直接优势在于避免在聚合工厂方法中使用太多参数的方法。

public class Employee {
   ...
   public static Employee from(EmployeeID id, EmployeeDetail detail){...};
}

代替
public class Employee {
   ...
   public static Employee from(EmployeeID id, + 10 Value Objects here){...};
}

你认为怎么样?


除了其他特征之外,值对象的意义在于它们应该是不可变的:无论何时更改值对象属性,您都必须替换整个值对象而不是突变它。这是我首先想到的第一个限制。 - Farhan Nasim
3个回答

4
你提出的方案是用 Scala 的惯用方式(通过 case class)来建模聚合:你有一个 ID,指向一个可变容器,其中包含一个不可变的对象图表示状态(以及可能定义状态转换的静态函数)。你正在远离更传统的面向对象设计概念,转而采用更多的函数式编程概念(来到黑暗面... ;) )。
如果这样做,通常你需要对状态进行分区,以便在操作聚合时尽可能少地更改状态的多个分支,从而使尽可能多的先前对象图得以重用。

非常感谢您的回答。那么关于主要问题(在文本中加粗),它们是什么对象?有哪些使用情况? - CelinHC
1
是的,这个“可变容器”既不是实体也不是值对象。我真的不知道在我的领域模型中引入一个“新”的概念会有什么后果。 - CelinHC
我认为从面向对象编程(OOP)转向函数式编程(FP)的后果将是通常的。其中最重要的是,如果您不使用函数式数据结构来建模您的领域,则会有性能惩罚。 - Farhan Nasim

1
我能像这样设计我的聚合根来避免在使用这种对象时出现代码重复吗?
您提出的是将整个实体除了其ID之外表示为“笨重”的值对象。一个概念或对象在您的域中的位置(找到这涉及定义您的有界上下文及其普遍语言)决定了它是作为值对象还是实体进行处理,而不是出于编码方便。
然而,如果您按照您的方案作为一般原则进行,您将冒着将不相关的数据纠缠成单个值对象的风险。这会导致许多概念和技术上的困难。以更新实体为例。实体被设计为根据对其执行的操作而在其生命周期中发展。每个操作仅更新实体的相关属性。使用您的解决方案,对于任何操作,您都必须构造一个新的值对象(因为值对象被定义为不可变),作为替代,可能复制许多不相关的数据。
您引用的示例很可能是只有一个值对象属性的实体。

非常感谢您的回答。那么关于主要问题(在文本中加粗),它们是什么对象?有哪些使用情况? - CelinHC

1

好的-很好的问题...

DDD问题解答

实体对象和值对象之间的区别取决于视角和特定情况下的需求。

让我们来看一个简单的例子...

前往您最喜欢的目的地的飞机航班有...

  1. 1A、10B、21C座位可供您预订(实体)
  2. 22个座位中有3个可用(值对象)。

第一个反映了可以填充的单独可识别的座位实体。 第二个反映出有3个可用的座位(值对象)。

使用值对象时,您不必关心哪些单独的实体(座位)可用-只关心总数。

很容易理解,这取决于谁在问以及它有多重要。

有些航班您会预订一个座位,而其他航班您会预订一个(任何)座位。

一般

问问自己!我关心个别元素还是整体?

NB。实体(飞机)可以考虑座位、身份和/或值对象-具体取决于用例。同样值得注意的是,它有多个依赖项-驾驶舱座位更可能是实体座位;而乘客座位则是值对象。

我非常确定我希望飞行员座位有合格的飞行员和合格的副驾驶员,但我并不在乎乘客座位在哪里。嗯,除了我想确保紧急出口座位适合乘客在紧急情况下帮助离开飞机。

没有简单的答案,但需要考虑一系列复杂的因素,并针对每种情况和领域复杂性进行考虑。

希望这解释了一些问题,愿意回答后续问题...


谢谢@mrdnk。我真的明白有时候你需要在其他BC中使用实体的VO flavor(这很常见),甚至在同一个BC内部(在另一个聚合中-这不太常见)。我认为后缀“descriptor”看起来更像是一种“模式”,而不是试图表达普遍语言。我们知道我们需要通过ID引用另一个AR。现在我想知道当仅通过ID引用不足以满足需求并且您想要复制额外数据以避免往返到其他聚合存储库时,“descriptor”后缀是否被使用。因此,您需要将其放入包装器中。您会如何命名它? - CelinHC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接