上下文映射关系中如何解耦特定和通用的领域? - Nick Tune


您正在构建一个新系统,并且您的团队的两名成员各自提出了用于发送通知的两种架构,哪一个是正确?如何选择?

  • 第一个开发人员提出的是推送模型:有界上下文应指示通知上下文发送通知。专门的通知上下文应该只是遵循来自其他上下文的命令,并在指示时发送通知。
  • 第二个开发人员不喜欢推送模型,并提出了一个精心设计的解决方案:当有界上下文引发事件时,通知上下文应该监听这些引发的事件并确定何时发送通知。

您将如何构建解决方案?更重要的是,您如何在团队中解决此决定?您将如何设计支持短期目标和长期演变的最有效架构?
拥有团队中的DDD技能是一个主要优势。能够分析您的域以了解核心,支持和通用功能,使您能够进行合理的工程权衡。
让我们以DDD的角度进一步探索这个场景。

领域能力分析
选项1(推送命令)的参数是泛型(通知)不应该依赖于特定的有界上下文。如果事件在许多域中是通用的,那么逻辑上如果它与某个特定领域中的事件进行耦合是错误的。
除了抽象推理之外,这个启发式警告我们是什么?我们需要注意哪些工程后果?
我们希望避免两种情况:

  1. 避免特定于域的逻辑泄漏到通用上下文中,从而导致共同变化
  2. 由于与特定领域的有界上下文过于紧密耦合,无法使用现成的解决方案重用或替换通用上下文

耦合领域和通用责任
使用选项1,域特定API和域通用API之间不存在共同变化。如果需要新通知,则仅更改特定于领域的上下文。但是选项2的情况并非如此。
使用选项2(精心设计),如果引入需要通知的新事件,则特定于域的API将需要发布新事件,并且通知服务将需要订阅该事件并发送通知。这在感觉上不对 : 领域的知识隐藏在通用上下文中。

隔离通用功能
如果通知服务是真正的通用服务并在许多团队甚至组织中重用,则需要了解数百个领域事件。有这么多团队取决于通知上下文,它肯定会成为一个瓶颈,减少了它在整个组织中重用的可能性。
另一项设计反馈是,我们无法用现成的解决方案取代通用上下文。如果它在许多领域真正具有通用性,但我们无法用具有更多功能的现成解决方案取代它,并且运行成本更低,设计就会给出错误的反馈。

那么从特定命令到通用命令是最佳实践吗?
所有证据都表明选项1是正确的:特定于领域的上下文应该将命令发送到领域域通用上下文以将它们与领域逻辑分离。但是,我们的分析很浅薄。我们需要进一步分析这个领域,让我们有信心做出良好的工程选择。

更深入的领域分析
更仔细地观察选项1,每个需要发送通知的特定于域的上下文都承担了额外的责任。它需要知道何时发送通知以及要发送的通知类型。
将通知逻辑分散在所有有界上下文中是否明智?除了纠结的代码,如果通知接近变化,这可能意味着跨多个团队进行大规模协调。
如果每个团队采用他们自己的通知方法,那么风险或不一致以及重复/浪费也会增加。共享库是可能的解决办法,但并不能解决所有问题,也会带来妥协。

只要难以确定职责责任是否属于某种情况,请进一步放大并分解责任。寻找可以分解的子责任 - 也许有一个隐藏的领域概念。当一个概念不能完全适合单个有界背景时,进一步分析它以确定子责任。也许有一个隐藏的域概念可以产生一个干净分区的模型。

放弃发送通知的有争议的责任,是否会有一个缺失的概念?也许有第三个概念将特定领域与领域通用相关联,以提供更优雅的模型。

上下文映射器
将特定领域与领域通用进行解耦的模式是领域上下文映射器(Domain Mapper Contexts),当这个上下文承担此角色时,它会侦听特定的领域事件并将它们映射到发送到通用上下文的命令。

请注意这种模式的权衡取舍与最初两种选择的优缺点相比较。它提供了两者的优点 - 可以自由地更改通知的发送方式,而不会因为与通知相关的复杂性而混淆每个特定于域的上下文。

考虑这种情况:内部通知系统将由现成的解决方案取代。域映射器将所有命令路由到新的通知服务,而不会影响任何特定于域的上下文。

考虑一种情况:要添加新通知。注册将在映射器中配置:当{domain event}触发{notification}时,这样{notification}通知变成领域通用功能 :许多领域利用电子邮件或推送通知,也可以使用Twitter上的通知首选项 。

为什么称为上下文Mapper?
这种模式的命名很重要。在一个域内发生的操作将映射到在另一个域中发生的操作(通用上下文在许多域中是通用的,因此部分在另一个域中)。
您可能与其他模式(如消息传递翻译器)有相似之处,但翻译意味着某种等价; 翻译的值是原始值的不同表示。使用映射器,情况并非如此。
映射器更像是一个监听器,可以观察域内发生的情况。它决定如何使用另一个域中的操作响应域中的事件。

领域上下文映射器网关
如果您决定使用SaaS解决方案替换自定义构建的通用上下文,则域映射器上下文将成为域上下文映射器网关(Gateway Domain Mapper Context)。它现在位于系统的边缘并跨系统边界进行通信。它是信息流入和流出系统的途径。实现可能看起来相同,但是使用更精确的术语对于您更清楚地传达架构非常有用。

域映射器的工程权衡
域上下文映射器多了一个附加映射层意味着现在涉及三个协作者。意味着更多可能失败的发生可能。
也有共同变化。当引入新事件或现有事件发生更改时,拥有事件的特定于域的上下文和映射器上下文都需要更改。有一种广泛使用的解决方案可以最大限度地降低这种耦合。

自助式的领域上下文映射器
经常出现的一种模式是自助服务有界上下文:扮演此角色的有界上下文允许消费者利用上下文的功能,而不会被拥有上下文的团队阻碍。
第一种变体是通过源控制的编译时机制,第二种是通过API调用的运行时机制。
在通知方案中,自助服务上下文可以提供DSL。拥有特定于域的上下文的团队将创建包含配置文件中的更改的拉取请求(甚至更好 - 编译和测试的代码),使用DSL配置域事件之间的映射以及通知被发送。
使用动态版本,将通过API调用或UI执行相应的配置。当动态配置成为可能并且域上没有编译时依赖性时,上下文将变为完全通用。这是一种常见的进化模式(从定制到商品)。

发布的语言
在这种情况下要考虑的另一种DDD模式是已发布的语言。触发通知的任何事件都应构成已发布语言的一部分。
已发布的语言是有界上下文生成的消息格式的合同或协议。确保需要通知的事件是已发布语言的一部分意味着应该更加注意确保向后兼容性并通知消费者未来的变化。

这里显示的每个模式都是有效的,并且可以在各种系统中成功使用。域映射器上下文是另一种具有明确权衡的模式,您可以使用它来探索替代建模可能性。

寻找超级用户
在天真或肤浅的层面上建模域很容易。当听到像“通知”这样的单词时,很容易陷入衔接名称谬误,假设因为一个单词听起来像一个单一的概念,它必须由我们系统中的单个有界上下文表示。
当我们使用DDD进行更深层次的分析时,我们希望将领域特定的和领域通用的概念分开,通常我们会发现有多种方法可以对域进行建模,包括具有多个有界上下文 - 从通用中描述特定领域知识 - 在一个超级环境中。
将领域特定与领域通用分离不是关于创建遵循古老DDD规则的漂亮抽象模型,而是关于将由于不同原因而一起变化的概念分离,以便我们可以设计权衡以允许系统更容易地进化。
为了获得更深入的领域洞察力,我建议将DDD技能和DDD活动纳入团队的工作方式。