微服務(wù)之軟件架構(gòu)層次設(shè)計分析
在實施領(lǐng)域驅(qū)動設(shè)計的過程中,限界上下文扮演了關(guān)鍵角色:它既是維護領(lǐng)域模型完整性與一致性的重要邊界,又是系統(tǒng)架構(gòu)的重要組成部分。隨著社區(qū)對限界上下文的重視,越來越多的人開始嘗試將更多的架構(gòu)實踐與限界上下文融合在一起,創(chuàng)造出符合領(lǐng)域驅(qū)動設(shè)計的架構(gòu)模式。本文對傳統(tǒng)分層架構(gòu)、六邊形架構(gòu)、DDD經(jīng)典分層架構(gòu)以及對稱菱形架構(gòu)進行歸納分析。
一、傳統(tǒng)分層架構(gòu)(三層)缺陷
傳統(tǒng)的分層架構(gòu)具有廣泛的應(yīng)用,例如經(jīng)典的三層架構(gòu),把系統(tǒng)分為表示層、業(yè)務(wù)邏輯層、數(shù)據(jù)訪問層。在Martin Fowler的《企業(yè)應(yīng)用架構(gòu)模式》一書中做過深入闡述,本書04年出版,時至今日分層架構(gòu)仍然是常用的設(shè)計方法,分層架構(gòu)可以降低耦合、提高復(fù)用、分而治之,但同時也還是存在一些問題:
應(yīng)用邏輯在不同層泄露,導(dǎo)致替換某一層變得困難、難以對核心邏輯完整測試(你是否有過困惑,代碼到底應(yīng)該放在哪個層,雖然定義了各層的職責(zé),但是總有人不嚴格遵循層次的分界,對于三層架構(gòu),常常會出現(xiàn)業(yè)務(wù)邏輯寫在了表示層,或者業(yè)務(wù)邏輯直接和數(shù)據(jù)訪問綁定)
傳統(tǒng)的分層架構(gòu)是一維的結(jié)構(gòu),有時應(yīng)用不光是上下的依賴,可能是多維的依賴,這時一維的結(jié)構(gòu)就無法適應(yīng)了。
其實主要還是傳統(tǒng)的分層架構(gòu)對于業(yè)務(wù)邏輯層的劃分過于粗糙,針對特定的復(fù)雜場景,尤其是涉及多場景依賴的情況是很難控制層次的持久清晰劃分。
二、DDD經(jīng)典分層架構(gòu)
DDD經(jīng)典分層架構(gòu)與傳統(tǒng)的分層架構(gòu)設(shè)計本質(zhì)是一樣的,都是通過層(Layer)來隔離不同的關(guān)注點(Concern Point),以此應(yīng)對不同需求的變化,使得這種變化可以獨立進行。

用戶界面層: 負責(zé)向用戶展現(xiàn)信息和解釋用戶命令。包含 web 端 UI 界面、移 動端 UI 界面、第三方服務(wù)等。
應(yīng)用層: 很薄的一層,用來協(xié)調(diào)應(yīng)用的活動,它不包含業(yè)務(wù)邏輯,不保留業(yè)務(wù) 對象的狀態(tài)。在領(lǐng)域設(shè)計中,它其實是一個外觀(Facade),一般是供其他限界上下文基礎(chǔ)設(shè)置層中 controller 調(diào)用。
領(lǐng)域?qū)? 本層包含領(lǐng)域的信息,是業(yè)務(wù)軟件的核心所在。在這里保留業(yè)務(wù)對象的狀態(tài),對業(yè)務(wù)對象狀態(tài)的持久化被委托給基礎(chǔ)設(shè)置層。它包含領(lǐng)域設(shè)計中的:聚合、值對象、實體、服務(wù)、資源接口等。
基礎(chǔ)設(shè)置層: 不要簡單的理解為物理上的一層,它作為其他層的支撐庫而存在。它提供了層間的通訊,實現(xiàn)對業(yè)務(wù)對象的持久化。一般包含:網(wǎng)絡(luò)通訊、 資源實現(xiàn)(數(shù)據(jù)庫持久化實現(xiàn))、異步消息服務(wù)、網(wǎng)關(guān)、controller 控制等。
三、整潔架構(gòu)
整潔架構(gòu)(Clean Architecture)是由Bob大叔在2012年提出的一個架構(gòu)模型,顧名思義,是為了使架構(gòu)更簡潔。整潔架構(gòu)的模型是一個類似內(nèi)核模式的內(nèi)外層結(jié)構(gòu),它具有以下特點:
越靠近中心,層次越高。
由外到內(nèi):框架與驅(qū)動程序 --》接口適配器 --》應(yīng)用級業(yè)務(wù)邏輯--》系統(tǒng)級業(yè)務(wù)邏輯。
源碼中依賴關(guān)系必須指向同心圓內(nèi)層,從外到內(nèi)。即,外層依賴內(nèi)存然而內(nèi)存不能依賴外層。

在這個架構(gòu)中,外層圓代表的是機制,內(nèi)層圓代表的是策略;機制和具體的技術(shù)實現(xiàn)有關(guān),容易受到外部環(huán)境變化;策略與業(yè)務(wù)有關(guān),封裝了最核心領(lǐng)域模型,最不容易受到外界環(huán)境變化的影響。

(從不同維度去對比層次特點)
四、六邊形架構(gòu)
六邊形架構(gòu)是Alistair Cockburn在2005年提出,解決了傳統(tǒng)的分層架構(gòu)所帶來的問題,實際上它也是一種分層架構(gòu),只不過不是上下或左右,而是變成了內(nèi)部和外部。六邊形架構(gòu)的六邊并不重要,六邊只是為了留足空間放置端口和適配器以及用六邊形接入多個外部系統(tǒng)視覺上最簡潔美觀。在領(lǐng)域驅(qū)動設(shè)計(DDD)和微服務(wù)架構(gòu)中都出現(xiàn)了六邊形架構(gòu)的身影,在《實現(xiàn)領(lǐng)域驅(qū)動設(shè)計》一書中,作者將六邊形架構(gòu)應(yīng)用到領(lǐng)域驅(qū)動設(shè)計的實現(xiàn),六邊形的內(nèi)部代表了application和domain層,而在Chris Richardson對微服務(wù)架構(gòu)模式的定義中,每個微服務(wù)使用六邊形架構(gòu)設(shè)計,足見六邊形架構(gòu)的重要性。
解決傳統(tǒng)分層架構(gòu)問題
六邊形架構(gòu)實現(xiàn)了業(yè)務(wù)邏輯以一種松耦合的形式與多個外部系統(tǒng)通過“適配器-端口”的形式進行集成。因此,六邊形架構(gòu)又稱為端口-適配器,這個名字更容理解。六邊形架構(gòu)將系統(tǒng)分為內(nèi)部(內(nèi)部六邊形)和外部,內(nèi)部代表了應(yīng)用的業(yè)務(wù)邏輯,外部代表應(yīng)用的驅(qū)動邏輯、基礎(chǔ)設(shè)施或其他應(yīng)用。內(nèi)部通過端口和外部系統(tǒng)通信,端口代表了一定協(xié)議,以API呈現(xiàn)。一個端口可能對應(yīng)多個外部系統(tǒng),不同的外部系統(tǒng)需要使用不同的適配器,適配器負責(zé)對協(xié)議進行轉(zhuǎn)換。這樣就使得應(yīng)用程序能夠以一致的方式被用戶、程序、自動化測試、批處理腳本所驅(qū)動,并且,可以在與實際運行的設(shè)備和數(shù)據(jù)庫相隔離的情況下開發(fā)和測試。

上圖六邊形架構(gòu)結(jié)構(gòu)圖,其中黑箭頭為調(diào)用關(guān)系,白箭頭為實現(xiàn)關(guān)系。右側(cè)Message為相關(guān)消息機制,多用于微服務(wù)架構(gòu)中多個微服務(wù)(六邊形架構(gòu))之間通信。六邊形架構(gòu)的內(nèi)部(業(yè)務(wù)邏輯)與外部(APP,WEB,數(shù)據(jù)庫等)完全隔離,只通過adapter適配器進行交互實現(xiàn)了業(yè)務(wù)邏輯層與持久層的完全解耦,更一步實現(xiàn)“高內(nèi)聚低耦合”。
核心部件
Inbound adapter 入站適配器:直接對外提供統(tǒng)一的接入?yún)f(xié)議,適配器可以根據(jù)不同的業(yè)務(wù)規(guī)則甚至是數(shù)據(jù)格式進行定義,其主要功能是為了轉(zhuǎn)換成內(nèi)部入站端口所需要的統(tǒng)一協(xié)議(也可以理解為統(tǒng)一的數(shù)據(jù)格式)。
Inbound port 入站端口:入站端口即對外提供服務(wù)所暴露的API接口,通常會以PRC的provider形式注冊,但端口服務(wù)不直接與外部系統(tǒng)交互,還需要經(jīng)過入站適配器進行轉(zhuǎn)換。一個端口對應(yīng)多個適配器,是對一類外部系統(tǒng)的歸納,它體現(xiàn)了對外部的抽象。應(yīng)用通過端口為外界提供服務(wù),這些端口需要被良好的設(shè)計和測試。內(nèi)部不關(guān)心外部如何使用端口,從一開始就要假定外部使用者是可替換的(一般端口數(shù)不會超過4個)。
Outbound port 出站端口:為系統(tǒng)獲取外部服務(wù)提供支持,如獲取持久化狀態(tài)、對結(jié)果進行持久化(要求對數(shù)據(jù)庫增刪改查操作的接口)。
Outbound adapter 出站適配器:將內(nèi)部統(tǒng)一協(xié)議轉(zhuǎn)換成外部系統(tǒng)所能識別的特定協(xié)議。并且在此實現(xiàn)出站端口的處理邏輯。
Business logic 業(yè)務(wù)邏輯:可以理解為DDD中的核心域,主要特征是具備抽象穩(wěn)定的業(yè)務(wù)實現(xiàn)。六邊形架構(gòu)有一個明確的關(guān)注點,從一開始就強調(diào)把重心放在業(yè)務(wù)邏輯上,外部的適配器和端口存在可變性、可替換性,依賴具體技術(shù)細節(jié)的特征,而業(yè)務(wù)邏輯相對更加穩(wěn)定,體現(xiàn)應(yīng)用的核心價值,需要被詳盡的測試。
依賴倒置
六邊形架構(gòu)必須遵循如下規(guī)則:內(nèi)部相關(guān)的代碼不能泄露到外部。所謂的泄露是指不能出現(xiàn)內(nèi)部依賴外部的情況,只能外部依賴內(nèi)部,這樣才能保證外部是可以替換的。對于入站適配器,就是外部依賴內(nèi)部屬于主動驅(qū)動。但是對于出站適配器,實際是內(nèi)部依賴外部,這時需要使用依賴倒置,由入站適配器將出站適配器注入到應(yīng)用內(nèi)部,這時端口的定義在應(yīng)用內(nèi)部,但是實現(xiàn)是由適配器實現(xiàn)。
這里需要明確兩個概念
入站和出站:是按照應(yīng)用服務(wù)調(diào)用流向說的,外部服務(wù)想調(diào)用業(yè)務(wù)邏輯要先經(jīng)過入站適配器和入站端口。而內(nèi)部邏輯需要外部數(shù)據(jù)支撐的時候,是先要通過出站端口調(diào)用出站適配器獲取結(jié)果。
主動驅(qū)動和被動驅(qū)動:是按照端口的實現(xiàn)位置說的,主動驅(qū)動是在內(nèi)部的業(yè)務(wù)邏輯處實現(xiàn)端口邏輯的。而被動驅(qū)動是在外部適配器里實現(xiàn)端口邏輯的。
調(diào)用關(guān)系及代碼模塊映射
以一個經(jīng)典前端訪問流程為例,體現(xiàn)六邊形架構(gòu)的調(diào)用鏈路:

根據(jù)六邊形的每個部件核心價值可以將映射到下面的代碼結(jié)構(gòu)中,這是單一應(yīng)用的參考結(jié)構(gòu),也可以根據(jù)實際的系統(tǒng)結(jié)構(gòu)套用到微服務(wù)中,實際理念是一樣的。
五、菱形架構(gòu)
六邊形架構(gòu)僅僅區(qū)分了內(nèi)外邊界,提煉了端口與適配器角色,并沒有規(guī)劃限界上下文內(nèi)部各個層次與各個對象之間的關(guān)系;而整潔架構(gòu)又過于通用,提煉的是企業(yè)系統(tǒng)架構(gòu)設(shè)計的基本規(guī)則與主題。因此,當(dāng)我們將六邊形架構(gòu)與整潔架構(gòu)思想引入到領(lǐng)域驅(qū)動設(shè)計的限界上下文時,還需要引入分層架構(gòu)給出更為細致的設(shè)計指導(dǎo),即確定層、模塊與角色構(gòu)造型之間的關(guān)系。而菱形對稱架構(gòu)在這方面更加便于理解和使用。
菱形對稱架構(gòu)模式脫胎于六邊形架構(gòu)與分層架構(gòu),它以領(lǐng)域為核心對限界上下文的關(guān)注點進行劃分,建立了由內(nèi)部領(lǐng)域模型與外部網(wǎng)關(guān)組成的內(nèi)外分層架構(gòu),以菱形的對稱結(jié)構(gòu)清晰展現(xiàn)了限界上下文的內(nèi)部結(jié)構(gòu),指導(dǎo)著限界上下文的協(xié)作關(guān)系。
其實菱形對稱架構(gòu)是解決了六邊形架構(gòu)中出口端口和出口適配器對于整個系統(tǒng)的調(diào)用出、入不對稱的問題(因為出口端口在內(nèi)存,出口適配器在外層實現(xiàn)了主要邏輯,也就是依賴倒置的實現(xiàn)對于整個調(diào)用鏈路來說是不對稱的)。菱形對稱架構(gòu)將端口和適配器組合成一個“網(wǎng)關(guān)”,定義了南向網(wǎng)關(guān)和北向網(wǎng)關(guān),用于體現(xiàn)調(diào)用方向的對稱性。

核心部件
北向網(wǎng)關(guān):北向網(wǎng)關(guān)定義的遠程網(wǎng)關(guān)與本地網(wǎng)關(guān)同時承擔(dān)了端口與適配器的職責(zé),這實際上改變了六邊形架構(gòu)端口-適配器的風(fēng)格;
遠程網(wǎng)關(guān):負責(zé)進程間通信。主要包含資源(Resource)服務(wù)、供應(yīng)者 (Provider)服務(wù)、控制器(Controller)服務(wù)與事件訂閱者(Event Subscriber)服務(wù)。
本地網(wǎng)關(guān):負責(zé)進程內(nèi)通信。當(dāng)外部請求從遠程服務(wù)進入時,如果需要調(diào)用領(lǐng)域?qū)拥念I(lǐng)域邏輯,則必須經(jīng)由本地服務(wù)發(fā)起對領(lǐng)域?qū)拥恼埱?。此時的本地服務(wù)又扮演了端口的作用,可認為遠程服務(wù)是本地服務(wù)的客戶端。
南向網(wǎng)關(guān):南向網(wǎng)關(guān)引入了抽象的端口來隔離內(nèi)部領(lǐng)域模型對外部環(huán)境的訪問,等同于上下文映射中的防腐層(ACL),同樣它也擴大了防腐層的功能;南向網(wǎng)關(guān)的端口抽象負責(zé)各種資源庫操作的抽象接口,可以被領(lǐng)域?qū)右蕾?,從使用場景特性上又可分為?/span>
資源庫(repository)端口: 隔離對外部數(shù)據(jù)庫的訪問,對應(yīng)的適配器提供聚 合的持久化能力。
客戶端(client)端口: 隔離對上游限界上下文或第三方服務(wù)的訪問,對應(yīng)的 適配器提供對服務(wù)的調(diào)用能力。
事件發(fā)布者(event publisher)端口: 隔離對外部消息隊列的訪問,對應(yīng)適 配器提供發(fā)布事件消息的能力。
適配器:負責(zé)網(wǎng)關(guān)端口的實現(xiàn),運行時通過依賴注入將適配器實現(xiàn)注入到領(lǐng)域?qū)印?/p>

調(diào)用鏈路
進程內(nèi)通信:如果上下游的限界上下文位于同一個進程邊界內(nèi),客戶端適配器可以直接調(diào)用本地服務(wù)。

進程間通信:如果上下游的限界上下文處于不同的進程邊界,就由遠程服務(wù)來響應(yīng)下游客戶端適配器發(fā)起的調(diào)用。

代碼結(jié)構(gòu)
通用的代碼模型實例:ohs 為開放主機服務(wù)模式的縮寫,acl 是防腐層模式的縮寫,pl 代表了發(fā)布語言;也可以使用北向(northbound)與南向(sourthbound)取代 ohs 與acl 作為包名,使用消息(messages)契約取代pl的包名。

支付工具系統(tǒng)真實案例:

詳細項目介紹可參考下圖:

