ddd - 如何分离有界上下文和共享事件?
ddd - How to separate bounded contexts and share events?
我实际上正在读一本名为 "DDD in PHP" 的书,以帮助我理解领域驱动设计。到目前为止一切都很好,但我很难理解如何在不耦合限界上下文的情况下实现一个特定主题:领域事件
假设我必须 BCs :
- 付款:处理发票的生成,将其发送给客户等
- 订单:处理订单的创建、订单状态等
放置 Order
时,将调度 OrderCreated
事件。
Payments
BC 通过订阅者捕获此事件,并创建发票。
问题是,如果我想完美地分离两个 BC,OrderPlaced
事件应该放在哪里,因为它被两个 BC 使用?它应该住在两个 BC 之外吗?在他们两个?如果我想独立部署 Invoices 模块,而不访问 Orders 模块及其 OrderPlaced 事件定义,会导致一些致命错误吗?
提前感谢您的回答!
The problem is, If I want to perfectly separate both BCs, where should the OrderPlaced Event live, since it's used by both BCs ? Should it live outside both BCs ? In both of them ?
我会将事件存储在拥有它的上下文中,即订单上下文。您打算如何分离上下文?它是物理/网络边界分离,还是仅仅是概念上的?
What if I want to deploy the Invoices module as a standalone, without having access to the Orders module, and its OrderPlaced event definition, would it cause some fatal errors ?
这取决于您使用 OrderPlaced 做什么。如果您从某种事件流订阅它,然后通过将其转换为内部发票概念在 InvoicesBC 中对其作出反应,那么您可能会没事,因为您可以不部署订阅者。如果您在 InvoicesBC 中的代码可以 运行 而无需了解 OrderPlaced 那么您应该没问题
一般来说,有以下几种方法可以解决这个问题:
- 分享一个共同的定义。在 C# 中,(不确定 PHP 中的等价物是什么)过去我在 BC 中有一个单独的 class 库用于另一个 BC 可能需要的事件(即 MyContext.Contracts dll) 然后可以被其他 BC 引用。这些作为内部 nuget 源(包管理器的东西)发布,因此其他上下文可以保持最新。
- 订阅端的弱序列化。如果您正在订阅一个事件流,您可以处理它存储的原始表示(即 JSON),而不是让一些库自动将它反序列化为一个对象。如果你沿着这条路走下去,我建议在发布端使用一些 "contract tests" 来模仿订阅者会做的事情。这将保护您免于违反与外界签订的合同。
问题是 Payments
BC 是否必须创建发票,因为顾名思义,它应该关注 payments
而不是 orders
或 invoices
。
也许是这样的:
Orders -> 'order created' -> Payments -> 'payment done' -> Orders -> 'invoice created'
虽然已经接受了一个答案,但我还是想补充一下我的意见:)
您当前的 BC 集成方式是直接订阅已发布的消息:Payments
订阅 OrderCreated
。这种交互方式被称为 choreography。这不一定是坏事,但你必须权衡利弊。例如,当您需要在中间添加一个步骤时,您可以让 Address
BC 订阅 OrderCreated
以便它可以验证送货地址。现在 Payments
BC 必须订阅 AdressValidated
事件。但是话又说回来,这个事件被发布了很多,因为我们在注册客户时也使用它。嗯...
另一种选择是使用 orchestration,您可以在其中明确存储相关进程的状态。你我有一个 OrderProcess
和一个 CustomerOnboardingProcess
。然后,您可以让当前的 BC 仅处理它们自己的 BC 中的消息。然后另一个 process BC 将协调消息并跟踪状态。
编排可能 运行 涉及的另外两个问题是:
- 必须等待人工干预
- 并行处理,其中两个任务 运行 正在处理同一个进程,一个需要将结果重新放在一起。
我希望这是有道理的。
我实际上正在读一本名为 "DDD in PHP" 的书,以帮助我理解领域驱动设计。到目前为止一切都很好,但我很难理解如何在不耦合限界上下文的情况下实现一个特定主题:领域事件
假设我必须 BCs :
- 付款:处理发票的生成,将其发送给客户等
- 订单:处理订单的创建、订单状态等
放置 Order
时,将调度 OrderCreated
事件。
Payments
BC 通过订阅者捕获此事件,并创建发票。
问题是,如果我想完美地分离两个 BC,OrderPlaced
事件应该放在哪里,因为它被两个 BC 使用?它应该住在两个 BC 之外吗?在他们两个?如果我想独立部署 Invoices 模块,而不访问 Orders 模块及其 OrderPlaced 事件定义,会导致一些致命错误吗?
提前感谢您的回答!
The problem is, If I want to perfectly separate both BCs, where should the OrderPlaced Event live, since it's used by both BCs ? Should it live outside both BCs ? In both of them ?
我会将事件存储在拥有它的上下文中,即订单上下文。您打算如何分离上下文?它是物理/网络边界分离,还是仅仅是概念上的?
What if I want to deploy the Invoices module as a standalone, without having access to the Orders module, and its OrderPlaced event definition, would it cause some fatal errors ?
这取决于您使用 OrderPlaced 做什么。如果您从某种事件流订阅它,然后通过将其转换为内部发票概念在 InvoicesBC 中对其作出反应,那么您可能会没事,因为您可以不部署订阅者。如果您在 InvoicesBC 中的代码可以 运行 而无需了解 OrderPlaced 那么您应该没问题
一般来说,有以下几种方法可以解决这个问题:
- 分享一个共同的定义。在 C# 中,(不确定 PHP 中的等价物是什么)过去我在 BC 中有一个单独的 class 库用于另一个 BC 可能需要的事件(即 MyContext.Contracts dll) 然后可以被其他 BC 引用。这些作为内部 nuget 源(包管理器的东西)发布,因此其他上下文可以保持最新。
- 订阅端的弱序列化。如果您正在订阅一个事件流,您可以处理它存储的原始表示(即 JSON),而不是让一些库自动将它反序列化为一个对象。如果你沿着这条路走下去,我建议在发布端使用一些 "contract tests" 来模仿订阅者会做的事情。这将保护您免于违反与外界签订的合同。
问题是 Payments
BC 是否必须创建发票,因为顾名思义,它应该关注 payments
而不是 orders
或 invoices
。
也许是这样的:
Orders -> 'order created' -> Payments -> 'payment done' -> Orders -> 'invoice created'
虽然已经接受了一个答案,但我还是想补充一下我的意见:)
您当前的 BC 集成方式是直接订阅已发布的消息:Payments
订阅 OrderCreated
。这种交互方式被称为 choreography。这不一定是坏事,但你必须权衡利弊。例如,当您需要在中间添加一个步骤时,您可以让 Address
BC 订阅 OrderCreated
以便它可以验证送货地址。现在 Payments
BC 必须订阅 AdressValidated
事件。但是话又说回来,这个事件被发布了很多,因为我们在注册客户时也使用它。嗯...
另一种选择是使用 orchestration,您可以在其中明确存储相关进程的状态。你我有一个 OrderProcess
和一个 CustomerOnboardingProcess
。然后,您可以让当前的 BC 仅处理它们自己的 BC 中的消息。然后另一个 process BC 将协调消息并跟踪状态。
编排可能 运行 涉及的另外两个问题是:
- 必须等待人工干预
- 并行处理,其中两个任务 运行 正在处理同一个进程,一个需要将结果重新放在一起。
我希望这是有道理的。