清洁架构系列 - 第一部分
我听过太多关于六边形架构、洋葱架构和Clean架构的说法,一直很疑惑它们到底是什么意思。过去几个月我学到了很多,想和大家分享一下。
我相信大多数软件开发人员都听说过整洁代码、SOLID原则、整洁架构、六边形架构、洋葱架构等等;但我怀疑他们中的大多数人并不真正了解这些软件开发领域中那些令人惊叹的概念。如果您了解它们,或者想要了解更多,欢迎阅读我的系列文章,我将分享我在整洁架构方面的所有心得体会。
我将尝试在不同的文章中解释我所学到的知识,大致遵循以下结构:
- 六边形建筑(阿利斯泰尔·科克伯恩)
- 洋葱架构 + 领域驱动设计(Jeffrey Palermo)
- 清洁架构(鲍勃大叔)
- 什么、为什么、何时
- 如何在 .NET Core API 中遵循整洁架构
- 附加曲目:.NET Core 中的 Clean Architecture + GraphQL (HotChocolate)
六边形建筑
那么,让我们开始学习六边形架构,也称为端口和适配器模式(我更喜欢这个名称)。即使你不知道这种模式的含义,你也可能已经在代码中使用过它了。
2005 年,Alistair Cockburn 在他的一篇文章中推广了这种模式。关于这种模式的年代,我想引用Juan Manuel Garrido de Paz 的话,他在一篇关于端口和适配器的精彩文章中提到了这一点:
如果你在想……“这篇文章是不是太老了?软件开发是一个不断发展的领域,新技术和框架层出不穷,每天都在淘汰我们昨天用过的,它怎么还能有价值呢?”答案就在问题本身。端口与适配器模式提倡与技术和框架解耦。所以,它并不算老。好的事物永不过时,就像美酒一样,越陈越香。
简要介绍之后,让我们继续来看一下著名的六边形图:
根据这张图,我们可以看到端口和适配器模式的三个重要要素。
- 应用程序:用六边形表示(之所以采用六边形,是因为这样更容易在其周围绘制元素),它包含了应用程序执行需求中定义的业务规则所需的所有业务逻辑。最初的“端口和适配器”文章完全没有提及六边形/应用程序内部的代码结构(http://web.archive.org/web/20180422210157/http://alistair.cockburn.us/Hexagonal+Architecture)。我读过很多关于领域驱动设计(DDD)或应用层的文章,但Alistair Cockburn并没有写如何编写代码,他只是提倡端口和适配器的概念,我稍后会尝试解释。
- 驱动者(主要参与者):与应用程序交互以获取响应或执行命令的外部参与者。换句话说,就是我们应用程序的用户。
- 驱动型参与者(辅助参与者):这些参与者由我们的应用程序触发。驱动型参与者有两种类型:存储库(应用程序可以从中获取数据,例如数据库)和接收者(应用程序仅向其发送信息,例如 SMTP 服务器)。
主要参与者和次要参与者的区别仅在于谁发起对话。
所以……那些接口和适配器到底在哪儿?
港口
端口位于参与者和应用程序核心之间。它们是应用程序的一部分,位于六边形的边缘,负责处理特定目的的交互。它们是应用程序的边界,或者换句话说,它们是应用程序与外部参与者通信所需的接口。
驱动端口是应用程序的API。这意味着它们提供了一种以编程方式访问服务以实现特定行为或输出的方法。想象一下,在 API 中,控制器调用的接口类型就是一个驱动端口。
另一方面,驱动端口是应用程序的SPI(服务提供商接口)。SPI 描述了您为了实现特定目标而扩展和实现的类、接口、方法等。它是扩展或改变软件应用程序行为的一种方式。换句话说,假设我们的某个 API 用于发送通知,那么驱动端口就是我们为该方法创建的用于发送通知的接口类型。
请记住,端口与技术无关,它们并不知道例如此通知是否正确发送,或者您是否在向您的老板发送垃圾邮件。驱动适配器将实现此驱动端口并处理这些问题。
适配器
适配器是一种软件组件,它允许特定技术与六边形的端口进行交互。适配器位于端口层之外。有些人说它们完全位于端口之外,有些人则说它们位于端口之外的上层父级六边形中,所以我倾向于认为它们是端口之外的。就是这样。
驱动程序适配器使用驱动程序端口接口,将特定技术的请求转换为与技术无关的驱动程序端口请求。在 API 中,控制器充当驱动程序适配器的角色,接收来自 UI 的请求并调用驱动程序端口的方法。
驱动适配器实现了驱动端口接口,将端口中与技术无关的方法转换为特定技术的方法。例如,为了记住用于发送通知的驱动端口,驱动适配器会实现该端口,并使用取决于编程语言的电子邮件库来发送电子邮件。
这种模式赋予你模拟一切的灵活性,更重要的是,你可以轻松地在不同技术之间切换。例如,如果你使用 SQL Server 库作为数据库驱动适配器,而需要切换到 MongoDB,这应该不会太难。端口和适配器模式会在某种程度上强制你在架构和代码中遵循 SOLID 原则,并带来使用这些原则的所有好处。
概括
我要引用胡安·曼努埃尔·加里多·德·帕斯关于端口和适配器的文章中的所有内容,因为它简直太完美了。
- 六边形—— 应用程序
- 驱动程序端口 — 应用程序提供的 API
- 驱动端口——应用程序所需
- 参与者 ——与应用程序交互的环境设备
- 驱动程序 — 应用程序用户(包括人或硬件/软件设备)
- 驱动型参与者——提供应用程序所需的服务
- 适配器—— 用于将特定技术应用于特定应用
- 驱动程序适配器 — 使用驱动程序的端口
- 驱动适配器 — 实现驱动端口
另外,我想澄清一下关于六边形本身的一点。其他文章中有人提到过它,但有些人误解了阿利斯泰尔·科克伯恩对六边形形状的运用。
在原文中,阿利斯泰尔指出,之所以采用六边形,是因为它便于突出内外不对称性以及端口的特性。“六边形建筑”一词正是源于这种视觉效果,与任何特定形状无关。
好处
- 可测试性。如果正确遵循此模式,所有代码都必须是隔离的,以便所有内容都可以进行模拟。
- 可维护性。我们之前讨论过,如果您需要扩展适配器,由于我们实现了以下端口和适配器之间的良好隔离,因此可以轻松完成。同样,如果我们需要将适配器实现切换到使用其他技术(例如数据库、SMTP 或队列),也可以轻松实现。
- 识别技术债务。技术债务不可避免,但遵循这种方法,您将能够识别代码异味,并从工作量和时间的角度衡量技术债务。
缺点
- 复杂性。对我来说,这是唯一真正的问题。团队中的每个人都必须目标一致,这种架构才能有效运作。否则,代码和架构都会变得一团糟,而且无法获得之前提到的任何好处。
示例仓库
本系列文章旨在阐述一个清晰的架构,在回顾完所有枯燥的理论之后,我会提供代码示例。但如果您想了解其他人如何使用六边形架构,可以参考以下 GitHub 代码库:
- .NET Core:https://github.com/ivanpaulovich/hexagonal-architecture-acerola
- Java:https://github.com/iluwatar/java-design-patterns/tree/master/hexagonal
- NodeJS:https://github.com/jbuget/nodejs-clean-architecture-app
- 前往:https://github.com/Holmes89/hex-example
我希望我已经解释清楚了六边形架构是什么,也希望你现在对这篇文章的反应不是这样的:
我们下一篇关于洋葱架构和领域驱动设计的文章将很快与您见面!
如果您喜欢这篇文章,请分享并点赞!
参考
- http://alistair.cockburn.us/Hexagonal+architecture(必读)
- http://web.archive.org/web/20180528132445/http://alistair.cockburn.us/Hexagonal+Architecture
- http://wiki.c2.com/?HexagonalArchitecture
- http://wiki.c2.com/?PortsAndAdaptersArchitecture
- https://our-academy.org/posts/arquitectura-hexagonal (西班牙语)
- https://softwarecampament.wordpress.com/portsadapters(必选)
- https://www.youtube.com/watch?v=fGaJHEgonKg(视频来自 Marcus Biel)
- https://www.youtube.com/watch?v=th4AgBcrEHA(视频)
- https://beyondxscratch.com/2017/08/19/decoupling-your-technical-code-from-your-business-logic-with-the-hexagonal-architecture-hexarch/
- https://codely.tv/blog/screencasts/arquitectura-hexagonal-ddd/ (西班牙语)
- http://mussabsharif.blogspot.com/2011/08/api-vs-spi.html
- https://blog.ndepend.com/hexagonal-architecture/
- http://www.dossier-andreas.net/software_architecture/ports_and_adapters.html
- https://hackernoon.com/hexagonal-architecture-with-kotlin-ktor-and-guice-f1b68fbdf2d9
- https://stackoverflow.com/questions/2954372/difference-between-spi-and-api
- http://thinkmicroservices.com/blog/2018/hexagonal-architecture.html

