晚上好,欢迎阅读本架构文档。很高兴你成功了!在您阅读时,此文档可能已过时,请随时更新!
所以,这基本上是关于为什么以及如何使用六边形架构的独特风味。
简而言之,六边形架构:
- 保护领域模型;
- 明确区分业务和基础设施职责。
免责声明
- 本文档是一个JHipster Lite模块。这可能不符合您的确切需求,您必须适应它!;
- 这是该架构的众多可能实现之一。如果您对此不满意,请坚持使用可以帮助您的方法;
- 代码架构可以帮助我们更快地构建出色的软件,如果它失败了,可能需要改变一些东西。
目标
以下是我们在使用这种架构时正在寻找的一些属性。在编码时牢记它们非常重要!
为改变而生
“生活中唯一不变的就是变化。”——赫拉克利特。这在软件中再合适不过了(因此得名),如果您只有几周的经验,您就会知道“用户不知道他们想要什么!!!”。如果第一个引用是真的,那么第二个是错误的!
并不是用户不知道他们想要什么,事实上,没有人知道。构建软件不是由某人告诉其他人该做什么来完成的,而是通过富有成效的合作伙伴关系完成的。是的,这意味着人们会一次又一次地改变主意(这完全没问题!)。
因此,作为专业的软件开发人员,我们必须确保我们编写的代码能够接受这些更改。这通常从降低解决方案的复杂性开始!
我们经常谈论三种类型的复杂性:
- 基本:在构建软件时,我们必须解决复杂性 X 的问题:这是基本的复杂性。这种复杂性与我们试图解决的问题直接相关,我们不能真正降低它。
- 强制:无论付出什么努力,我们都必须给系统增加一些复杂性,因为我们有技术工作要做(持久化数据,发送消息,......)。这种复杂性称为强制复杂性。
- 意外的:除了前面的两个复杂性之外,还有意外的复杂性,这是我们不想要的,因为它不是必需的。示例:如果您的配置在过去 10 年中一直处于静止状态,您可能不需要将其放入数据库中,在代码中处理它会更容易和更有效(不,您可能不需要微服务与一组 3 人)。
六边形架构允许我们通过为软件的每个部分赋予明确的职责,将所有这些复杂性降低到最低限度。
架构强制执行的非常明确的关注点分离简化了每个部分的自动测试,因为它只做一件事。能够构建可靠的测试也是构建欢迎软件更改的好方法!
即使架构简化了测试编写,编写好的测试也需要时间和实践!
缩短反馈回路
在软件开发中,如果您想更快(就像真的更快),您将不得不进行短反馈循环。如果您有一个按钮可以在几秒钟内告诉您您的解决方案仍然按预期运行,那么您将比在任何更新后手动检查要快得多(事实上,您不会在任何更新后手动检查) ...)。
说实话:六边形架构对最快的反馈循环没有帮助,这些反馈循环是配对或mob 编程中的配对反馈。
但是,紧随其后的是编译,为此,六边形架构会有所帮助!由于非常好的关注点分离,您将能够构建具有非常高内聚和非常低耦合的模块(Java 中的包)。这意味着,基础设施模块中的大多数类永远不会离开那里,因此允许编译时间反馈。
还有另一个很棒的编译时间反馈不是直接来自架构,而是来自那些架构中经常使用的实践:类型驱动开发。这个想法很简单:为每个业务概念创建一个专用类型。例子:
Firstname
:是的,这是一个但是这不是电话号码或克林贡字典,所以为它创建一个类型(带有一些检查和格式)。String
Lastname
: 是的,另一个但是......同样的原因。String
只需这两个,您就可以完全修复方法参数中的许多错误和反转:如果您发送错误的参数,它将无法编译! firstname
lastname
自动化测试比编译慢一点。如前所述,此架构简化了测试,因此您将能够从测试中获得快速(以秒为单位)和可靠的反馈。
我们之前说过将反馈与最快的反馈配对,但业务专家的反馈呢?使用“经典”(Controller、Service、Repository)架构,我们必须构建一个完整的“事物”,希望得到业务专家的反馈。在这里,领域模型代码与业务非常接近,因此很容易与业务专家坐在屏幕前并从中获得反馈!当然,您可能需要解释一些“编码内容”,但是您将能够很早就从业务专家那里获得任何给定算法的反馈!
延迟基础设施选择
通过会议开始一个项目以“构建架构”是很常见的,在这种情况下,这意味着选择基础设施元素。所以,在第 0 天,我们试图弄清楚我们是否需要 MongoDB、PostgreSQL 或两者都需要(那么 Elasticsearch 呢?)。
问题是:我们经常在没有足够信息的情况下做出这些选择,我们只会做出最好的猜测(因为真正的需求来自代码)。另一个问题是我们花了很多时间来做这件事。另一种选择是只选择一件事:语言(我们使用 Java 吗?)。选择语言可能具有挑战性,但比选择大量技术和语言要容易。
六边形架构让我们一了解语言就可以开始。从此,我们将能够开始构建解决方案,并让真正的基础设施需求从代码中显现出来。当然,我们必须尽快选择一个结构化框架(Spring,Quarkus,...),但我们可以将持久性选择推迟相当长的一段时间!
延迟选择允许:
- 更好的选择。即使您说“我们会根据需要进行更改”,您也必须再次与沉没成本谬论作斗争;
- 更快的第一个循环(因为您从引导程序中删除了大部分强制性复杂性)。
在哪里放置代码
最后,您正在寻找的架构部分:P。
因此,首先要做的事情是:一个应用程序由多个“六边形”组成,每个Bounded Context一个。(是的,有时您只能拥有一个,但这是一个例外)。我们通常将每个限界上下文作为应用程序中的根包。
最初,该架构以六边形(因此得名)的形式呈现,其中心是域模型:
在这种风格中,调用流程如下:
我们可以使用此文件夹组织来强制执行此架构:
my_business_context
: 上下文的根包(命名取决于您的技术命名约定)application
: 包含应用层代码domain
:包含业务代码infrastructure
:primary
: 包含驱动你的上下文的适配器实现secondary
: 包含您的上下文驱动的适配器实现
正如多次所说,这里的每个“部分”都有一个特定的关注点,所以让我们跟随那个洞里的兔子。
domain
模型中的代码
这是真正重要的代码。您可以使用领域驱动设计构建块或任何其他有助于您构建清晰的业务表示的工具来构建它。
这个模型不依赖任何东西,一切都依赖它,所以它完全与框架无关,你只需要选择一种语言来构建你的领域模型。
除了用于进行业务操作的代码之外,我们还将在域模型中找到端口。端口用于反转依赖关系。由于域模型有时需要端口进行某些操作,因此它们只能在那里,因为域不依赖任何东西。 interfaces
application
代码
应用层不得包含任何业务规则,其职责是:
- 非常简单的编排:
- 从端口得到一些东西;
- 对该事物进行操作(调用对象上的方法);
- 使用端口保存该东西;
- 使用端口调度创建的事件。
- 事务管理;
- 授权检查(这是接线点,授权的业务必须在域内)。
primary
代码
primary
部分包含驱动我们域的代码的适配器。示例:公开 REST Web 服务的代码。这部分很大程度上取决于框架,并负责尽可能地展示业务行为。
secondary
代码
secondary
部分是实现来自域的端口的适配器。这部分很大程度上取决于框架,它的职责是尽可能地利用我们业务所需的基础设施。