闡釋限界上下文
前情回顧
在上一篇文章《業務服務的價值在哪裏》中,我對比了用例、用戶故事和業務服務的區別。或許我不應提出此對比,由此會引起一些不必要的爭論,也有抑彼揚此、王婆賣瓜的嫌疑。
作爲一名至今還在寫代碼的程序員,我所謂的業務服務之價值,確實是來自開發一線的感受。前文已經 “表白” 清楚,這裏也就略過不提了。
前文還提到了業務服務規約。若要在細節層面傳遞領域知識,需要爲業務服務編寫業務服務規約纔可。然而在全局分析階段,一開始不必鑽入細節,因爲它之輸出將作爲架構映射階段的重要輸入。如果從分析階段就沉入太多細節,就會陷入 “分析癱瘓”,且無法幫助我們儘快獲得合理的架構。
因此,我對業務服務的介紹就先告一個段落,讓我們快速進入架構映射階段。重要的,是我們要識別出限界上下文。
要識別限界上下文,需要了解限界上下文到底是什麼?我的著作《解構領域驅動設計》用了大量篇幅來闡釋限界上下文,因此,在這組系列文章中,我就僅列出濃縮的精華。
限界上下文的六個要素
首先是限界上下文的六個要素,我用一句話進行描述:
封裝了領域知識的領域對象,在知識語境的界定下,扮演了不同的角色,執行了不同活動,共同對外公開內聚的業務能力。
這六個要素之間的關係體現爲下圖:
限界上下文主要封裝了提供領域知識的領域模型對象。在劃分領域驅動設計的邊界時,要根據領域知識的邊界進行劃分。在其內部,這些領域對象與基礎設施提供的功能共同向外以服務形式提供各個完整的業務能力。
限界上下文的兩個本質
限界上下文是解空間的子空間,它體現瞭如下兩個本質:
-
限界上下文是領域模型的知識語境
-
限界上下文是業務能力的縱向切分
如何理解 “領域模型的知識語境”?我們可以想象一名僞裝者穿行在不同的空間中,扮演了不同的角色,他的身份由其所處的空間決定。這個空間,就是我們進行講述的知識語境。
可以換一個角度理解 “盲人摸象” 這則寓言。如下圖所示,它隱含說明了在限界上下文的限定邊界內,定義的領域模型雖然是局部的,但對於當前限界上下文而言,它就是整體。
在目標系統範圍內,一個完整的領域概念就是一頭大象,例如 Customer 的方方面面作爲一個整體,Product 的方方面面作爲一個整體,它們都是完整的一頭大象。但是,在限界上下文的團隊裏,人們看到的大象的局部,在他們眼中,不是局部,而是大象的整體。——是否有柏拉圖洞穴理論的哲學觀?誰又能看到現實的真相呢?
採用模塊的設計觀念觀察大象,大象是一個唯一的整體:
採用限界上下文的設計觀念觀察大象,還是這個整體,但是在邏輯上被分爲了不同的部分,受到知識語境的限制,每個團隊都將自己看到的局部看作是整頭大象,於是,在各個上下文中都定義了各自的 Elephant,到了系統層次,它們通過唯一 ID 確定大象的身份,又共同組成一個整體。
以 Product 爲例,完整的 Product 屬性有數十個,但在運輸上下文,只需要瞭解 Product 與運輸有關的屬性,如 shippingWidth、shippingHeight、inShippingBox 等。此時,就應該在運輸上下文定義一個 Product 類,它擁有這三個屬性,在運輸上下文中,它就是商品整體,也就是整頭大象。
限界上下文是業務能力的縱向切分。
這一本質旗幟鮮明地說明了:
-
從領域維度對整個系統的解空間進行切分,如此形成端對端的縱向的領域特性
-
限界上下文不止包含領域模型對象,還包括支持業務能力的基礎設施
與限界上下文相比,我們通常使用的 “模塊” 概念就不同了,它的邊界沒有這麼清晰,既可以橫向切分,也可以縱向切分。由此方式定義的業務模塊只定義了具有領域知識的領域模型對象,卻無法對外提供完整的業務能力,它需要基礎設施模塊的支持。如下圖所示:
限界上下文的邊界由領域維度決定。不考慮粒度的差異,完全可以將限界上下文當作一個相對完整的子系統:
正所謂 “麻雀雖小,五臟俱全”,如圖看到的限界上下文雖然粒度更小,但它是功能相對完備的。這就解釋了爲何在進行微服務設計時需要借鑑領域驅動設計的思想。
模塊的劃分是做生日蛋糕的做法,即從實現的角度,先做好一層蛋糕,再添加巧克力,之後爲其鋪上奶油,最後點綴各色水果。
限界上下文的劃分是切生日蛋糕的做法,即從消費的角度,給每個人切一塊蛋糕。理論上,不管切下的蛋糕有多小,都是相對完整的:蛋糕、巧克力、奶油和水果俱全。
如此切分的前提基於一個事實:大多數軟件系統的需求變化,是根據領域維度進行變化的。要保證設計的架構具有演進性,不是要拒絕變化,而是要讓變化產生的影響降到最低。故而,架構設計需要順應變化的方向。
限界上下文的四個特徵
一個設計良好的限界上下文必須滿足自治性。限界上下文的自治特徵如下圖所示:
簡單總結這四個特徵:
-
最小完備:就領域知識而言,它實際體現了領域模型的知識語境,即當前限界上下文需要的必備領域知識,必須分配給它,才能保證其最小的完整性。
-
自我履行:一旦具有了最小完備性,限界上下文就擁有了 “自我履行” 的意識,在輔以邊界內基礎設施的支持,就能輸出完整的業務能力,因此,它也體現了業務能力的縱向切分。
-
穩定空間:限界上下文內部的領域層需儘量保持穩定,這就需要隔離外界變化對它產生的影響。引入抽象,即可達成此目的。
-
獨立進化:需求自身的變化會引起領域模型的修改和更新,可認爲是領域模型的一種進化。如果說穩定空間是隔離外界的變化,那麼,獨立進化就是避免內部的變化影響外部。引入封裝,即可達成此目的。
菱形對稱架構
與限界上下文自治特性對應的架構模式是我提出的 “菱形對稱架構”。
或許又有人說我 “創新” 何太急了。但我認爲:在任何領域,我們都要敢於去創新。創新不一定要開天闢地,哪怕是一個小小的“新想法”,也不可否認其價值;創新也不一定要批判過去,哪怕是對現有成果的小小改進,也可視爲技術的持續前進。至於什麼是真創新,什麼是僞創新,就要看以誰的標準而論了。
不可否認,菱形對稱架構脫胎於六邊形架構,思想則沿襲自整潔架構思想。與之不同的是,菱形對稱架構是特別針對領域驅動設計的限界上下文提出的。
一句話形容菱形對稱架構,可以總結爲:內外分離,南北對稱。整個架構模式如下圖所示:
菱形對稱架構的英文描述爲 Rhombic Symmetry Architecture(RSA),也可以簡稱爲菱形架構(Diamond Architecture)。
所謂 “內外分離”,就是在菱形對稱架構中,整個限界上下文被分爲內部的領域層和外部的網關層。
內外分離的風格可以更好地隔離業務複雜度與技術複雜度。團隊根據菱形對稱架構編寫代碼時,一個基本的檢查手段就是詢問:我寫的代碼與領域邏輯有關嗎?如果是,就放在內部的領域層;如果非,就放在外部的網關層。進行代碼評審時,也可通過這一判斷標準進行檢查。
限界上下文內部的領域模型需要滿足 “最小完備” 的自治特徵,外部的網關層提供了業務能力需要的基礎設施,滿足了 “自我履行” 的自治特徵。
所謂 “南北對稱”,就是南向網關和北向網關的對稱,前者體現 “抽象” 思想,故而分爲端口與適配器;後者體現 “封裝” 思想,分爲本地服務與遠程服務。
爲了更好地隔離領域層變化對外界的影響,外部的網關層就好似雞蛋殼一般保護着雞蛋,避免直接將領域模型裸露在外。因此,在引入本地服務與遠程服務的同時,還需要引入一層消息對象,它建立了服務契約與領域模型之間的間接層。
通過引入南向網關,可以滿足限界上下文 “穩定空間” 的自治特徵;通過引入北向網關,可以滿足限界上下文 “獨立進化” 的自治特徵。
如前所述,菱形對稱架構很好地滿足了限界上下文的四個自治特徵。同時,它還遵循了穩定****依賴原則,該原則要求被依賴的元素(類、模塊、服務)要穩定。這實際上是控制依賴複雜度的有效方法。
遵循該原則,菱形對稱架構的南北網關分別體現爲:
-
北向網關:參考 Robert Martin 提出的整潔架構思想,該思想認爲內部要比外部更穩定,要滿足穩定依賴原則,必須是外部依賴內部。北向網關定義的調用順序由外向內依次爲遠程服務調用本地服務,本地服務調用領域層。
-
南向網關:外部依賴內部在理論上是成立的,但在實際開發中一定會出現內部依賴外部,此時要遵循依賴倒置原則,內部不依賴於外部,而是依賴於外部的抽象。南向網關定義了端口和適配器,內部的領域對象如果要訪問外部,只能訪問抽象的端口。
我之所以說菱形對稱架構是特別針對領域驅動設計的限界上下文提出,還在於它直接反應了 Eric Evans 提出的上下文映射。同時,它也可以和分層架構映射起來,如下圖所示:
南向網關擴大了防腐層(ACL)的外延,從限界上下文的代碼模型邊界來看,不止是上游限界上下文,諸如數據庫、文件、外部平臺、消息隊列都是它要防止腐化的內容。爲此,我將訪問數據庫的抽象 Repository 放到了南向網關的端口層,與訪問上游限界上下文的 Client 位於同等地位。對應領域驅動設計的分層架構,南向網關就是基礎設施層。
北向網關就是開放主機服務(OHS)。由於 Eric Evans 強調應用層需爲一個薄薄的層,不應包含領域邏輯,故而也可認爲應用層的應用服務就是開放主機服務。然而就此二者,在 Eric Evans 的書中語焉不詳,爲保證概念的清晰性與一致性,我乾脆將應用服務認爲是進程內調用的本地服務,然後通過區分通信協議與設計模式,分別定義了不同的遠程服務。對應領域驅動設計的分層架構,北向網關就是應用層。
開放主機服務需要定義發佈語言(PL),以保證其穩定性。在菱形對稱架構中,就是定義的消息。
如果爲限界上下文引入了菱形對稱架構,除了共享內核模式,如防腐層、開放主機服務與發佈語言等上下文映射模式就已經包含了。至於爲何不用分層架構,是因爲我覺得像菱形對稱架構這樣內外分離的表現形式,可以更清晰地體現業務與技術的隔離。
關於菱形對稱架構的定義與運用,有很多內容要談,各位可以去閱讀我的書籍《解構領域驅動設計》。當然,它也可以很簡單,只要你理解了抽象與封裝的思想,那就只需要記住這八個字即可:
內外分離、南北對稱。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/5OXRGhhgVcEnRazqQVijQw