33

我在域中几乎没有不同的限界上下文。CRUD 操作的验证建立在每个有界上下文中。

例如,只有当创建它的人是 Group Leader 时,我才能创建一个名为 GAME 的实体

在此示例中,我有两个限界上下文 (BC)。一个是Game BC,另一个是User BC。为了解决这个问题,在Game BC中,我必须在继续创建游戏之前对User BC进行域服务调用,例如IsGroupLeader() 。

我不认为 DDD 推荐这种类型的通信。我也可以在Game BC中有一个User 实体,但我不想这样做,因为相同的User 实体在不同的 BC 的不同上下文中的使用方式不同。

我的问题是:

  1. 我应该使用游戏 BC必须向用户 BC发送事件询问用户状态的域事件吗?使用这种方法,我不会像IsGroupLeader那样进行同步调用,而是调用is_group_leader的事件。然后 Game BC 必须等待 User BC 处理事件并返回状态。Game BC 只有在 User BC 处理完事件后才会创建 Game 实体。

  2. CQRS 能解决我的问题吗?

任何想法表示赞赏。

4

6 回答 6

35

集成 BC 时,您有几个选择。不鼓励调用外部 BC 的原因是因为它要求两个 BC 同时运行。然而,这通常是完全可以接受的,并且比替代方案更简单。另一种方法是让 Game BC 订阅来自用户 BC 的事件并保留其所需数据的本地副本,在这种情况下,这是关于用户是否是组长的信息。这样,当Game BC需要判断一个用户是否是组长时,它不需要调用User BC,它只是读取本地存储的数据。这种事件驱动替代方案的挑战是同步事件。您必须确保游戏 BC 从用户 BC 接收所有适当的事件。另一个挑战是处理最终一致性,因为 BC 可能在任何给定时间点略微不同步。

CQRS 与这个问题有些正交。

于 2013-05-23T14:59:37.417 回答
19

以下是我的推理方式。

我认为 Game BC 不知道“用户”,但它可能知道“玩家”。

如果 Game BC 依赖于活动/当前玩家,则应在创建 Game BC 实例时将其传递到 BC。

例如。

 Player currentPlayer = GetPlayerSomehow...();
 GameBC gameBC = new GameBC(currentPlayer);
 gameBC.DoStuff();

现在你的两个 BC 仍然是分开的,你可以分别测试它们等等。

为了使这一切正常工作,您只需执行以下操作:

 User currentUser = GetCurrentUser();
 Player currentPlayer = new Player();
 currentPlayer.IsGroupLeader = currentUser.IsGroupLeader;
 GameBC gameBC = new GameBC(currentPlayer);
 gameBC.DoStuff();

这充当了 UserBC 和 GameBC 之间的反腐败层,您可以将您想要的状态从 UserBC 移动并验证到您的 GameBC 所需的状态。

如果您的 GameBC 需要访问许多用户,您仍然可以将某种映射服务传递到在内部进行这种转换的游戏 BC。

于 2013-05-24T07:47:54.067 回答
4

我想你快到了。接近一个好的解决方案。我不太确定您是否必须将这两个分成两个 BC。您的 User Aggregateroot (?) 和 Game 可能属于一个 BC 并相互依赖。用户“拥有”一个或多个“游戏”的“会员资格”(只是猜测您的实体关系)。但我现在只是在头脑风暴。尝试遵循:) 不同的方法如下:

第一个 GameBC 有一个 Create() 方法,它实际上将 UserMembership 作为参数。创建(用户会员)。然后你通过 UserMembership 实体知道什么样的成员资格和用户这个。如果接受,则创建游戏。如果未引发异常或 Game 收到错误规则消息,则取决于您要与客户端通信的方法。协调可以在应用层完成,而不会泄露领域知识。

其次 ,您可以作为其他答案之一。您在 Game.Create(UserId) 方法中引发了 CreateGameEvent。该事件由驻留在应用程序层中的 EventHandler(由 IoC 在应用程序启动中注册)捕获并通过存储库查找 UserMembership。领域知识的小泄漏是知道谁被允许做什么的业务规则在应用层进行了验证。这可以通过让 CreateGameEventHandler 获取 UserId 和 RuleRef(可以是字符串“CAN_CREATE_GAME”或枚举)并让 UserPermission 对象验证权限来解决。如果不。异常在应用层被抛出和捕获。缺点可能是您不希望在 Create 方法中对权限引用字符串进行硬编码。

第三 ...在第二种方法结束的地方继续。您知道如果您遵循 SRP 原则,GameBC 可能不是进行用户权限查找的正确位置。但是该操作以某种方式围绕该方法触发。另一种方法是创建(GroupLeader 用户)。或者你可以让 Game.Create(User user) 然后验证 User 是 GroupLeader 类型。Create(GroupLeader) 告诉你调用这个方法需要什么。

最后 也许是我现在写这篇文章时更喜欢的替代方案。当你想创建一个实体时,我通常让 Create(Save) 方法在存储库中。IGameRepository 接口位于域组装项目中的游戏实体旁边。但是您也可以创建一个负责启动游戏实体生命周期的 GameFactory。这里也是放置 Create 方法的好地方... GameFactory.Create(GroupLeader) { return new Game.OwnerUserId = GroupLeader.Id; 然后你只需保存它 IGameRepository.Save(Game)

然后你就有了一种直观和自我描述的方式来告诉其他开发人员“你必须有一个 GroupLeader 实例才能创建一个游戏”。

最后,我希望您意识到您了解该领域,并且您会找出最适合您的领域。务实一点,不要硬核 Eric Evan。有这么多的开发者被困在如何完成事情的“宗教”中。项目的大小、资金、时间和对其他系统的依赖等也会影响您在执行 DDD 时的严格程度。

祝你好运。

于 2013-05-24T13:12:41.083 回答
2

为了应对您所面临的这类问题,我们使用了有限角色,这是一种多年来出现并证明效果很好的建模模式。有界上下文是在企业组织中通常可以映射到特定角色的语义单元之后定义的。

考虑到不同的角色面临不同的问题,因此会说略微(或完全)不同的语言,这应该是显而易见的。

因此,我们总是将与我们的应用程序交互的角色建模为应用程序需求(基础设施、持久性、UI、本地化等)和业务规则(域)之间的连接点,并且我们将它们编码在不同的模块(也称为程序集或包)。

至于您的第二个问题,CQRS 可以是一种编码您所描述的 BC 之间的交互类型的方法,但我不喜欢在这种特定情况下使用它。

于 2013-05-23T16:12:34.697 回答
0

我想我可能必须采用不同的方法,使用户实体成为游戏 BC的一部分(同一实体也是用户 BC的一部分)。我将使用 Repository 从Game BC的数据库中读取 IsGroupLeader 标志。这样,就消除了对User BC的依赖,并且不需要与User BC进行通信。

你怎么看?

于 2013-05-24T00:53:19.950 回答
0

我建议将用户和用户信息的角色传递给Game bounded context service并且还有一个GroupLeader value object内部游戏 BC。这样你总能知道是谁group_leader

于 2019-11-06T09:54:07.543 回答