shusheng007
Published on 2025-02-23 / 40 Visits
0
0

六边形架构(hexagonal architecture)与洋葱架构(onion architecture)一看就懂

[版权申明] 非商业目的注明出处可自由转载 出自:shusheng007

概述

软件开发到目前已经发展了70多年了,在此期间相关从业者一直在孜孜不倦的使其所开发的软件具有更高的可扩展性、可测试性、可维护性... 总之不希望自己的软件是一锤子买卖!于是这么多年来发展出了各式各样的架构方法。

最近对我们的服务进行了一些架构方面的重构,借此机会简单聊一聊两个非常有用的架构方法:六边形架构(hexagonal architecture)和洋葱架构(onion architecture)。

架构设计之目标

软件系统为什么需要架构,架构要解决的问题是什么?这是个非常大的话题,而且有很多学术名称,这里是本人的一些比较粗鄙的认知,希望可以抛砖引玉。

首先对于一个持续产生价值的软件系统,其必然是以满足业务需求为前提的,但是在此前提下也不能放任软件质量持续下降,不然最终业务目标也会变的无法实现,于是软件架构也就随之出现并不断地发展至今。

软件架构中最为重要的一条就是:分离关注点,也可以概括为高内聚,低耦合。只要合理的做到了这一点,软件系统自然就会变得可扩展、可测试、可维护了。

分层架构

分层架构也许是我们最为熟悉和常用的一种软件架构,我第一次接触软件架构时遇到的就是三层架构。

View层(表现层)、Service层(业务逻辑层)、DAO层(数据访问层)

three-lays.drawio.png

分层架构有什么弊端呢?

从图中可以看出,我们的业务逻辑主要集中在Service层中,而其有很多对外的依赖,例如数据库,消息中间件、第三方服务等等。 这些外部依赖是非业务相关的,是不稳定的,是会变化的。例如第三方服务的API变了,MQ更换,或者进行了不兼容升级...,这些都会对我们的业务层造成影响,我们需要去修改我们的业务层去适配这些变化,这就有可能出问题。由于外部依赖变动引起核心业务修改的软件系统是很糟糕的。

于是就有人提出了六边形架构来解决这个问题。

六边形架构

六边形架构(hexagonal architecture)是由Alistair Cockburn在2005年提出的。Cockburn后来将其命名为端口和适配器模式(Port and Adapter Pattern),但大多数人仍然喜欢使用以前的名称六边形架构。原文可见hexagonal architecture

我们的前辈在很多年前就明白了业务应该位于最核心的地方,引起其变动的应该只有业务自身。但是在开发过程中不断地有内部的业务逻辑逃逸到外部,与外部构件纠缠在一起,久而久之就会导致核心业务与外部构件紧密的耦合在一起,导致应用程序变得非常难于扩展和维护...

于是作者借鉴硬件设备的概念,提出了六边形架构。以计算机为例,主板上会提供各种标准协议的插槽(port),例如显卡、声卡、网卡等,任何硬件厂家都可以按照自己的方式生产这些外围设置,只要适配这些卡槽即可。

其结构图如下所示:

hexagonal-architecture.drawio.png

结构和组成

  • Application core,应用程序核心,上图的六边形部分。这是我们应用的边界,就是我们的程序究竟在做什么事。如果按照DDD的概念来说的话,这部分包括领域模型、领域服务和应用服务。

  • Port,端口,其是我们基于实际需求定义的接口。例如我们的程序要进行持久化(数据库,文件...),就可以定义一个接口 XxxRepositoryPort,然后在其中设计方法。再定义一个 XxxRepositoryPortAdapter 来实现这个接口,在这个适配器内完成数据的持久化操作,切记不要包含业务,仅仅包含持久化相关的操作。

    Port是个接口,虽说大部分时候是你自己定义自己实现,但是仍然应该尽可能的用心设计,使其尽可能的稳定。

  • Adapter,适配器。Adapter负责使用外部构件来完成Port中定义的功能,随着应用程序的迭代有可能对适配器进行修改,也有可能使用新的适配器替换旧的适配器。

从上图中可以可以看到,六边形被分成了左右两部分。左边是 primary/driving adapters,右边是 secondary/driven adapters 那这两部分有什么区别呢?

大部分程序都是外界驱动型的,也就是从左边给程序一个驱动因素,程序在内部调用右边的外部构件完成业务然后给出结果。例如一个Web购物服务,当用户点击页面上的下单按钮,其就会在数据库中生成订单数据并告知用户。

Driving adapter

这些adapter比较难以理解,个人认为这些adapter有点牵强。既然有adapter那么Port是什么?假设我们有一个web服务,同一个功能外界可以通过GUI 访问,也可以通过API访问,还可以通过命令行CLI访问,那么port是什么?这种情况我们一般认为我们的用户案例(usecase)就是我们的port,每种表现类型被认为是适配器,我们一般不会为其定义单独的接口。

值得注意的是,我们的服务有时会被MQ消息驱动,例如被Kafka、RabbitMQ或者Azure的Eventhub 消息驱动,这些也应该位于左侧。

Driven adapter

这些adapter较为容易理解,当我们要依赖外部构件时就会使用到。假设我们要开发一个支付功能,需要使用微信支付和支付宝支付的服务,那么我们就可以定义一个支付相关的Port,我们可以为支付宝支付和微信支付各写一个Adapter,业务逻辑只认识Port,不认识具体的Adapter,当用户支付时才会选择使用哪种支付,程序就会调用相应的Adapter。

注意, Port数量根据实际定义,不能认为叫六边形架构就只能有6个port

适合场景

  • 多种输入输出方式: 当系统需要支持多种输入输出方式(如 RESTful API、消息队列、命令行等)时,六边形架构通过适配器模式,方便地将不同的输入输出方式与核心业务逻辑连接。
  • 外部系统替换: 如果系统需要与多个外部系统交互,且这些外部系统可能会变化或替换,六边形架构通过端口和适配器的设计,使得替换外部系统时对核心业务逻辑的影响最小。

洋葱架构(onion architecture)

洋葱圈架构是由 Jeffrey Palermo 在2008年提出的,本尊如下,其系列博文可访问 onion architecture

Jeffrey-Palermo.jpeg

通过前文所诉,我们的前辈在很多年前就明白了业务应该位于最核心的地方,引起其变动的应该只有业务自身。

洋葱架构关注的是如何将代码按照不同的职责组织为同心圆,而各层之间的依赖是从外向内的,如下图所示。

onion.drawio.png

相信你看了上图就会瞬间明白为什么这个软件架构叫洋葱圈架构了,因为它就像是一个洋葱被横着切了一刀后的样子。

结构和组成:

  • 核心层:包含领域模型、核心业务逻辑。
  • 应用层:处理业务逻辑的用例。
  • 基础设施层:数据库、文件系统等外部资源的访问层。
  • 表示层:UI、API、控制器等。

洋葱圈架构有如下特征:

  • 应用程序是围绕领域模型构建的(The application is built around an independent object model)
  • 内层定义接口,外层实现接口(Inner layers define interfaces. Outer layers implement interfaces)
  • 所有层的依赖方向是从外向内的(Direction of coupling is toward the center)
  • 程序的核心代码在没有基础设施的情况下可以正常编译和运行(All application core code can be compiled and run separate from infrastructure)

让我来解释一下这个架构模式:

首先,所有的业务模型和核心逻辑位于同心圆的中心(上图中的domain model 与 domain service)。这块的代码不依赖同心圆的任何其他层的代码。那有的人要问:那它什么都不能依赖了?那怎么可能呢?例如使用Java开发,那么Java平台的类库肯定都是要依赖的。总之一句话,这一块的代码,对外的依赖越少越好。

那这么做有什么好处呢?好处就是你的核心业务不受外界变化的影响,因为不依赖任何人,所以特别稳定,唯一引起它变化的就是产品经理(业务变更)!

其次,你的程序要想具有价值不可能不依赖其他组件。例如你的程序需要依赖数据库,需要依赖第三方服务等等。那么按照这个逻辑思考,自然而然的这些组件就应该位于同心圆的最中心,这是非常朴素的道理,那不就和这个架构冲突了吗?直到此刻,软件设计中一个伟大的思想登场了,它就是:依赖倒置

明明是我们的业务要依赖数据库,怎么才能使其变成数据库依赖我们的业务代码呢?那就是通过在我们的核心

业务层按照业务需求定义接口,数据访问层去实现接口,这样就实现了使数据访问层依赖了业务层。

最后,同心圆的依赖方向由外向内,越不稳定越靠外。

实用场景

  • 复杂业务逻辑: 当系统包含复杂的业务规则和领域模型时,洋葱架构有助于将业务逻辑与技术实现分离,提升代码的可维护性和可测试性。
  • 领域驱动设计(DDD): 洋葱架构与 DDD 的思想高度契合,适用于需要深入建模业务领域的场景。

与领域驱动设计(DDD)结合

首先这两种架构在理论上都可以独立于DDD来使用,但是不可否认的是其设计思想应该都受到了了DDD的启发,特别是洋葱圈架构。所以对于复杂的企业级应用可以将三者结合起来使用,六边形架构的设计思想可以指导DDD的防腐层的设计。

实例

说了这么多理论,让我们看一个实例吧,毕竟:talk is cheep , show me code!

在实际场景时,当开发一个业务比较复杂维护周期比较长的企业级应用时,我们可以考虑将洋葱圈架构与六边形架构结合使用,再配合DDD效果是比较好的。

下面是一个简单的demo程序,源码见 GitHub 源码

image.png

结语

今天我们谈论的两种架构方式都已经20多岁了了,可是现在仍然被广泛使用,足见其生命力之强盛。在IT这个日新月异的行业来讲算是非常有性价比的知识了,希望有缘人要认真学会。


Comment