當(dāng)提到系統(tǒng)間交互的時候,人們都會想到大名鼎鼎的防腐層,即用一個 Adaptor 進行系統(tǒng)間模型的轉(zhuǎn)換,用來防止其他系統(tǒng)的模型變更對本系統(tǒng)造成影響。但是在實踐這個模式的過程中,我們是否常常遇到如下問題:
- 業(yè)務(wù)代碼只有三行,模型轉(zhuǎn)換代碼卻寫了幾十行
- 為了給前端增加一個返回值,需要給整條鏈路上的每個模型增加一個屬性,可鏈路上還會涉及好幾個系統(tǒng),一不小心漏加,還會導(dǎo)致 bug
- 明明是類似的東西,在不同接口中卻是不同的類,導(dǎo)致調(diào)用方?jīng)]法統(tǒng)一處理。
這個時候,我們就應(yīng)該仔細思考 “防腐層” 是否真的適合了。防腐層一詞最早出現(xiàn)于 Evans 《領(lǐng)域驅(qū)動設(shè)計》,英文為 Anticorruption Layer,簡稱 ACL(下文中都會稱之為 ACL),如果去翻閱原著,會發(fā)現(xiàn) “防腐層" 只不過是 Evans 定義的九種系統(tǒng)間關(guān)系的一種,在設(shè)計系統(tǒng)間關(guān)系時,我們應(yīng)該根據(jù)實際情況靈活選擇,而不是生搬硬套 ACL 來處理所有的系統(tǒng)間關(guān)系。
DDD 的戰(zhàn)略模式與戰(zhàn)術(shù)模式:ACL 屬于哪種?
毛爺爺曾說“戰(zhàn)略上藐視敵人,戰(zhàn)術(shù)上重視敵人”,戰(zhàn)略一般是指比較大的規(guī)劃,而戰(zhàn)術(shù)更加偏向具體執(zhí)行,在長津湖戰(zhàn)役中,彭德懷將美軍分割成四個部分分別擊破的規(guī)劃,就是戰(zhàn)略,電影中第七穿插連在新興里擊退美軍的過程就是戰(zhàn)術(shù)。

具體到軟件工程中,戰(zhàn)略是指高層視角的系統(tǒng)與人員規(guī)劃,可能涉及多團隊的合作關(guān)系,往往由架構(gòu)師拍板決策;而戰(zhàn)術(shù)一般是具體的類和方法的設(shè)計,開發(fā)者一般有更大的話語權(quán)。
在《領(lǐng)域驅(qū)動設(shè)計》中,前三部分主要講戰(zhàn)術(shù)設(shè)計,最后一部分專門講戰(zhàn)略設(shè)計。

ACL 這個詞出現(xiàn)在 "戰(zhàn)略設(shè)計" 部分。這可能違背大多數(shù)人的直覺,因為在日常工作中,我們常常不加思考地就引入一個 ACL,以為這就是一個簡單的設(shè)計模式。其實兩個系統(tǒng)間是否要引入 ACL,是架構(gòu)師對于系統(tǒng)間關(guān)系深思熟慮的結(jié)果,并不是一個簡單的代碼層面的設(shè)計模式。
此處涉及到兩個相關(guān)的 DDD 術(shù)語:“限界上下文”(Bounded Context)和“上下文映射圖”(Context Map)。一個系統(tǒng)所管理的業(yè)務(wù)范圍稱之為“限界上下文”(Bounded Context),而且我們前文所說的 “系統(tǒng)間關(guān)系”,本質(zhì)上就是兩個 “限界上下文” 之間的關(guān)系,這種系統(tǒng)間關(guān)系在 DDD 中我們稱之為 “上下文映射圖”(Context Map),ACL 就是一種 “上下文映射圖”。
7 種系統(tǒng)間關(guān)系
本節(jié)將根據(jù)耦合度從高到低逐一探討這些關(guān)系。耦合度高有時并不是壞事,它能夠讓團隊內(nèi)部的系統(tǒng)更加內(nèi)聚,而不是無法整合的碎塊。我們應(yīng)該根據(jù)具體情況進行選擇。
因為系統(tǒng)間關(guān)系往往也是組織架構(gòu)的反映,此處每種關(guān)系除了描述其相關(guān)的技術(shù)架構(gòu),本節(jié)也將描述其適用的組織架構(gòu)。
共享內(nèi)核(Shared Kernel)
系統(tǒng)間共享部分模型和相關(guān)邏輯,是最親密的合作關(guān)系。

當(dāng)負責(zé)這兩個系統(tǒng)的團隊存在非常緊密的合作關(guān)系(甚至就是同一個團隊),并且業(yè)務(wù)上十分相似時,共享內(nèi)核就是一個不錯的選擇。在職責(zé)上,即使共享內(nèi)核有專門的 owner,其任何修改都需要經(jīng)過多方探討,因為其中的任何模型更改都會深刻地影響這幾個系統(tǒng)。
最典型的就是業(yè)務(wù)系統(tǒng)以及業(yè)務(wù)相關(guān)的批量導(dǎo)入導(dǎo)出系統(tǒng),兩者雖然因為技術(shù)原因(隔離消耗資源的任務(wù))被劃分成了兩個系統(tǒng),但是它們的模型理論上應(yīng)該完全共享的(即共享內(nèi)核),這樣才能保證業(yè)務(wù)上的修改及時反映到導(dǎo)入導(dǎo)出中。試想如果這種場景還用 ACL 做隔離的話,每次業(yè)務(wù)系統(tǒng)修改,還需要同步在導(dǎo)入導(dǎo)出模型中做修改,這將是非常麻煩的。
客戶-供應(yīng)方(Customer-Supplier)
兩個系統(tǒng)雖然相對獨立發(fā)展,但是底層系統(tǒng)(Supplier, 供應(yīng)方)愿意為上層系統(tǒng)(Customer, 客戶)的需求負責(zé),并且做對應(yīng)的變更。這也是一種非常親密的合作關(guān)系,只比共享內(nèi)核弱一點。
因為兩個系統(tǒng)在實現(xiàn)時會互相考慮對方,此時兩者交互部分的模型會非常相似,相比共享內(nèi)核,它只共享一些模型,不共享邏輯代碼。
例如,智能辦公的表單導(dǎo)出系統(tǒng)依賴表單搜索系統(tǒng)搜索需要導(dǎo)出的數(shù)據(jù),表單搜索系統(tǒng)的維護者是同一個團隊的開發(fā)者,他會接受導(dǎo)出相關(guān)的所有搜索需求,所以不需要在此基礎(chǔ)上再進行任何封裝,直接將表單搜索系統(tǒng)提供的 QueryCondition 模型作為導(dǎo)出任務(wù)模型的一個屬性:
public class ExportJob {
// 來自搜索系統(tǒng)的模型
private QueryCondition queryCondition;
// 其他屬性
private Long id;
//...省略
}
遵奉者(Conformist)
兩個系統(tǒng)完全獨立發(fā)展,底層系統(tǒng)因為沒有人力或者其他原因,不可能因為上層系統(tǒng)的需求做出任何變更。這與下文 ACL 適用的情況類似。
上層系統(tǒng)的模型設(shè)計與底層系統(tǒng)的模型嚴格地保持一致,雖然也是需要一個轉(zhuǎn)換層轉(zhuǎn)換,但是沒有做任何“真正”的轉(zhuǎn)換,最多只是簡單的屬性拷貝,這是和 ACL 的主要區(qū)別,ACL 會做更加復(fù)雜的轉(zhuǎn)換。
例如,釘釘?shù)耐獠?ISV(三方應(yīng)用開發(fā)商)想要基于釘釘通訊錄開發(fā)一款應(yīng)用,因為這個三方應(yīng)用所有功能都是基于釘釘通訊錄,ISV 可以采取的最合適的方式就是定義一批和釘釘通訊錄差不多的模型,這樣才能最充分地利用通訊錄已有功能,概念上的一致性也有助于和釘釘通訊錄開發(fā)人員溝通。
防腐層(ACL)
ACL 對應(yīng)的組織架構(gòu)與遵奉者類似。
ACL 適合兩種情況:
- 同樣的概念在兩個系統(tǒng)中有不同的含義:“用戶”概念在 IM 中就是消息發(fā)起接受方,但是在釘釘通訊錄中卻是指某個組織內(nèi)的、含有職位,角色等信息的職員
- 模型相差巨大:比如 excel 表格和釘釘文檔中的表格
ACL 是指復(fù)雜的數(shù)據(jù)轉(zhuǎn)換層,在轉(zhuǎn)換數(shù)據(jù)時需要做概念上的翻譯。
另謀它路(Separate Way)
“最好”的解耦方法就是完全另寫一套代碼。
這有點像 “拆中臺” 的思路,研究了半天,發(fā)現(xiàn)依賴復(fù)雜的中臺,還不如業(yè)務(wù)團隊自己寫一套簡單的系統(tǒng)實現(xiàn)。
這已經(jīng)不能算是系統(tǒng)間關(guān)系了,是 “完全沒有關(guān)系” 的意思。
開放主機服務(wù)(Open Host Service)與發(fā)布語言(Published Language)
直接舉例,底層服務(wù)開放一個 Http 接口(開放主機服務(wù)),允許以 json 的數(shù)據(jù)格式(發(fā)布語言)進行調(diào)用。
它們其實就是開放 RPC 調(diào)用。他們被單獨列出來完全是因為 《領(lǐng)域驅(qū)動設(shè)計》這本書成書較早,互聯(lián)網(wǎng)軟件比較少,RPC 也沒有特別規(guī)范的標(biāo)準(zhǔn)。在微服務(wù)時代,限界上下文的調(diào)用幾乎都通過 RPC 進行,并且使用 Hsf, Dubbo 等 RPC 框架將發(fā)布語言封裝在最底層。這也是我將本條放在最后一個的原因。
總結(jié)
軟件工程追求的終極目標(biāo)就是 “高內(nèi)聚,低耦合”,翻譯成兩個正面的指標(biāo)就是內(nèi)聚和解耦:
- 共享內(nèi)核:內(nèi)聚度最高,解耦最差
- ACL:內(nèi)聚度最低,解耦最強

這張圖恰恰反映了軟件工程沒有銀彈的道理,通過梳理系統(tǒng)間關(guān)系,合理的控制系統(tǒng)的內(nèi)聚與耦合程度,才是項目架構(gòu)的難點所在。
ACL 被濫用的原因在于開發(fā)者將自己寫的一小部分代碼像“伊甸園”一樣保護起來,這是比較簡單的處理方式,但是如果要梳理如何和現(xiàn)有業(yè)務(wù)與系統(tǒng)結(jié)合,卻要付出大量精力。這就導(dǎo)致系統(tǒng)過度“解耦”,以至于同一個團隊維護的業(yè)務(wù)系統(tǒng)卻給調(diào)用方一種支離破碎的感覺。
因此是否引入 ACL 要根據(jù)兩個系統(tǒng)當(dāng)前情況和未來規(guī)劃決定,如果滿足:
- 同一個團隊維護
- 屬于同一個業(yè)務(wù)
- 模型差別不大
其中的一到兩條。此時可以考慮暫時不引入 ACL,而是采用共享內(nèi)核,或者客戶-供應(yīng)商來處理系統(tǒng)間關(guān)系。
但是這條定律也只能作為參考,還是需要結(jié)合現(xiàn)實中對于內(nèi)聚與解耦的需求決定,畢竟軟件工程中唯一的定律就是沒有定律。