端口和适配器架构

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 端口和适配器架构

2005年,Alistair Cockburn构思了端口和适配器架构 (又称六边形架构)并记录在他的博客中。下面这句话就是他对该架构的目标的定义:

让用户、程序、自动化测试和批处理脚本可以平等地驱动应用,让应用的开发和测试可以独立于其最终运行的设备和数据库。——Alistair Cockburn 2005,端口和适配器

有许多文章在谈及端口和适配器架构时会花很多篇幅在分层上。然而, 我并没有在 Alistair Cockburn 的原文中找到关于分层的只言片语。

其思想是将我们的应用看作是一个系统的中心交付物,输入和输出都是通过端口出入应用,这些端口将应用和外部工具、技术以及传达机制隔离开来。应用不应该关心是谁在发送输入或接收输出。这就是为了保护产品免受技术和业务需求演进的影响。由于技术/供应商锁定,这些演进可能导致产品刚开发没多久就被废弃。

我将在本文中剖析以下主题:

  • 传统架构方式的问题
  • 分层架构的演化
  • 什么是端口?
  • 什么是适配器?
  • 适配器的两种不同类型
  • 端口和适配器架构有哪些优势?
  • 实现隔离和技术隔离
  • 传达机制的隔离
  • 测试
  • 总结

◐  传统架构方式的问题


传统的架构方式在前端和后端都可能给我们带来问题。

在前端,业务逻辑最终可能会渗透到 UI(例如,我们把用例的逻辑放到控制器或视图里,导致这些逻辑不能在其它 UI 界面中重用), 甚至 UI 会反过来渗透到业务逻辑中(例如,我们会为了模板中需要的业务逻辑在实体中创建对应的方法)。

image.png

而在后端,我们可能会在自己的业务逻辑里使用外部类的类型提示、继承或者实例化它们,这会导致对这些外部的库和技术直接引用,最后任由它们渗透到业务逻辑中。

◐  分层架构的演化


托EBI (译)和DDD(译)的福, 2005 年我们已经知道了“系统中真正重要的是位于中间的层次”。业务逻辑(应该)存在于这些层次之中,它们才是我们和竞品的真正区别。这才是真正的“应用”。

image.png

但是,Alistair Cockburn 意识到 顶部和底部的层次从另一方面来说,就是应用的入口/出口。尽管实际中它们不一样,却有着十分相似的目标,在设计上也是对称的。而且,如果我们想要隔离出应用中间的层次,这些入口和出口能以另一种相似的方式使用。

image.png

区别于典型的分层架构图,我们将它们画在系统的左右两侧,而不是上下两边。

虽然我们识别出了系统中对称的两侧,但两侧都可能有若干入口/出口。例如, API和UI就是位于应用左侧的两个不同的入口/出口。为了表示应用有若干个入口/出口,我们把应用的形状改成了多边形。应用的形状可以是有多条边的任意多边形,但最终六边形获得了青睐。这也是“六边形架构”的由来。

image.png

端口和适配器架构使用了实现为端口和适配器的抽象层次,解决了传统架构方式带来的问题。

什么是端口?

端口是对其消费者无感知的进入/离开应用的入口和出口。在许多编程语言里,端口就是接口。例如,在搜索引擎里它可能是执行搜索的接口。在应用中,我们把这个接口当成入口/出口使用,而不用去关心它的具体实现,实际上在所有将接口定义为类型提示的地方,这些实现会被注入。

什么是适配器?

适配器是将一个接口转换(适配)成另一个接口的类。

例如,一个适配器实现了接口 A 并被注入了接口 B。当这个适配器被实例化时,一个实现了接口B的对象将从构造方法注入进来。实现了接口 A 的 对象会被注入到需要接口A的地方,然后接收方法请求,将其转换并代理给那个实现了接口B的内部对象。

如果我说的不够明白,别慌,后面我会给出一个更具体的例子。

适配器的两种不同类型

左侧代表 UI 的适配器被称为主适配器或者主动适配器,因为是它们发起了对应用的一些操作。而右侧表示和后端工具链接的适配器,被称为从适配器或者被动适配器,因为它们只会对主适配器的操作作出响应。

端口/适配器的用法也有一点区别:


  • 在左侧,适配器依赖端口,该端口的具体实现会被注入到适配器,这个实现包含了用例。换句话说,端口和它的具体实现(用例)都在应用内部。
  • 在右侧,适配器就是端口的具体实现,它自己将被注入到我们的业务逻辑中,尽管业务逻辑只知道接口。换句话说,端口在应用内部,而它的具体实现在应用之外并包装了某个外部工具。

image.png

◐  端口和适配器架构有哪些优势


使用这种应用位于系统中心的端口/适配器设计,让我们可以保持应用和实现细节之间的隔离,这些实现细节包括昙花一现的技术、工具和传达机制。它还让可重用的概念更容易更快速地得到验证并被创建出来。

实现隔离和技术隔离

上下文

我们的应用使用SOLR作为搜索引擎,并使用一个开源库连接它并执行搜索。

传统架构方式

传统架构方式下,我们会直接在我们的代码中使用库(SOLR)里的类,作为类型提示,或者实例化和/或作为我们实现的基类。

端口和适配器架构方式

如果采用端口和适配器架构的话,我们会创建一个接口,比如叫做 UserSearchInterface,在代码中用这个接口作为类型提示。我们还会为 SOLR 创建一个实现该接口的适配器,比如叫做 UserSearchSolrAdapter。这个实现是 SOLR 的包装,SOLR 会被注入其中并用来实现接口指定的方法。

问题

不久之后,我们想用Elasticsearch换掉SOLR。甚至,对于同样的搜索行为,我们希望有些时候使用SOLR,有些时候使用Elasticsearch,在运行时决定就好。

如果我们采用传统架构,我们需要查找所有使用SOLR的代码并替换成Elasticsearch。然而,这可不是简单的查找替换:两个引擎的用法不同,方法、输入、输出也不尽相同,替换并不是一件轻松的任务。而在运行时在决定使用那个引擎甚至是不可能的。

然而,假设我们使用了端口和适配器架构,我们只需要创建一个新的适配器,比如就叫UserSearchElasticsearchAdapter,在注入时使用它换掉SOLR的适配器,也许改一下DCI中的配置就可以做到。我们完全可以使用工厂来决定注入那个适配器,实现在运行时注入不同的实现。

传达机制的隔离

和上面这个例子类似,假设我们的应用需要 Web GUI,CLI 和 Web API。我们想在全部三种 UI 中提供某个功能,比如叫做UserProfileUpdate的功能。

使用端口和适配器架构的话,我们会在一个应用服务的方法中实现这个功能并将其作为一个用例。服务会实现一个接口,该接口说明了方法、输入以及输出。

每个版本的 UI 都有各自的控制器(或控制台命令)来通过这个接口触发期望的逻辑,应用服务接口的具体实现会被注入到 UI 中。这种情况下,适配器实际上就是控制器(或 CLI 命令)。

之后我们可以修改 UI,因为我们知道这些修改不会影响业务逻辑。

测试

上面两个例子中,使用端口和适配器架构会让测试更加容易。第一个例子中,我们用接口(端口)的 Mock 就可以测试应用,而不需要使用 SOLR 或 Elasticsearch 。

第二个例子中,所有的 UI 都可以独立于应用进行测试。我们的用例也可以独立于 UI 进行测试,传给服务一些输入再断言结果就好。

◐ 总结


在我看来,端口和适配器架构只有一个目标:将业务逻辑和系统使用的传达机制以及工具隔离。为此,它使用了常见的编程语言结构:接口。

在UI侧(主动适配器),我们创建使用应用接口的适配器,比如控制器。

在基础设施侧(被动适配器),我们创建实现应用接口的适配器,比如资源库。

这就是全部!

然而,我惊讶的发现早在十三年前同样的思想就已经公开发表了,尽管它没有刻意地强调要将工具和传达机制从应用核心中隔离出来。

系统和角色的任何交互都要通过边界对象。按照 Jacobson 的描述,角色可以是客户或者管理员(操作员)这样的人类用户,也可以是定时器或者打印机这样的非人类“用户”,它们分别对应着端口和适配器架构中的主动适配器和被动适配器。

◐  引用来源


  • 1992 – Ivar Jacobson – Object-Oriented Software Engineering: A use case driven approach
  • 200? – Alistair Cockburn – Hexagonal Architecture
  • 2005 – Alistair Cockburn – Ports and Adapters
  • 2012 – Benjamin Eberlei – OOP Business Applications: Entity, Boundary, Interactor
  • 2014 – Fideloper – Hexagonal Architecture
  • 2014 – Philip Brown – What is Hexagonal Architecture?
  • 2014 – Jan Stenberg – Exploring the Hexagonal Architecture
  • 2017 – Grzegorz Ziemoński – Hexagonal Architecture Is Powerful
  • 2017 – Shamik Mitra – Hello, Hexagonal Architecture
相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
4月前
软件复杂度问题之端口适配器架构划分系统,如何解决
软件复杂度问题之端口适配器架构划分系统,如何解决
|
5月前
网络编程中的互联网协议 , IP地址 , 域名 , 端口 , 架构 , 网页数据请求 , 响应码
网络编程中的互联网协议 , IP地址 , 域名 , 端口 , 架构 , 网页数据请求 , 响应码
|
Kubernetes Cloud Native Java
Rainbond ServiceMesh架构组件端口冲突处理方式
Rainbond 开箱即用的ServiceMesh架构默认通过 Sidecar 代理的方式,为我们透明的解决了分布式场景下组件间的通讯问题。但是我们实际的业务中经常会出现一种情况,那就是一个组件需要和多个其他组件通信,而这些组件使用的服务端口有可能会相同,这就会导致 envoy 在本地回环地址`127.0.0.1`起监听时出现端口冲突。本文介绍多种方式处理端口冲突问题。
1254 0
|
8天前
|
弹性计算 Kubernetes Cloud Native
云原生架构下的微服务设计原则与实践####
本文深入探讨了在云原生环境中,微服务架构的设计原则、关键技术及实践案例。通过剖析传统单体架构面临的挑战,引出微服务作为解决方案的优势,并详细阐述了微服务设计的几大核心原则:单一职责、独立部署、弹性伸缩和服务自治。文章还介绍了容器化技术、Kubernetes等云原生工具如何助力微服务的高效实施,并通过一个实际项目案例,展示了从服务拆分到持续集成/持续部署(CI/CD)流程的完整实现路径,为读者提供了宝贵的实践经验和启发。 ####
|
5天前
|
监控 安全 应用服务中间件
微服务架构下的API网关设计策略与实践####
本文深入探讨了在微服务架构下,API网关作为系统统一入口点的设计策略、实现细节及其在实际应用中的最佳实践。不同于传统的摘要概述,本部分将直接以一段精简的代码示例作为引子,展示一个基于NGINX的简单API网关配置片段,随后引出文章的核心内容,旨在通过具体实例激发读者兴趣,快速理解API网关在微服务架构中的关键作用及实现方式。 ```nginx server { listen 80; server_name api.example.com; location / { proxy_pass http://backend_service:5000;
|
6天前
|
缓存 监控 API
探索微服务架构中的API网关模式
随着微服务架构的兴起,API网关成为管理和服务间交互的关键组件。本文通过在线零售公司的案例,探讨了API网关在路由管理、认证授权、限流缓存、日志监控和协议转换等方面的优势,并详细介绍了使用Kong实现API网关的具体步骤。
24 3
|
7天前
|
运维 NoSQL Java
后端架构演进:微服务架构的优缺点与实战案例分析
【10月更文挑战第28天】本文探讨了微服务架构与单体架构的优缺点,并通过实战案例分析了微服务架构在实际应用中的表现。微服务架构具有高内聚、低耦合、独立部署等优势,但也面临分布式系统的复杂性和较高的运维成本。通过某电商平台的实际案例,展示了微服务架构在提升系统性能和团队协作效率方面的显著效果,同时也指出了其带来的挑战。
43 4
|
6天前
|
存储 缓存 监控
探索微服务架构中的API网关模式
探索微服务架构中的API网关模式
22 2
下一篇
无影云桌面