由Alistair Cockburn于 2005 年记录的Hexagonal Architecture 是一种具有许多优势的软件架构,并且自 2015 年以来再次受到关注。
Hexagonal Architecture的初衷是:
允许应用程序同样由用户、程序、自动测试或批处理脚本驱动,并在与其最终运行时设备和数据库隔离的情况下进行开发和测试。
要探索通过自动化测试试点应用程序或独立于数据库进行开发和测试的好处,我们建议您阅读我们最近发布的关于测试金字塔的这一系列博客文章:实践测试金字塔。
这个 Promise 非常有吸引力,它还有另一个有益的效果:它允许隔离应用程序的核心业务,并独立于其他一切自动测试其行为。这可能是该架构引起领域驱动设计 (DDD) 从业者关注的原因。但请注意,DDD 和六边形架构是两个截然不同的概念,它们可以相互加强,但不一定一起使用。但这是另一个话题!
最后,这种架构设置起来并不复杂。它基于一些简单的规则和原则。让我们探索这些原则,看看它们在实践中的含义。
◆ 六边形结构原理
六边形架构基于三个原则和技术:
- 明确区分用户端、业务逻辑和服务器端
- 依赖关系从用户端和服务器端到业务逻辑
- 我们使用端口和适配器隔离边界
词汇说明:在本文的其余部分中,将使用 User-Side、Business Logic和Server-Side等词。这些词来自原始文章,它们在下面的部分中定义。
◆ 原则:分离用户端、业务逻辑和服务器端
第一个原则是明确地将代码分成三个大的形式化区域。
在左侧,用户端
这是用户或外部程序与应用程序交互的一侧。它包含允许这些交互的代码。通常,您的用户界面代码、API 的 HTTP 路由、使用应用程序的程序的 JSON 序列化都在这里。
这是我们找到推动业务逻辑的参与者的一面。 注意:Alistair Cockburn 也将其称为左侧。
业务逻辑,在中心
这是我们要从左侧和右侧隔离的部分。它包含所有涉及和实现业务逻辑的代码。业务词汇和纯粹的业务逻辑,与解决您的应用程序的具体问题相关,使其丰富和具体的一切都处于中心位置。理想情况下,不知道如何编码的领域专家可以阅读这部分中的一段代码并指出不一致的地方(真实的故事,这些都是可能发生在你身上的事情!)。
注意:Alistair Cockburn 也将其称为中心。
在右侧,服务器端
在这里,我们将找到您的应用程序需要什么,它驱动什么工作。它包含基本的基础架构详细信息,例如与数据库交互的代码、对文件系统的调用或处理对您所依赖的其他应用程序的 HTTP 调用的代码。
这是我们找到由业务逻辑管理的参与者的一侧。
注意:Alistair Cockburn 也称它为Right Side。
以下原则将允许将用户端、业务逻辑和服务器端之间的这种逻辑分离付诸实践。
为什么这很重要?
这种分离的第一个重要特征是它分离了问题。在任何时候,您都可以选择专注于单一逻辑,几乎独立于其他两个逻辑:用户端逻辑、业务逻辑或服务器端逻辑。它们更容易理解而不混合它们,并且每个逻辑的约束对其他逻辑的影响较小。
另一个特点是我们将业务逻辑放在代码的最前面。它可以被隔离在一个目录或模块中,以使其对所有开发人员都是明确的。它可以在不承担程序其余部分的认知负担的情况下进行定义、改进和测试。这很重要,因为归根结底,是开发人员对投入生产的业务的理解。
最后,在自动化测试方面(我们将在下面看到),我们将通过合理的努力成功地进行测试:
- 单独的整个业务逻辑,
- 独立于服务器端的用户端和业务逻辑之间的集成
- 业务逻辑和服务器端在用户端独立集成
◆ 插图:一个应用程序的小例子
为了更具体地说明这些原则,我们将使用 Thomas Pierrain ( @tpierrain ) 和 Alistair Cockburn ( @TotherAlistair ) 本人于 2017 年提出的“六边形中的 Alistair”活动中使用的小示例。
这个小应用程序的目的是提供一个命令行程序,将诗歌写入控制台的标准输出。
此应用程序的预期输出示例:
$ ./printPoem
这里有一首诗: 我要睡觉 拍打文件 请轻声。 -- 正冈志贵 (1867 - 1902) 输入回车退出...
为了正确说明三个区域(用户端、业务逻辑、服务器端),此应用程序将在外部系统中搜索诗歌:一个文件。我们也可以将此应用程序连接到数据库,原理是相同的。
在这种情况下,我们如何应用这第一个原则,即分为三个区域?如何分配左侧(驱动什么)、中间(核心业务)和右侧(驱动什么)?
用户端
从用户的角度来看,该程序被呈现为一个控制台应用程序。所以控制台的概念将在左侧,在User-Side。用户将通过控制台驱动域。
服务器端
从技术上讲,在我们的例子中,诗歌存储在一个文件中。这个文件的概念可以在右侧的Server-Side上找到。业务将通过试行此右侧来发出其诗歌的请求,具体由 PoetryLibraryFileAdapter 实现。 在这里,如上所述,我们可以轻松地交换我们的诗歌来源(文件、数据库、Web 服务……)。因此,源文件作为文件的实际实现是一个技术细节(也称为技术实现细节)。
业务逻辑
在这种情况下,我们的核心业务,对用户有价值的,是读诗的概念。例如,我们可以使用 PoetryReader 类在代码中实现这个概念
用户端 → 业务逻辑交互
从业务的角度来看,请求来自控制台应用程序或其他应用程序并不重要,这是我们希望能够抽象的技术细节。这正是最初的意图之一:“既由用户驱动,又由测试驱动”。因此,业务逻辑中没有控制台的概念。然而,从用户的角度来看(= 它提供的服务),我们的应用程序确实允许请求诗歌。我们将在业务逻辑(由 IRequestVerses 实现)中找到这个概念,这将允许用户端与业务逻辑进行交互。
业务逻辑 → 服务器端交互
同样,从业务逻辑的角度来看,无论诗歌来自文件还是数据库,我们都希望能够独立于外部系统来测试我们的应用程序。业务逻辑中没有文件的概念。要进行操作,域仍然需要获取诗歌。我们在商业逻辑中以 IObtainPoems 接口的形式找到了获取诗歌的概念。正是这种获取诗歌的概念将允许域与服务器端交互。
注意:从这里开始,当您阅读图表时,您可以开始观察显示类之间关系的箭头。实线箭头表示调用或组合交互。没有填充的箭头表示继承关系(如在 UML 中)。但是不需要马上分析一切,我们稍后会详细探讨。
注意:IRequestVerses 和 IObtainPoems 这两个名称代表了很多接口,我们将按照一个原则来讨论它们。对于轶事,以“i”开头的接口名称的约定不再流行,但 Thomas Pierrain 将接口名称读取为第一人称单数的句子。IRequestVerses 内容为:例如,我请求经文。我喜欢这个主意。
◆ 原则:依赖进入内部
这是实现目标的基本原则。我们在前面的原则中已经开始看到这一点。
原则:依赖关系进入业务逻辑
该程序可以通过控制台和测试来控制,业务逻辑中没有控制台的概念。业务逻辑不依赖于用户端,而是用户端依赖于业务逻辑。用户端(ConsoleAdapter)依赖于诗歌请求的概念,IRequestVerses(它在用户方面定义了一个通用的“诗歌请求”机制)。
同样,程序可以独立于其外部系统进行测试,业务逻辑不依赖于服务器端,相反。服务器端依赖于业务逻辑,通过获取诗歌的概念,IObtainPoems。从技术上讲, Server-Side端的一个类会继承Business Logic中定义的接口并实现它,我们将在下面详细看到它,并讨论依赖倒置。
内外
如果我们将依赖关系 (<<depends on...>>) 视为箭头,则该原则将中心业务逻辑定义为内部,而其他一切都定义为外部(见图)。当我们讨论六边形建筑时,我们经常会发现这些内部和外部的概念。它甚至可以是记住和传递的基本点:依赖关系进入内部。
换句话说,一切都依赖于业务逻辑,业务逻辑不依赖于任何东西。Alistair Cockburn 坚持内部和外部的这种划分,这比User-Side和Server-Side的区别更结构化,以解决最初的问题。
◆ 原则:边界与接口隔离
总而言之,用户端代码通过业务代码中定义的接口(此处为 IRequestVerses)驱动业务代码。业务代码通过同样在业务代码中定义的接口(IObtainPoems)驱动服务器端。这些接口充当内部和外部之间的明确绝缘体。
一个比喻:端口和适配器
六边形架构使用端口和适配器的比喻来表示内部和外部之间的交互。如图,业务逻辑定义了端口,只要符合端口定义的规范,各种适配器都可以在端口上互换连接。
例如,我们可以想象业务逻辑的一个端口,我们将在该端口上连接单元测试期间的硬编码数据源,或集成测试中的真实数据库。只需在Server-Side编写相应的实现和适配器,业务逻辑不受此更改的影响。
这些由业务代码定义的接口,它们隔离并允许与外部世界交互,是Ports & Adapters比喻的端口。注意:如前所述,端口是由业务定义的,所以它们在里面。
另一方面,适配器代表外部代码,使端口与其余用户端代码或服务器端代码之间形成粘合剂。这里的适配器分别是 ConsoleAdapter 和 PoetryLibraryFileAdapter。这些适配器 在外面。
另一个比喻:六边形
正如我们在上图中看到的那样,为这种架构命名的另一个隐喻是六边形。为什么是六边形?主要原因是它是一个易于绘制的形状,为图表上的多个端口和适配器留出了空间。事实证明,即使六边形最终是相当轶事,六边形架构的表达比端口和适配器模式更流行。可能是因为它听起来更好?
理论部分已经结束,没有其他原则:对于其他一切,我们都是完全自由的。
◆ 细节:内部和外部的代码是如何组织的?
除了上面看到的原则之外,我们完全可以按照我们的意愿在每个区域内组织代码。
关于业务代码,内部,一个好主意是根据业务逻辑选择组织其模块(或目录) 。
要避免的一种组织是按类型对类进行分组。例如“ports”目录,或“repositories”目录(如果您使用此模式),或“services”目录。在您的业务代码中考虑 100% 的业务,包括模块或目录的组织!理想的情况是能够打开一个目录或一个业务逻辑模块并立即了解您的程序解决的业务问题;而不是只看到“repositories”、“services”或其他“managers”目录。
另请参阅此主题:
- https://medium.com/@msandin/strategies-for-organizing-code-2c9d690b6f33
- https://martinfowler.com/bliki/PresentationDomainDataLayering.html
◆ 细节:在运行时
您究竟如何实例化所有这些以满足运行时依赖关系?如果您使用的是依赖注入框架,您可能不需要问自己这个问题。但我认为要了解六边形架构,看看应用程序启动时会发生什么是很有趣的。为此,至少在本文期间不要使用依赖注入框架。
例如,如果我们手动实例化所有内容,我们将如何编写应用程序的入口点:
课堂节目
代码语言:javascript复制{
静态无效主要(字符串 [] 参数)
{
// 1. 实例化右侧适配器(“走出六边形”)
IObtainPoems fileAdapter = new PoetryLibraryFileAdapter(@".Peoms.txt");
// 2. 实例化六边形
IRequestVerses poetryReader = new PoetryReader(fileAdapter);
// 3. 实例化左侧适配器(“我想问/进入”)
var consoleAdapter = new ConsoleAdapter(poetryReader);
System.Console.WriteLine("这里有一些...");
控制台适配器.Ask();
System.Console.WriteLine("输入退出...");
System.Console.ReadLine();
}
}
实例化顺序通常是从右到左:
- 首先我们实例化Server-Side,这里是读取文件的 fileAdapter。
- 我们实例化将由应用程序驱动的Business Logic类,我们通过将 fileAdapter 注入到构造函数中的方式在其中注入文件适配器的poetryReader。
- 安装User-Side,consoleAdapter 将驱动诗歌阅读器并写入控制台。这里将poetryReader通过注入构造函数注入consoleAdapter。
我们说过,内在不应该依赖于外在。那么为什么我们将 fileAdapter(来自服务器端的代码)注入到来自业务逻辑的代码的poetryReader 中呢?
我们可以这样做,因为通过查看模式和代码,除了作为 PoetryLibraryFileAdapter ( Server-Side ) 之外,fileAdapter 还是 IObtainPoems 通过继承的实例。
在实践中,PoetryReader 并不依赖于 PoetryLibraryFileAdapter,而是依赖于 IObtainPoems,这在Business Logic代码中有很好的定义。您可以通过查看其构造函数的签名来检查它。
公共诗歌阅读器(IObtainPoems 诗歌图书馆) { this.poetryLibrary = 诗歌图书馆;}
PoetryLibraryFileAdapter 和 PoetryReader 是弱耦合的。
◆ 细节:右侧的依赖倒置
fileAdapter 的定义依赖于业务(此处为继承依赖),但在运行时,poetryReader 可以在实践中控制 fileAdapter 的实例,这是一个典型的依赖倒置案例。
事实上,如果没有 IObtainPoems 接口,业务代码将依赖于服务器端代码的定义,我们希望避免这种情况:
该接口允许反转此依赖项的方向:
除了使业务独立于外部系统之外,右边的这个接口还可以满足著名的SOLID D或依赖倒置原则。这个原则说:
- 高级模块不应依赖于低级模块。两者都必须依赖于抽象。
- 抽象不应该依赖于细节。细节必须依赖于抽象。
如果我们没有接口,我们将拥有一个依赖于低级模块(服务器端)的高级模块(业务逻辑)。
注意:对于左侧和业务代码的交互,依赖自然是正确的方向。
这种交互实现的差异与用户端/业务逻辑和业务逻辑/服务器端关系之间的差异有关。提醒:用户端驱动业务逻辑,服务器端由业务逻辑驱动。
◆ 细节:为什么左边有一个接口?
由于User-Side和Business Logic之间的依赖关系已经在正确的方向上,所以 IRequestVerses 接口的作用不是反转依赖关系。
但是,它仍然有一个好处:明确限制用户端代码和业务逻辑代码之间的耦合表面。
实际上,PoetryReader 类可以有其他方法,而不是 IRequestVerses 接口的方法。ConsoleAdapter 不知道这一点很重要。
它与另一个 SOLID 原则,接口隔离原则保持一致。
不应强迫客户依赖他们不使用的方法。
但是一旦你理解了意图,如果左边的端口只有一个方法,并且它的实现只有一个方法,就像我们的例子一样,接口真的有必要吗?使用一种动态语言,最终可以通过鸭子打字来工作吗?
我们可以回答一个问题:您的团队对此有何看法?每个人都清楚隔离的目标吗,不需要界面来触发对话?完全由您决定。
◆ 六边形架构中的测试
这种软件架构的一个重要好处是它促进了测试自动化,这是其最初意图的一部分。
◆ 如何从用户端替换一些代码?
一般情况下,左侧代码的作用可以直接由测试框架来扮演。确实,测试代码可以直接驱动业务逻辑代码。
注意:该图说明了一个集成测试,因为右侧部分没有被替换。它也可以更换,见下文。
◆ 如何替换服务器端的一些代码?
右边的代码必须由业务驱动。一般来说,如果你想编写一个单元测试,你可以根据你想要测试的内容用一个模拟或任何其他形式的测试替身来替换它。
目标达成!
允许应用程序由用户、程序、自动化测试或批处理脚本驱动,并且可以独立于其可能的执行系统和数据库进行开发和测试。
当心!这并不妨碍您测试您的用户端和服务器端代码,任何代码都值得测试。关于这个主题,我再次向您推荐“实践测试金字塔”系列。
事实上,通过结合我们是否替换,我们看到使用这个架构我们可以测试我们想要的:
- 单独的整个业务逻辑,
- 用户端和业务逻辑之间的集成,独立于服务器端
- 业务逻辑和服务器端之间的集成,在用户端独立
◆ 为了更进一步
作为一个团队谈论它,谁已经知道如何在家里做呢?
来吧,在现实生活中用你的代码做实验。例如,一个小型个人项目,或与您的团队合作的一个小型项目。对你来说什么容易,什么难?
以下是您在实施过程中可能遇到的一些其他问题:
- 一个端口可以只有一种方法,也可以组合多种方法。你的情况有什么意义?
- 即使它很好地遵循依赖关系原则,代码也不一定分成三个显式模块或目录或包或名称空间。正如在 Thomas Pierrain 的代码中一样,我在“域”目录中多次看到业务逻辑代码,以及在“基础设施”目录中的用户端和服务器端代码。在他的示例中,内部代码位于 HexagonalThis.Domain 命名空间中,外部代码位于 HexagonalThis.Infra 命名空间中。
快速提醒:没有灵丹妙药。六边形架构是复杂性和功能之间的一个很好的折衷方案,也是发现我们所讨论的主题的一种非常好的方式。但这只是众多解决方案中的一种。对于简单的情况,它可能太复杂,而对于复杂的情况,它可能太简单。还有其他值得探索的软件架构。例如,Clean Architecture在形式化和绝缘方面走得更远(带有额外的 SOLID)。或者在不同但兼容的轴上,CQRS可以更好地分离读取和写入。
◆ 参考
“六边形中的阿利斯泰尔”活动的视频在这里。此活动的代码位于Thomas Pierrain 的 github 上。
您还可以阅读有关此主题的这些好文章:
- http://alistair.cockburn.us/hexagonal-architecture
- http://wiki.c2.com/?HexagonalArchitecture/
- https://martinfowler.com/bliki/PresentationDomainDataLayering.html
- https://fr.slideshare.net/ThomasPierrain/coder-sans-peur-du-changement-avec-la-meme-pas-mal-hexagonal-architecture
- http://tpierrain.blogspot.fr/2016/04/hexagonal-layers.html
- http://alistair.cockburn.us/Configurable Dependency
- http://blog.cleancoder.com/uncle-bob/2016/01/04/ALittleArchitecture.html
来源:
https://blog.octo.com/en/hexagonal-architecture-three-principles-and-an-implementation-example/
“IT大咖说”欢迎广大技术人员投稿,投稿邮箱:aliang@itdks.com
来都来了,走啥走,留个言呗~
IT大咖说 | 关于版权
由“IT大咖说(ID:itdakashuo)”原创的文章,转载时请注明作者、出处及微信公众号。投稿、约稿、转载请加微信:ITDKS10(备注:投稿),茉莉小姐姐会及时与您联系!
感谢您对IT大咖说的热心支持!
- 相关推荐 推荐文章
- Redis 内存淘汰策略,从根儿上理解
- 这个牛逼了,基于(SpringBoot VUE)实现的自定义拖拽式智能大屏
- 终于有人把怎么搭建数据指标体系给讲明白了,数据分析师必备
- SpringBoot企业级技术中台微服务架构与服务能力开发平台
- SQLSERVER backup 命令总结
- MyBatisPlus又在搞事了!一个依赖轻松搞定权限问题!堪称神器
- 领导不懂IT技术,分不清报表和BI,看完这篇文章就懂了
- MIT开源协议,一款百分百开源、支持商用的亚马逊ERP系统
- 从架构师的角度带你把“响应式编程”给一次性搞明白,果然绝绝子
- 替代ELK:ClickHouse Kafka FlieBeat才是最绝的