原文:https://herbertograca.com/2017/09/14/ports-adapters-architecture/
這篇文章是軟件架構(gòu)編年史(譯)的一部分燎潮,這部編年史由一系列關(guān)于軟件架構(gòu)的文章組成腥寇。在這一系列文章中嘿悬,我將寫下我對(duì)軟件架構(gòu)的學(xué)習(xí)和思考,以及我是如何運(yùn)用這些知識(shí)的棚品。如果你閱讀了這個(gè)系列中之前的文章,本篇文章的的內(nèi)容將更有意義。
2005年,Alistair Cockburn構(gòu)思了端口和適配器架構(gòu) (又稱六邊形架構(gòu))并記錄在他的博客中陨献。下面這句話就是他對(duì)該架構(gòu)的目標(biāo)的定義:
讓用戶、程序懂更、自動(dòng)化測(cè)試和批處理腳本可以平等地驅(qū)動(dòng)應(yīng)用,讓應(yīng)用的開(kāi)發(fā)和測(cè)試可以獨(dú)立于其最終運(yùn)行的設(shè)備和數(shù)據(jù)庫(kù)急膀【谛——Alistair Cockburn 2005,端口和適配器
有許多文章在談及端口和適配器架構(gòu)時(shí)會(huì)花很多篇幅在分層上卓嫂。然而慷暂, 我并沒(méi)有在 Alistair Cockburn 的原文中找到關(guān)于分層的只言片語(yǔ)。
其思想是將我們的應(yīng)用看作是一個(gè)系統(tǒng)的中心交付物晨雳,輸入和輸出都是通過(guò)端口出入應(yīng)用行瑞,這些端口將應(yīng)用和外部工具、技術(shù)以及傳達(dá)機(jī)制隔離開(kāi)來(lái)餐禁。應(yīng)用不應(yīng)該關(guān)心是誰(shuí)在發(fā)送輸入或接收輸出血久。這就是為了保護(hù)產(chǎn)品免受技術(shù)和業(yè)務(wù)需求演進(jìn)的影響。由于技術(shù)/供應(yīng)商鎖定帮非,這些演進(jìn)可能導(dǎo)致產(chǎn)品剛開(kāi)發(fā)沒(méi)多久就被廢棄氧吐。
我將在本文中剖析以下主題:
- 傳統(tǒng)架構(gòu)方式的問(wèn)題
- 分層架構(gòu)的演化
- 什么是端口?
- 什么是適配器末盔?
- 適配器的兩種不同類型
- 端口和適配器架構(gòu)有哪些優(yōu)勢(shì)?
- 實(shí)現(xiàn)隔離和技術(shù)隔離
- 傳達(dá)機(jī)制的隔離
- 測(cè)試
- 總結(jié)
傳統(tǒng)架構(gòu)方式的問(wèn)題
傳統(tǒng)的架構(gòu)方式在前端和后端都可能給我們帶來(lái)問(wèn)題筑舅。
在前端,業(yè)務(wù)邏輯最終可能會(huì)滲透到 UI(例如陨舱,我們把用例的邏輯放到控制器或視圖里翠拣,導(dǎo)致這些邏輯不能在其它 UI 界面中重用), 甚至 UI 會(huì)反過(guò)來(lái)滲透到業(yè)務(wù)邏輯中(例如游盲,我們會(huì)為了模板中需要的業(yè)務(wù)邏輯在實(shí)體中創(chuàng)建對(duì)應(yīng)的方法)误墓。
而在后端蛮粮,我們可能會(huì)在自己的業(yè)務(wù)邏輯里使用外部類的類型提示、繼承或者實(shí)例化它們优烧,這會(huì)導(dǎo)致對(duì)這些外部的庫(kù)和技術(shù)直接引用蝉揍,最后任由它們滲透到業(yè)務(wù)邏輯中。
分層架構(gòu)的演化
托EBI (譯)和DDD(譯)的福, 2005 年我們已經(jīng)知道了“系統(tǒng)中真正重要的是位于中間的層次”畦娄。業(yè)務(wù)邏輯(應(yīng)該)存在于這些層次之中又沾,它們才是我們和競(jìng)品的真正區(qū)別。這才是真正的“應(yīng)用”熙卡。
但是杖刷,Alistair Cockburn 意識(shí)到 頂部和底部的層次從另一方面來(lái)說(shuō),就是應(yīng)用的入口/出口驳癌。盡管實(shí)際中它們不一樣滑燃,卻有著十分相似的目標(biāo),在設(shè)計(jì)上也是對(duì)稱的颓鲜。而且表窘,如果我們想要隔離出應(yīng)用中間的層次,這些入口和出口能以另一種相似的方式使用甜滨。
區(qū)別于典型的分層架構(gòu)圖乐严,我們將它們畫在系統(tǒng)的左右兩側(cè),而不是上下兩邊衣摩。
雖然我們識(shí)別出了系統(tǒng)中對(duì)稱的兩側(cè)昂验,但兩側(cè)都可能有若干入口/出口。例如艾扮, API和UI就是位于應(yīng)用左側(cè)的兩個(gè)不同的入口/出口既琴。為了表示應(yīng)用有若干個(gè)入口/出口,我們把應(yīng)用的形狀改成了多邊形泡嘴。應(yīng)用的形狀可以是有多條邊的任意多邊形甫恩,但最終六邊形獲得了青睞。這也是“六邊形架構(gòu)”的由來(lái)磕诊。
端口和適配器架構(gòu)使用了實(shí)現(xiàn)為端口和適配器的抽象層次填物,解決了傳統(tǒng)架構(gòu)方式帶來(lái)的問(wèn)題。
什么是端口霎终?
端口是對(duì)其消費(fèi)者無(wú)感知的進(jìn)入/離開(kāi)應(yīng)用的入口和出口滞磺。在許多編程語(yǔ)言里,端口就是接口莱褒。例如击困,在搜索引擎里它可能是執(zhí)行搜索的接口。在應(yīng)用中,我們把這個(gè)接口當(dāng)成入口/出口使用阅茶,而不用去關(guān)心它的具體實(shí)現(xiàn)蛛枚,實(shí)際上在所有將接口定義為類型提示的地方,這些實(shí)現(xiàn)會(huì)被注入脸哀。
什么是適配器蹦浦?
適配器是將一個(gè)接口轉(zhuǎn)換(適配)成另一個(gè)接口的類。
例如撞蜂,一個(gè)適配器實(shí)現(xiàn)了接口 A 并被注入了接口 B盲镶。當(dāng)這個(gè)適配器被實(shí)例化時(shí),一個(gè)實(shí)現(xiàn)了接口B的對(duì)象將從構(gòu)造方法注入進(jìn)來(lái)蝌诡。實(shí)現(xiàn)了接口 A 的 對(duì)象會(huì)被注入到需要接口A的地方溉贿,然后接收方法請(qǐng)求,將其轉(zhuǎn)換并代理給那個(gè)實(shí)現(xiàn)了接口B的內(nèi)部對(duì)象浦旱。
如果我說(shuō)的不夠明白宇色,別慌,后面我會(huì)給出一個(gè)更具體的例子颁湖。
適配器的兩種不同類型
左側(cè)代表 UI 的適配器被稱為主適配器或者主動(dòng)適配器宣蠕,因?yàn)槭撬鼈儼l(fā)起了對(duì)應(yīng)用的一些操作。而右側(cè)表示和后端工具鏈接的適配器甥捺,被稱為從適配器或者被動(dòng)適配器植影,因?yàn)樗鼈冎粫?huì)對(duì)主適配器的操作作出響應(yīng)。
端口/適配器的用法也有一點(diǎn)區(qū)別:
- 在左側(cè)涎永,適配器依賴端口,該端口的具體實(shí)現(xiàn)會(huì)被注入到適配器鹿响,這個(gè)實(shí)現(xiàn)包含了用例羡微。換句話說(shuō),端口和它的具體實(shí)現(xiàn)(用例)都在應(yīng)用內(nèi)部惶我。
- 在右側(cè)妈倔,適配器就是端口的具體實(shí)現(xiàn),它自己將被注入到我們的業(yè)務(wù)邏輯中绸贡,盡管業(yè)務(wù)邏輯只知道接口盯蝴。換句話說(shuō),端口在應(yīng)用內(nèi)部听怕,而它的具體實(shí)現(xiàn)在應(yīng)用之外并包裝了某個(gè)外部工具捧挺。
端口和適配器架構(gòu)有哪些優(yōu)勢(shì)?
使用這種應(yīng)用位于系統(tǒng)中心的端口/適配器設(shè)計(jì)尿瞭,讓我們可以保持應(yīng)用和實(shí)現(xiàn)細(xì)節(jié)之間的隔離闽烙,這些實(shí)現(xiàn)細(xì)節(jié)包括曇花一現(xiàn)的技術(shù)、工具和傳達(dá)機(jī)制声搁。它還讓可重用的概念更容易更快速地得到驗(yàn)證并被創(chuàng)建出來(lái)黑竞。
實(shí)現(xiàn)隔離和技術(shù)隔離
上下文
我們的應(yīng)用使用SOLR作為搜索引擎捕发,并使用一個(gè)開(kāi)源庫(kù)連接它并執(zhí)行搜索。
傳統(tǒng)架構(gòu)方式
傳統(tǒng)架構(gòu)方式下很魂,我們會(huì)直接在我們的代碼中使用庫(kù)(SOLR)里的類扎酷,作為類型提示,或者實(shí)例化和/或作為我們實(shí)現(xiàn)的基類遏匆。
端口和適配器架構(gòu)方式
如果采用端口和適配器架構(gòu)的話法挨,我們會(huì)創(chuàng)建一個(gè)接口,比如叫做 UserSearchInterface拉岁,在代碼中用這個(gè)接口作為類型提示坷剧。我們還會(huì)為 SOLR 創(chuàng)建一個(gè)實(shí)現(xiàn)該接口的適配器,比如叫做 UserSearchSolrAdapter喊暖。這個(gè)實(shí)現(xiàn)是 SOLR 的包裝惫企,SOLR 會(huì)被注入其中并用來(lái)實(shí)現(xiàn)接口指定的方法。
問(wèn)題
不久之后陵叽,我們想用Elasticsearch換掉SOLR狞尔。甚至,對(duì)于同樣的搜索行為巩掺,我們希望有些時(shí)候使用SOLR偏序,有些時(shí)候使用Elasticsearch,在運(yùn)行時(shí)決定就好胖替。
如果我們采用傳統(tǒng)架構(gòu)研儒,我們需要查找所有使用SOLR的代碼并替換成Elasticsearch。然而独令,這可不是簡(jiǎn)單的查找替換:兩個(gè)引擎的用法不同端朵,方法、輸入燃箭、輸出也不盡相同冲呢,替換并不是一件輕松的任務(wù)。而在運(yùn)行時(shí)在決定使用那個(gè)引擎甚至是不可能的招狸。
然而敬拓,假設(shè)我們使用了端口和適配器架構(gòu),我們只需要?jiǎng)?chuàng)建一個(gè)新的適配器裙戏,比如就叫UserSearchElasticsearchAdapter乘凸,在注入時(shí)使用它換掉SOLR的適配器,也許改一下DCI中的配置就可以做到挽懦。我們完全可以使用工廠來(lái)決定注入那個(gè)適配器翰意,實(shí)現(xiàn)在運(yùn)行時(shí)注入不同的實(shí)現(xiàn)。
傳達(dá)機(jī)制的隔離
和上面這個(gè)例子類似,假設(shè)我們的應(yīng)用需要 Web GUI冀偶,CLI 和 Web API醒第。我們想在全部三種 UI 中提供某個(gè)功能,比如叫做UserProfileUpdate的功能进鸠。
使用端口和適配器架構(gòu)的話稠曼,我們會(huì)在一個(gè)應(yīng)用服務(wù)的方法中實(shí)現(xiàn)這個(gè)功能并將其作為一個(gè)用例。服務(wù)會(huì)實(shí)現(xiàn)一個(gè)接口客年,該接口說(shuō)明了方法霞幅、輸入以及輸出。
每個(gè)版本的 UI 都有各自的控制器(或控制臺(tái)命令)來(lái)通過(guò)這個(gè)接口觸發(fā)期望的邏輯量瓜,應(yīng)用服務(wù)接口的具體實(shí)現(xiàn)會(huì)被注入到 UI 中司恳。這種情況下,適配器實(shí)際上就是控制器(或 CLI 命令)绍傲。
之后我們可以修改 UI扔傅,因?yàn)槲覀冎肋@些修改不會(huì)影響業(yè)務(wù)邏輯。
測(cè)試
上面兩個(gè)例子中烫饼,使用端口和適配器架構(gòu)會(huì)讓測(cè)試更加容易猎塞。第一個(gè)例子中,我們用接口(端口)的 Mock 就可以測(cè)試應(yīng)用杠纵,而不需要使用 SOLR 或 Elasticsearch 荠耽。
第二個(gè)例子中,所有的 UI 都可以獨(dú)立于應(yīng)用進(jìn)行測(cè)試比藻。我們的用例也可以獨(dú)立于 UI 進(jìn)行測(cè)試铝量,傳給服務(wù)一些輸入再斷言結(jié)果就好。
總結(jié)
在我看來(lái)银亲,端口和適配器架構(gòu)只有一個(gè)目標(biāo):將業(yè)務(wù)邏輯和系統(tǒng)使用的傳達(dá)機(jī)制以及工具隔離款违。為此,它使用了常見(jiàn)的編程語(yǔ)言結(jié)構(gòu):接口群凶。
在UI側(cè)(主動(dòng)適配器),我們創(chuàng)建使用應(yīng)用接口的適配器哄辣,比如控制器请梢。
在基礎(chǔ)設(shè)施側(cè)(被動(dòng)適配器),我們創(chuàng)建實(shí)現(xiàn)應(yīng)用接口的適配器力穗,比如資源庫(kù)毅弧。
這就是全部!
然而当窗,我驚訝的發(fā)現(xiàn)早在十三年前同樣的思想就已經(jīng)公開(kāi)發(fā)表了(譯)够坐,盡管它沒(méi)有刻意地強(qiáng)調(diào)要將工具和傳達(dá)機(jī)制從應(yīng)用核心中隔離出來(lái)。
系統(tǒng)和角色的任何交互都要通過(guò)邊界對(duì)象。按照 Jacobson 的描述元咙,角色可以是客戶或者管理員(操作員)這樣的人類用戶梯影,也可以是定時(shí)器或者打印機(jī)這樣的非人類“用戶”,它們分別對(duì)應(yīng)著端口和適配器架構(gòu)中的主動(dòng)適配器和被動(dòng)適配器庶香。
引用來(lái)源
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