将引用(id)存储到其他聚合时,在 DDD 中使用值对象背后的直觉是什么

What is the intuition behind using value objects in DDD when storing the references (ids) to other Aggregates

如果之前有人问过这个问题(我在快速搜索时找不到类似的问题),让我先道歉。

DDD 模式以定义和隔离聚合为中心,将业务复杂性拆分为更易于管理的块,并且严格禁止聚合与其他聚合保持任何类型的关系。对其他聚合的引用将存储为 id,然后可用于按需获取其他聚合。因此,单个聚合将仅包含其属性、值对象和对其他聚合的引用。

使用一个常见的例子(用户、订单),它看起来是这样的:

public class User {
  private Long id;
  private List<Long> orders;
}

public class Order {
  private Long id;
  private Long userId;
}

但是我看到多个来源对聚合引用使用另一层封装,将它们从 属性 类型(如上例中定义)转换为值对象,如下所示:

public class User {
  private Long id;
  private List<OrderId> orders;
}

public class Order {
  private Long id;
  private UserId userId;
}

我对 DDD 比较陌生,所以我想了解在使用非复合 ID 时这样做的好处。 乍一看,我看到了很多非常明显的缺点(或者它们在我看来是这样的!),比如常见基类型的数量激增、序列化问题、使用代码和访问存储在这些持有者中的值时的额外复杂性,但是我确信,如果没有一个很好的理由,我不会这样做,因为我忽略了某个地方。

非常欢迎任何评论、想法或反馈!

这是一个域抽象

private UserId userId;

这是一个数据结构

private Long userId;

您的域逻辑(可能)不关心或不需要关心支持 UserId 表示的底层数据结构,或者它们如何存储在数据库中,或者任何这些废话。

广义的术语是“信息隐藏”——围绕决策创建防火墙,以便可以更改决策而不会将该更改级联到系统的其余部分。参见 Parnas 1971


还有一些错误检测的好处。考虑

todays_order.userId + yesterdays_order.userId

这完全是胡说八道的代码;将两个标识符加在一起没有任何用处。但是将 Long 值加在一起在其他情况下是完全正常的事情,编译器不会发现这个错误。

recindOrder(orderId, userId)

你发现这个错误了吗?我的参数顺序错了!当方法签名为

recindOrder(Long userId, Long orderId)

机器无法帮助我发现问题,因为我没有给它提示它需要超越数据结构。


还有一种理论认为,通过提供域值的显式表示,该代码会吸引其他相关函数,否则这些函数可能找不到归宿——实际上,它提高了设计的连贯性。

(根据我的经验,对于像标识符这样的语义不透明类型,它不像货币这样的数字抽象那样真实。但是,如果您有一些 保留 的标识符,那么标识符类型成为记录预订的方便位置。)