【问题标题】:DDD aggregate design example with a many-to-many relationship具有多对多关系的 DDD 聚合设计示例
【发布时间】:2020-09-03 13:26:26
【问题描述】:

我想通过一个建模练习来尝试更好地理解 DDD,特别是在存在多对多关系的情况下

让我们以 Xbox 用户及其成就为例。

如果你不熟悉这是背景: Xbox 上的每个游戏都有成就。 用户将拥有他们拥有/玩过的游戏列表,他们可以解锁这些游戏的成就。

这里是多对多的关系,一个用户可以有很多游戏,而一个游戏可以有很多用户。

不用多说,这里肯定有两个聚合根。用户和游戏。 游戏聚合根有一个成就列表。

我的困惑来自于跟踪特定游戏的用户进度。 例如他们有哪些游戏,以及他们在每场游戏中取得的成就。

在典型的 CRUD 设计中,它可能看起来像这样:

您现在如何为 DDD 建模?

User Game 和 User Achievement 将包含有关每个进度的信息。

这是我目前认为的解决方案,但是对吗?

用户和游戏都是聚合根。

一个游戏的进度和它的个人成就似乎也有联系,我会说这是它自己的总根。这是因为就像游戏及其成就一样,它可能具有交易边界。当您在游戏成就上取得进展时,这可能会影响游戏的进度(例如,解锁成就会增加游戏玩家获得的总得分。)

我一直听说你想解决多对多关系,这通常没问题,但这里的关系有自己的数据,我不认为这种关系属于任何一个。

您可能会争辩说您会将它放在用户聚合上,但我担心它会增加该用户聚合的权重,并且没有理由让它存在。 我不认为有任何事务边界,我不喜欢当你想加载它时必须加载所有用户的游戏。即使这是延迟加载,我担心它可能会导致并发问题?

这就是为什么我认为这里的多对多关系是有效的,以及为什么新的聚合根可能是合适的。

也就是名字用户游戏进度连对了。了解领域,您有什么建议?

谢谢你,任何帮助都非常感谢。

【问题讨论】:

    标签: many-to-many aggregate domain-driven-design


    【解决方案1】:

    您在这里寻找的是Domain Events。来自微软文档:

    使用领域事件显式实现更改的副作用 在您的域内。换句话说,使用 DDD 术语,使用 域事件以显式地跨多个实现副作用 聚合体。可选地,为了更好的可扩展性和更少的影响 数据库锁,使用内部聚合之间的最终一致性 同一个域。

    “游戏进度”对我来说似乎不是一个总根。要更新“游戏进度”,您可以从 User 聚合中引发域事件,例如“User_Moved”,并从 Game 聚合中引发类似事件。然后,您可以订阅这些事件以更新游戏进度。此外,由于领域事件是在内存中的,因此您可以在单个事务中实现这一点。

    【讨论】:

    • 所以我知道事件。但我不认为他们会解决我的问题。事件将作为对命令的反应出现,并且是一种通知其他聚合体发生了某些事情的方式。我觉得有一点混乱,因为游戏在这里有两个含义。有一种全球意义上的游戏,专门针对用户。游戏聚合根是它自己的东西。它与用户无关。它与用户无关。它的进步。因此,每个用户的进步都是独一无二的。所以从某种意义上说,存在游戏和用户的游戏,如果这有意义的话
    • 所以为了澄清,系统中的事件将引发域事件,但跟踪用户的进度不是游戏的责任。游戏是一个独立的概念。如果游戏要跟踪每个用户的进度,那么您可以拥有 x 多个用户进度记录的列表,这是不可扩展或不合适的,因为没有不变量试图在给定游戏的所有用户进度之间保持一致
    • 如果我理解正确,基本上你可以有一个单独的UserGameProcess 作为域对象,User 作为聚合。在您的 User Aggregate 中,您将拥有诸如 UpdateGameProgress 之类的方法,它可以将 Game Id 作为参数以及相关参数。然后UpdateGameProgress可以调用UserGameProgress中对应的方法来更新游戏进度..希望有意义。
    • 是的,这就是我担心的地方。我看到了两个问题。首先是它会导致一个大的集群聚合。一个用户可以拥有 100 多个游戏。每个游戏也可以有大量的成就(如用户的游戏进度和成就)。即使我们使用延迟加载,我们也希望避免大型集群聚合。第二件事是,用户不需要包含他们的游戏进度,因为没有我们试图保持一致的跨国不变量。它可能是最终的。这就是为什么我认为保留 m-m
    • 我想我明白你的意思,有一个单独的聚合可能会在这里有所帮助。然后,您可以让您的领域事件与这些聚合进行对话。如果这些聚合不需要在同一个事务中提交,我可能会将新消息放入域事件侦听器的队列中,以将游戏进程更新为单独的事务。
    【解决方案2】:

    我认为您不会为您的问题找到比“取决于情况”更具体的权威答案。

    例如 - 假设游戏是国际象棋,我们希望玩家在每种颜色中至少拥有一次成就时奖励一项成就。这不是“游戏”聚合的责任;我们可以从生命周期不对齐的事实立即看出这一点。

    简述这一点的一种快速方法表明,我们可能有游戏引发“领域事件”来宣布获胜,然后一个使用胜利作为输入并宣布奖励的小型状态机,然后是一些列出特定特定奖励的报告玩家编号。

    状态机,以及伴随的接收获胜和发出奖励的域逻辑可能本身就是一个聚合 - 每个玩家都有一个实例。

    报告 - 将这个奖项与同一玩家的其他奖项结合在一起......好吧,除了一些看起来相当贫乏的管道细节,除非你需要做一些聪明的事情来只显示“最近”的奖项,或者类似的东西。

    探讨这些想法的精彩演讲:All Our Aggregates Are Wrong,作者:Mauro Servienti。

    如果您一开始就知道,这个“玩家奖励”聚合可以是它自己的聚合,也可以是玩家下的奖励列表。

    它当然可以是玩家集合中的奖励列表。但是 - 假设上面的分析是正确的 - 奖励列表不会影响,也不会受到玩家的任何其他变化的影响。

    每个玩家都有一个列表(可能为空),并且您希望能够使用玩家 ID 检索特定玩家的列表,但仅这些条件并不会限制我们将列表视为玩家集合的一部分。

    【讨论】:

    • 所以我明白在你的例子中玩家奖励不会出现在游戏中。显然它不属于那里,因为它不是游戏的孩子,它只需要收听游戏事件,就有可能为玩家解锁奖励。这个底层是多对多的。球员奖,但聚合使其成为一对多。在您的情况下,您似乎正在使用聚合来管理奖励列表。如果您一开始就知道,这个“玩家奖励”聚合可以是它自己的聚合,也可以是玩家下的奖励列表。
    猜你喜欢
    • 2021-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-13
    • 2011-06-22
    • 2011-05-03
    • 1970-01-01
    相关资源
    最近更新 更多