根据依赖关系查找聚合 ID

Finding aggregate id based on dependency

我有一个非常混乱的问题,因为我不确定这是否更多是全球设计问题。

我有定义一些聚合的服务,这些聚合发布事件。让我们从服务中选择一个聚合并将其称为 A。 现在,我定义了另一个服务,它有一些应该与 A 一起工作的聚合,让我们调用第二个聚合 B。

当 A 发布一些事件时,我想向 B 发送命令(最好通过 saga),但我很难计算出 B 的适当 ID(A 不知道 B 存在,因此它发布的事件确实存在没有关于如何计算 id 的任何提示)

我能想到几个可能的场景:

第一个是根据 A 的 ID 静态计算 B 的 ID,例如在轴突中我可以做类似 some-id-${suffix} 的事情,所以当我从 A 收到带有 some-id 的事件时,我可以马上知道应该派发到some-id-B

第二个会使用读取端吗? (我不确定它是如何被正确调用的)并查询它并尝试根据 A id 找到 B id 但这似乎有点过分了。

有什么我可以阅读的内容可以引导我了解可能的场景并给我提示如何处理它们吗?谢谢!

据我了解,从聚合 B 到聚合 A 之间存在一种关系。这种关系很正常,而且经常发生。

注意:由于这个问题很笼统而且没有上下文,我可能会漏掉一些东西。如果有比描述的更特殊的情况,请通知我。

This is a great read for aggregate design

注意:在阅读此答案的其余部分之前,请查看 Martin Fowler 的 this video,我强烈建议您阅读它,因为它非常详细地解释了与事件和命令相关的概念。

注意:因为实体这个词也很重要,从这里开始我不会再使用聚合了,所以假设每个实体(PlayerUser, Game) 是它们自己聚合的根并且是一致性边界,因此在这种情况下将使用域事件的最终一致性。我现在也将忽略 CQRS,以避免不得不谈论读取端和写入端。我们将讨论并实现一个模型

以游戏为例。您有一个 Player,它应该代表 Game 中的 UserPlayer 实体应该以某种方式引用 GameUser。它可以通过直接引用或 ID。在分布式系统的情况下,它将通过 ID。对于我们的示例,让我们使用 UUID(例如 8d670541-aa51-470b-9e72-1d9227669d0c)作为 ID,这样我们就可以随机生成它们而无需定义架构,自动生成序列号(例如在 SQL 数据库中)或特殊算法。 假设 User 具有 UserStatistics。因此,当 Player 得分时(例如,通过在射击游戏中杀死其他玩家),如果 UserStatistics 实体不存在,则应创建该实体,并且更新。 UserStatistics 也应该通过 ID 引用 User,所以我们有从 UserStatistics用户.

UserStatistics 将如下所示:

UserStatistics {
  UUID UserID,
  uint KillsCount,
  uint GamesPlayedCount
}

因为 Player 不能在没有 User 的情况下存在,所以应该首先创建 User .因为 PlayerGame 的一部分,所以应该首先创建 Game。让我们在 通用语言 中定义一些术语。一个用户 'joins'一个游戏成为其中的 Player。假设游戏将由其他人而不是 Users 创建,以避免在 User 创建游戏并应加入游戏时讨论这种情况也在同一时间等等。如果这发生在同一笔交易中等等... 游戏 将类似于 MMO,由某人创建,普通用户可以加入。

用户加入一个游戏时,玩家 实体将使用 userIDgameID 创建。创建没有 userIDgameIDPlayer 无效。

让我们讨论 CommandsEvents 的问题。 命令 可以由事件 触发。让我们使用观察者模式。一个 Entity 将不得不观察另一个 Entity 的事件。在我们的示例中,这意味着依赖关系是从 UserStatistics(观察者)到 UserPlayer( subject/message 制作人)。 Command UserStatistics 上的特定 Command 将作为对 Event 引发的反应执行PlayerUser 不应影响 PlayerPlayer以任何方式。通过使用 Event 来故意触发特殊的 Command 以被动攻击的方式并不是一个很好的策略。 命令可以由事件触发,但不只有一个特定的命令可以触发。许多不同的 Commands 可以被触发,并且只能触发相关的 EntitiesServicesSystems 应该关心发生了什么。 PlayerUser 只提供 Events.

User 加入 Game 并创建 Player 时,它将引用两者实体,所以它看起来像这样:

Player {
  UUID GameID,
  UUID UserID
}

另外 UserJoinedGameEvent 事件将从 User 实体引发(它可以从 Game 但我们会选择 User)。它看起来像这样:

UserJoinedGameEvent {
  UUID GameID,
  UUID UserID,
  UUID PlayerID
}

UserStatisticsService 可以订阅事件和更新统计信息。

用户加入游戏时,将开始收集统计信息的过程,我们将更新(如果没有则创建) t 存在)他的 UserStatistics 以及他玩了多少场比赛。同时,当 Player 击杀时,我们将不得不再次更新统计数据。

StartGatheringUserStatisticsCommand 将从 UserJoinedGameEvent 事件触发。

让我们添加一个事件 PlayerMadeKillEvent,如下所示:

PlayerMadeKillEvent {
 UUID UserID,
 UUID PlayerID,
 UUID GameID
}

UserStatisticsService 将订阅 PlayerMadeKillEvents 并使用 更新 UserStatistics PlayerMadeKillEvent.UserID 查找特定 User.

的统计信息

User 退出 Game 时,可以引发 UserQuitsGameEvent 并收集统计信息可以停了

在这个例子中,我们没有特定的模式来生成特殊的 ID,我们可以引用将首先创建的其他聚合,然后使用它们的 ID。