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

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

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

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

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

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

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

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

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

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

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

詳細(xì)項(xiàng)目介紹可參考下圖:

