DDD中两个有界上下文之间的通信

时间:2013-05-23 11:42:59

标签: domain-driven-design cqrs domain-events bounded-contexts

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

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

在这个例子中我有两个有界上下文(BC)。一个是游戏BC ,另一个是用户BC 。要解决此问题,在游戏BC 中,我必须在用户BC 之前进行域名服务调用,例如 IsGroupLeader(),然后再继续创造游戏。

我不认为DDD推荐这种类型的通信。我也可以在游戏BC 中拥有用户实体,但我不想,因为同一个用户实体的使用方式不同不同BC中的不同背景。

我的问题是:

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

  2. CQRS是否能解决我的问题?

  3. 任何想法都赞赏。

6 个答案:

答案 0 :(得分:27)

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

CQRS与此问题有些正交。

答案 1 :(得分:16)

以下是我的理由。

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

如果游戏BC依赖于活动/当前玩家,则应在创建游戏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中。

答案 2 :(得分:2)

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

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

因此,我们总是将与应用程序交互的角色建模为应用程序需求(基础架构,持久性,UI,本地化等...)和业务规则(域)之间的联结点,并在不同的模块中对它们进行编码(又称组件或包)。

关于你的第二个问题,CQRS可以用来编码你正在描述的BC之间的那种交互,但是在这个特定的上下文中我不喜欢它。

答案 3 :(得分:2)

我认为你几乎就在那里。接近一个好的解决方案。我不太确定你必须把这两个分成两个不列颠哥伦比亚省。您的用户聚合(?)和游戏可能属于一个BC并且相互依赖。用户"有一个"会员资格"到一个或多个"游戏(只是猜测你的实体关系)。 但我现在只是头脑风暴。尝试遵循:)不同的方法如下:

<强>第一 GameBC有一个Create()方法,实际上将UserMembership作为参数。创建(UserMembership)。 然后你通过UserMembership实体知道什么样的会员资格和用户这个。如果被接受,则创建游戏。如果没有抛出异常或游戏获得破坏的规则消息,则取决于您希望与客户端进行通信的方式。协调可以在应用层完成,不会泄露领域知识。

<强>第二 你做其他答案之一。您在Game.Create(UserId)方法中引发CreateGameEvent。该事件由位于Application层中的EventHandler(由IoC在Application启动中注册)捕获,并通过存储库查找UserMembership。域知识的小泄漏是知道谁被允许的业务规则,你可以在应用层验证。这可以通过让CreateGameEventHandler获取UserId和RuleRef(可以是字符串&#34; CAN_CREATE_GAME&#34;或枚举)并让UserPermission对象验证权限来解决。如果不。在应用程序层中抛出异常并捕获异常。 缺点可能是您需要在Create方法中对权限引用字符串进行硬编码。

<强>第三 ...继续第二种方法的结束。您知道,如果遵循SRP原则,GameBC可能不是进行用户权限查找的正确位置。但是以某种方式围绕该方法触发了该动作。另一种方法是Create(GroupLeader用户)。或者您可以使用Game.Create(用户用户)然后执行User是GroupLeader类型的验证。 Create(GroupLeader)告诉您需要调用此方法的内容。

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

然后你有一种直观的,自我描述的方式告诉其他开发者&#34;你必须有一个GroupLeader实例才能创建游戏&#34;。

最后,我希望您意识到您了解域名,并且您将找出最适合您的域名。务实,不要去Eric Evan铁杆。有那么多的开发者被困在一个宗教信仰中。在如何做的事情。项目的大小,金钱,时间以及对其他系统的依赖等也会影响您对DDD的严格要求。

祝你好运。

答案 4 :(得分:0)

我建议将用户和用户信息的角色传递给Game bounded context service,并在Game BC中使用GroupLeader value object。这样,您总可以知道谁是group_leader

答案 5 :(得分:-1)

我想我可能不得不采用不同的方法来制作用户实体 游戏BC 的一部分(同一实体是用户的一部分BC 也)。我将使用Repository从 Game BC 中的db读取IsGroupLeader标志。这样就消除了对用户BC 的依赖,并且用户BC 无需进行任何通信。

您怎么看?