xfyuan
xfyuan A Chinese software engineer living and working in Chengdu. I love creating the future in digital worlds, big and small.

“端口—适配器”模式的概念(3)

“端口—适配器”模式的概念(3)

这是“端口—适配器”模式的概念梳理第三部分。

6.- PROS AND CONS

模块化和应用与技术的解耦,是六边形架构两个重要的特征。这些特征是优点和缺点的来源。下面是我发现的一些关于六边形架构的利与弊。

6.1 - PROS

6.1.1 - TESTABILITY IMPROVEMENT

这个架构所提供的主要收益就是能够独立于其依赖的外部设备来测试应用程序。这通过两件事来实现:

  • 对于每个驱动者端口,开发一个测试适配器以在该端口上测试其用例。
  • 对于每个从动者端口,开发一个 mock 适配器。

对六边形做隔离性测试对于这些很有用:

  • 运行回归测试。当源代码因某种原因变更时(添加了新功能,修复了一个 bug,等等),这些测试运行来确保如上改动没有在任何已存在功能上的边际效应。要运行这些测试,驱动者适配器使用一个自动化测试框架。
  • BDD (行为驱动开发)。对于每个驱动者端口的功能,一套验收标准是由用户定义的。当所有验收标准都吻合时,该功能就被看作“完成了”。这些验收标准被称为 scenarios,将被测试用例通过测试适配器来运行。要运行这些验收测试,适配器可以使用如 Cucumber 的工具。

Nat Pryce(Growing Object-Oriented Software, Guided by Tests 一书的合著者)在他的文章 Visualising Test Terminology 中定义了有关六边形架构各种不同的测试:

  • 单元测试(Unit Tests):测试六边形内的单个对象。
  • 集成测试(Integration Tests):测试适配器。它们确保在端口和外部世界之间的转换通过适配器正确地完成了。
  • 验收测试(Acceptance Tests):测试驱动者端口,比如,隔离中的六边形。它们检查应用程序有如用户预期那样的行为,符合他/她在之前用例中所定义的验收标准。
  • 系统测试(System Tests):一起测试整个系统,适配器和六边形。它们也测试系统的部署和启动。
6.1.2 - MAINTAINABILITY IMPROVEMENT

可维护的系统是那种易于修改的系统。六边形架构提升了可维护性,因为它提供了关注点跟业务逻辑解耦的分离,使得查找我们要更改的代码更加容易。

应用程序的可维护性是与技术债相关的长期概念。 可维护性越高,技术债就越少。因此,六边形架构减少了技术债。

6.1.3 - FLEXIBILITY

在不同的技术之间切换是容易的。对一个给定的端口,你可以有多个适配器,每个使用特定的技术。要选择使用其中一个,你只用配置好哪个适配器用于该端口即可。这种配置可以如同调整一个外部配置参数文件一样容易。不用修改源代码,不用重新编译,不用重新构建。

同样地,把一个新的特定技术的适配器添加到一个端口可以不用触及现有代码即可做到。这个适配器开发和编译它自己的。运行时,它会被检查并插入到该端口。

6.1.4 - APPLICATION IMMUNE TO TECHNOLOGY EVOLUTION

技术远比业务逻辑更频繁地进化着。在业务逻辑跟技术绑在一起的应用程序中,你不能进行技术变更而不触碰业务逻辑。这并不好,因为业务不应改变。

使用六边形架构,你想要升级的技术是位于应用程序外部的适配器中。你只用更改适配器就好。应用程序本身保持不变性,因为它不依赖于适配器。

6.1.5 - DELAY TECHNOLOGICAL DECISIONS

当你开始开发写代码时,会只专注在业务逻辑上,而推迟决断将要使用哪个框架和技术。你可以晚一点来选择一种技术,并为其编写适配器。

6.2 - CONS

6.2.1 - COMPLEXITY

一个实现了六边形架构的软件项目有复杂的结构,使用了很多的模块和定义在它们之间的明确依赖。我所说的模块是指源代码子项目(比如,Maven 模块),用于物理上分离架构的不同元素。

至少,会有一个用于六边形的模块,每个适配器一个模块,以及一个模块用于启动整个项目。你也必须定义模块之间的依赖:六边形不依赖任何东西,适配器依赖于六边形,而启动是依赖所有其他东西的。

如果编程语言不允许六边形只暴露端口,那么就会有甚至更多的模块了。你不得不把六边形拆分为端口及实现。六边形的实现和适配器将依赖于端口,而端口不依赖任何东西。

6.2.2 - BUILD PROCESS PERFORMANCE

由于我们已经看到的复杂性,如果项目太大,有太多适配器,那么编译、运行测试、把所有模块构建到一起,以及启动整个项目的过程将会花费许多许多的时间。

6.2.3 - INDIRECTION AND MAPPINGS

通过端口和适配器把应用程序跟技术解耦会增加间接性,比如,当适配器在端口和特定技术接口之间转换时对方法的额外调用。除此之外,可能还需要在应用程序跟外部世界对象之间进行映射。

7.- WHEN TO “HEXAGONAL THIS!”

或者你会这样说:“什么时候应该在项目上应用六边形架构?”好吧,答案恐怕令人不快:“得看情况”:

  • 对于小项目,也许“治愈胜过疾病”,所以解决琐碎的问题不值得使用该架构来增加额外的复杂性。
  • 对于中等/大型项目,有很长的生命周期,在其生涯中会进行多次修改,使用六边形架构就非常值得。

有些人可能说如果他们确认项目中要用的技术或框架不会变更(比如,由于某些原因被绑定到了特定技术),就不需要六边形架构了。好吧,即使在这种情况下,“端口-适配器”模式也很有用,因为你可以添加 mock 适配器,以便在应用程序所依赖的设备/服务不可用时使用,或者你可以为不同的运行时环境(开发、测试、生产)添加适配器。

8.- IMPLEMENTATION STEPS

起点是应用程序作为一个黑盒,所定义的端口接口围绕其四周,无论是在驱动者侧还是从动者侧,以此与外部世界进行交互。

起初可能你无法完整定义每个从动者端口,因为你仍然不完全了解应用程序对端口用途的所有需求。或者你可能错过某些从动者端口。但这些需求将会随着六边形内的开发而产生,比如,驱动者端口的实现。

所以,对于从头开发一个六边形应用程序,下面是适配器在驱动者与从动者两侧被构造和添加的顺序,直到完成所有工作:

8.1 - TEST DRIVER ADAPTERS / MOCK DRIVEN ADAPTERS

  • 驱动者侧(Driver side):对于每个驱动者端口,构造一个测试适配器,并由测试来驱动实现该驱动者端口。BDD 可被用于此处以实现驱动者端口,而测试用例会是 GWT scenarios
  • 从动者侧(Driven side):当在实现一个驱动者端口时,你可能需要使用从动者端口。这样的情况下,为其构造 mock 适配器。

一旦你实现了所有的驱动者端口和 mock 从动者端口,这一步就完成了。

此时,六边形是完整的,有测试在驱动者侧,mock 在从动者侧。应用程序是可以被隔离性测试的。

下一步是为每个端口添加所需的“真实”的驱动者适配器和从动者适配器,这依赖于外部世界的通信需求。例如,你可能需要 Web UI 和 REST API 适配器在驱动者侧,而 SQL 数据库和 app-to-app 适配器在从动者侧。

8.2 - REAL DRIVER ADAPTERS / MOCK DRIVEN ADAPTERS

  • 驱动者侧(Driver side):对于每个驱动者端口,构造并添加你需要的“真实”的驱动者适配器。例如,Web UI, REST API,等等。
  • 从动者侧(Driven side):保留你在步骤一中构造的 mock 适配器。

这样你就可以测试新的驱动者适配器了。

8.3 - TEST DRIVER ADAPTERS / REAL DRIVEN ADAPTERS

  • 驱动者侧(Driver side):配置每个驱动者端口以使用在步骤一中构造的测试驱动者适配器。
  • 从动者侧(Driven side):对于每个从动者端口,构造并添加你需要的“真实”的从动者适配器。例如,数据库适配器,邮件提醒适配器,等等。

这样你就可以测试新的从动者适配器了。

8.4 - REAL DRIVER ADAPTERS / REAL DRIVEN ADAPTERS

  • 驱动者侧(Driver side):配置每个驱动者端口使用在步骤二中构造的“真实”的驱动者适配器。
  • 从动者侧(Driven side):配置每个从动者端口使用在步骤三中构造的“真实”的从动者适配器。

这样你就可以对应用程序进行端到端测试了,在驱动者侧和从动者侧都包含“真实”的适配器。

到这一步你就全部完成了。你可以用期望的适配器来配置每个端口,并以端口与适配器配置的任意组合来运行应用程序。

comments powered by Disqus