聚合间引用必须使用主键吗? 为什么通过身份引用?为什么要直接引用?参考资料

问题描述

当我阅读 Microservice Patterns 时,其中一段说领域驱动设计需要聚合来遵循一些规则。规则之一是“聚合间引用必须使用主键”。

例如,它基本上意味着类 Book 可能只有 getownerUserId() 而不应有 getownerUser()

但是,在 Eric Evans 的 Domain-Driven Design 中,它清楚地说:

AGGREGATE 中的对象可以保存对其他 AGGREGATE 根的引用。

我猜这意味着 Book 可以有 getownerUser()

如果我对这两本书的上述理解是正确的,那么《微服务模式》这本书对聚合的理解是错误的吗?或者是否有“微服务模式”所指的领域驱动设计的一些变体?或者,我错过了什么?

解决方法

两本书用不同的词表达了大致相同的内容。我会添加我的。

一个聚合可以在同一个 bounded context 中保存对其他聚合的引用。此引用是通过标识符进行的。在许多情况下,标识符是主键(关系工件)或文档 ID(例如来自像 MongoDB 这样的文档数据库)。无论如何,在域中,它只是一个“标识符”。

聚合也可以引用另一个有界上下文中的聚合。在这种情况下,引用不仅仅是一个标识符,而是“外部”聚合到当前有界上下文的投影。

想想图书馆系统。一个有界上下文可能是结账系统,另一个可能是关于书籍本身。图书馆读者聚合可以在其聚合中引用书籍;这些引用将是仅包含一些书籍属性的小对象:可能是 ID、书名和作者,但不包含页数、出版商、图书馆位置等。

,

“聚合根”本质上是说“主键”的 DDD 方式(我怀疑不说“主键”的原因是这样做会将更多的基础设施问题带入域中)。

如果 User 是与 Book 分开的聚合,则 Book 只能保存 User 的 ID(假设这是 User 的聚合根),而不是 User

由于 User 类之外的任何内容都只能通过 ID 访问用户,因此命名 getUser()getUserId() 并返回 getUser() 可能更好用户 ID。

,

“聚合间引用必须使用主键”

“主键”非常特定于 RDBMS,因此 identity 会更合适。

“AGGREGATE 中的对象可以保存对其他 AGGREGATE 根的引用。”

可以,但通常不应该

为什么通过身份引用?

聚合根 (AR) 是一个强一致性边界。 AR 保护其不变量(包括通过并发防止违规)的自然方法是以允许其监督/检测每个更改的方式封装其数据。

当您通过对象引用而不是标识来引用其他 AR 时,一致性边界变得模糊,这使得设计更难以推理。

这是一个(相当愚蠢的)例子:

Bad Design Example

我们可以看到,仅仅通过查看 AR 的结构来了解其边界的真正组成部分是不够的,这肯定会导致问题。

此外,您是否知道如果删除 InviteList 是否会删除人员,或者在调用 InviteList 时是否会保留对 save(inviteList) 内的人员所做的更改?您必须检查持久性映射(假设是 ORM)和级联选项才能确定。

为什么要直接引用?

我想说允许直接引用另一个 AR 的主要原因是对从域对象构造的查询保持务实。如果没有这种关系,通常很难查询(例如,查找所有具有名为 InviteList 的受邀者的 "Foo")或构建必须聚合来自多个 AR 的数据的 DTO(例如,具有所有受邀者姓名的 InviteListDto ).

然而,这也是 CQRS 现在变得如此流行的众多原因之一。如果您完全绕过查询的域模型(例如纯 SQL),那么您不必在域中为查询需求做出让步。

参考资料

这是 Vaugh Vernon 的 sample from the IDDD 书,他在书中谈到了 Evans 的那句名言。