【问题标题】:Inter-aggregate references must use primary keys?聚合间引用必须使用主键?
【发布时间】:2021-06-26 18:48:45
【问题描述】:

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

例如,它基本上意味着一个类Book 可能只有getOwnerUserId() 而不应该有getOwnerUser()

然而,在 Eric Evans 的Domain-Driven Design 中,它明确表示:

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

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

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

【问题讨论】:

    标签: domain-driven-design aggregateroot


    【解决方案1】:

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

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

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

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

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

      • 感谢您的回答。但是,我不同意“聚合根是 DDD 表示主键的方式”。在“领域驱动设计”一书中,很多实体经常被描述为“聚合根”。例如,书中的一句话说“Cargo 也是一个明显的 AGGREGATE 根”。将其解释为“Cargo 也是一个明显的主键”听起来并不正确,因为“Cargo”是整个类,而不仅仅是标识。
      • 整个类可以是聚合根。但是聚合根必须是聚合的一个成员,参见 Evans 书中“聚合”的定义:“外部引用(对聚合)仅限于聚合的一个成员,指定为根”
      • 在“Cargo 也是一个明显的 AGGREGATE 根”之前的段落中,有“但是在某些时候,我们可能希望删除该集合以支持使用 Cargo 进行数据库查找 i> 作为关键”。随后的图 7.3 也将 Cargo 定义为全局唯一标识符。
      • 让我在这里粘贴 DDD 书中聚合的完整定义:“AGGREGATE,关联对象的集群,为了数据更改而被视为一个单元。外部引用仅限于一个成员在 AGGREGATE 中,指定为“根”,一组一致性规则适用于 AGGREGATE 的边界。”
      • 是否可以说“聚合根”这个词有时指的是整个类,有时只指主键?
      【解决方案3】:

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

      “主键”是 RDBMS 特有的,所以 identity 会更合适。

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

      可以,但一般不应该

      为什么要通过身份引用?

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

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

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

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

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

      为什么要有直接引用?

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

      不过,这也是 CQRS 近来如此受欢迎的众多原因之一。如果您完全绕过查询的域模型(例如纯 SQL),那么您不必在您的域中为查询需求做出让步。

      参考文献

      这是一本由 Vaugh Vernon 撰写的 sample from the IDDD 书,其中他谈到了 Evans 的那句话。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多