以DDD方式设计云微服务六边形架构- Sandeep

22-09-19 banq

虽然微服务一直被认为是领域驱动设计范式的实现,并以正在开发的单个服务的有界上下文为中心,但事实与现实相去甚远。虽然 DDD 方法在微服务世界中无处不在,因为它在域和子域中具有独立/隔离的问题区域,但如果仍然遵循传统的代码结构化方式,我们就会错过这个神奇世界的真正本质。
这篇文章将重点介绍我们今天遵循的实践,以及符合领域驱动设计和 SOLID 原则的可能解决方案。这些建议是设计微服务结构的一种固执己见的方式,并且基于云原生应用程序和相关工作的清洁架构实践,最初由 Alistair Cockburn、Robert C. Martin 出版和拥有,并且在 Tom Hombergs 所著的书中也有描述。

介绍
在我们的实现过程中,我们都使用设计模式和原则来使我们的应用程序代码更加健壮、可维护、可理解和可扩展。但是,如果我们的设计意图与代码意图不相关,我们就无法充分利用这些原则。所以在纸面上,我们确实设计并提到了实现的原则,但代码可能会朝着完全不同的方向发展。
因此,如果我们遵循 DDD/SOLID/OOPS 并且我们打算开发独立于 Web、持久性和集成依赖项的服务。而不是域依赖于它们中的任何一个;它们都依赖于域。对应用程序的任何更改都将是域状态或行为的更改,相应的服务将相应地适应更改,而不是由 UI 或持久性或任何其他角度的更改来驱动域。
反转这种依赖关系意味着更容易代码维护,实施符合项目既定设计指南的治理检查,并使其更具可扩展性以添加更多功能,否则代码更改将由于耦合而产生连锁反应。否则,每一次新的更改/更新或添加新功能都会使其更加脆弱、固定和僵化,以适应未来的扩展。

背景:六边形架构
六边形架构又名端口和适配器模式(在某种意义上也可以是管道和过滤器,其中请求和响应在单个方向上建模)意图将所有业务逻辑和模型保持在一个地方,其他一切都依赖于此而不是其他方式。
如果我们从任何应用程序层的角度来看,依赖关系将来自控制器/接口和持久性的域,而不是我们通常在项目中创建的分层架构。通过这种方式,我们使这些六边形独立并且可以与 DDD 术语中的有界上下文相关联。通过使用上述类比,我们希望将此架构称为“插件架构”;您的基本模型保持不变,您可以通过将更多插件实现附加到相同的接口来进一步扩展功能。




适配器
这些是核心逻辑接口的具体实现,要么调用域,要么被域调用以完成业务。

域对象
代表现实世界的主题。

用例
端口接口的任何业务功能、可访问性和实现。

端口
端口可以​​被视为核心逻辑的简单接口。可能有对应于来自核心逻辑六边形的传入和传出呼叫的入站和出站端口。

Inbound 端口
将核心逻辑暴露给消费适配器。

Outbound 端口
通过核心逻辑消费外部服务。

问题陈述

耦合是罪魁祸首
当我们设计我们的应用程序时,我们考虑了世界上的“微服务”、“云原生开发”。简而言之,整个想法是将一个大问题分解为可管理的部分,使其更易于管理、可维护、透明、DevOps/云就绪,同时快速响应更改,因为更改可能专门位于某些有限的上下文中并且不会对其他人有任何影响。因此,虽然我们使用云原生开发实践快速修复和交付(利用 CI/CD)功能或对功能的更改,但代码及其结构应该支持相同的功能。
当且仅当代码是基于 SOLID 原则编写的并且在组件之间使用松散耦合以快速响应更改而没有连锁反应时,以上所有这些都是可能的。

场景一
传统的铺设方式。控制器/接口取决于服务/域和服务/域到 DAO/存储库。实际上应该由领域而不是这种方式驱动。
优点
· 熟悉且易于上手。
·看起来与分层架构和层依赖关系更加同步。
缺点
· 对数据库/基础设施的依赖
· 可以引入雪花
· 由 UI 或持久性驱动



场景二
另一种设计微服务的方法是将所有域对象和接口打包为单独的工件/组件并让服务实现它们。
优点
·易于分发和设计到界面。
·根据上下文,接口的多个实现。
缺点
· 服务、域和存储库取决于接口,而不是域/核心逻辑。
·考虑消费而非核心逻辑而构建的接口。
·域日志不稳定,由消费者和界面驱动。





领域应该是中心
在任何领域应用程序中,领域对象都是技术人员和业务人员的中心舞台,因为它们描述了现实世界的业务模型。域对象应该暴露给外部世界以被消费以执行业务意义上的操作。由于域对象包含状态和行为(响应事件),因此它们是用例实现的主要候选对象。用例将包装域对象以供消费者/客户访问和分发。
在使用多层应用程序时,我们可以看到相同的真正好处,因此这些层是可替换的,而无需对应用程序/业务层进行任何更改。
解决方案




服务和域/核心模型被隔离并映射到特定的问题域和/或子域,以被视为有界上下文,并且可以实现为具有自己的分布式接口(Web/移动等)及其持久性逻辑的微服务.
出站端口/适配器的设计和开发可以被视为支持服务(https://12factor.net/backing-services),因为出站端口的六边形架构中的当前抽象支持底层实现的无缝更改,而无需对域/核心进行任何更改逻辑。

扩展 OOPS 视图
我们还可以将六边形架构实现视为 OOPS 的扩展,用于设计组件,其中服务和域被封装在单个/隔离的组件中。具有服务接口的域对象展示了必要的抽象并提供接口来访问域的行为,并扩展了域和服务或服务实现的可扩展性(考虑开放/关闭原则)或模块化。




执行







· 控制器将作为接口模块服务的一部分。
· 服务接口、领域对象、实现应该是领域模块的一部分。
· 存储库实现应该是持久化模块的一部分

好处




· 支持多语言消费者,无需更改域和核心逻辑(消息、HTTP、移动设备等)。
· 对域执行单一职责(SOLID)




· 支持多语言持久性。
· 支持服务虚拟化(用于接口调用服务)。
· 持久性关注点不影响域事实上域会驱动持久性关注点。
· 依赖反转到域(SOLID)
· 核心逻辑中的存储库接口代表实现替换(SOILD)
· 内部接口代表与外部接口配对,而不是一个巨大的接口。(坚硬的)




· 只有一个/单一的职责与域来管理业务逻辑。(坚硬的)
· 遵循面向对象原则的领域逻辑,对扩展开放,对修改关闭。(坚硬的)
· 支持松散耦合和高内聚设计,因为有界上下文和反向依赖关系,对一个区域的更改可能不会对其他区域产生任何影响。
· 由于独立的组件和接口设计,域逻辑更容易测试。此外,它独立于任何接口/集成技术,可以模拟/存根以测试域逻辑。
· 干净的域逻辑不受网络和持久细节的污染。

结论
以这种方式构建微服务可以让您更加独立地使用您的领域,这反映了真实世界的模型。这也使您的域/核心逻辑独立于消费者(网络/移动等)和集成(持久性/消息等)并且非常接近纯 SOLID 原则的实现。这种方法使您更敏捷地适应变化,而不会对其他层产生太大的变化影响,尤其是。接口和集成层由于在需要时进行了独立的代码更改。由于其隔离和可分发的特性,核心逻辑可以在各种企业场景中扩展/实现,而不是为每个应用程序创建相同的。

 

1