領域驅動設計對依賴的控制
在《解構領域驅動設計》一書中分析了軟體複雜度的成因,一曰規模,一曰結構,還有一個則是變化的影響。規模與結構存在一定的矛盾關係:解決規模複雜度的有效方法為“分而治之”,一旦系統被分解為多個更為細小的軟體元素,結構複雜度就會增加。結構與變化之間存在互相影響的關係:如果結構控制不合理,變化帶來的影響就會更強,使得系統更加複雜。
認真分析結構和變化對系統複雜度的影響,一個關鍵是對依賴的控制。當我們對系統進行分解時,依賴會成為我們無法繞開的問題,它是技術債的重要組成部分,是不可避免的。如果沒有控制好依賴,系統的架構就會隨著時間的推移不可避免地腐化下去,如人不可避免的老去。
要合理控制依賴,只有兩個可行的思路:
從多到少:減少依賴而非徹底消除依賴,其核心原理是做好職責的合理分配
從強到弱:如果依賴不可避免,則要想辦法降低依賴,其核心原理是封裝與抽象
減少依賴數量
領域驅動設計透過引入限界上下文和聚合,在戰略層次和戰術層次分別提供了減少依賴的控制手法。
限界上下文
我在《解構領域驅動設計》書中總結了限界上下文的兩個特徵:
限界上下文是領域模型的知識語境
限界上下文是業務能力的縱向切分
此兩大特徵既充分闡釋了限界上下文的本質,又提供了減少依賴的思路。
領域模型的知識語境
限界上下文提供了領域概念的知識邊界,在特定的上下文之下,領域概念體現的是一種區域性的全貌。所謂“區域性的全貌”聽起來頗為矛盾,然而就觀察者而言,在你所處的“上下文”邊界內,由於你並不關心其他上下文的知識,因而你看到的雖然只是“區域性”,卻可以理解為這是你需要的“全貌”。
譬如在一家軟體企業,假設一個員工作為我們要了解的全貌,在不同部門看到的卻只是各自的區域性:
人力資源部門:教育背景、現有角色、崗位、職務、考勤
財務部門:薪資、社保
專案管理部門:技能水平、所屬團隊、團隊角色
以財務部門為例,我並不需要知道該員工技能水平如何,也不需要知道他在哪一個團隊,只需要知道該員工的薪資構成,然後按照企業規章核算工資並按時發放即可。員工薪資與社保等資訊就在此時構成了在財務上下文下財務人員關心的全貌。
這一設計的好處在於引入了領域知識的控制邊界,倘若分配合理,就能減少軟體元素之間不必要的依賴關係。這也是限界上下文與模組之間的不同之處。
如果以業務模組對系統進行分解,一種直觀的設計方案是單獨分解出一個員工模組,然後將該員工的所有屬性與行為(構成了領域知識)都分配給員工模組。當財務部門進行工資核算與支付時,需要從員工模組中獲取它與薪資、社保相關的領域知識,帶來了財務模組與員工模組之間的依賴關係。
限界上下文則不同,由於員工與財務相關的領域知識都根據上下文分配給了需要關注這些領域知識的財務上下文,在核算工資與支付工資時,財務上下文就無需求助於員工上下文了。這是讓依賴減少的最佳模式。
業務能力的縱向切分
限界上下文與模組之間的不同之處,還在於限界上下文不止限於封裝了領域知識。它是對業務能力的縱向切分,如此切分出來的每一塊,都是相對獨立而完整的。準確的說法,就是先根據領域維度對整個系統進行縱向切分,然後再到限界上下文內部,根據技術維度對其進行橫向切分,將限界上下文的領域層獨立出來。
模組的劃分不是這樣,業務模組和基礎功能模組涇渭分明。業務模組自身不具備支援業務能力的功能,如訪問資料庫、網路通訊或訊息佇列,於是引入了業務模組與其他基礎功能模組之間的依賴。在限界上下文中,這樣的依賴(領域與基礎設施之間的依賴)雖然依舊存在,但由於系統的劃分邊界是整個限界上下文,依賴發生在限界上下文內部,從架構層次看,相當於消除了依賴,變相地減少了依賴。
聚合
聚合在模型粒度與依賴之間引入了平衡。遵循物件建模的思維,建議為每一個領域概念都定義一個領域模型類,哪怕是email、quantity、address這樣細小的基礎概念,也當如此。Martin Fowler在重構中也提到,編碼時要注意規避“基本型別偏執”壞味道。不用基本型別說明這些細小的領域概念,自然就需要定義對應的模型類了。
一旦為非常細小的領域概念定義了領域模型類,粒度就變得非常小,整個領域模型的類數量就會增加。增加了類的數量,必然也就會增加依賴。這時,聚合引入的邊界就起到了很好的控制作用,它要求:
聚合內的領域模型由實體和值物件組成,形成一棵樹
一棵樹只有一個根,只有實體才能作為根
根實體作為聚合的唯一出口和唯一入口
跨聚合之間只能透過根實體建立關係
透過邊界的控制,保障根實體之間才能建立關聯關係,就去掉了許多非根元素跨聚合之間的關係,減少了領域模型類之間的依賴。根實體體現了對內部領域模型的封裝,由它代表整個聚合內的所有領域模型,站在聚合邊界之外,就可以認為領域模型類的數量減少了,呼叫者也無需關心聚合內部的其他實體和值物件。
限界上下文透過知識語境和業務能力形成的邊界控制,減少了戰略層面(也就是架構層面)軟體元素之間的依賴關係;聚合則透過規定的設計原則與設計約束形成的邊界控制,減少了戰術層面領域模型類之間的依賴關係。
正因為如此,我才在《解構領域驅動設計》書中指出:
限界上下文是架構對映階段基本的架構單元
聚合是領域建模階段基本的設計單元
要做好領域驅動設計,在架構層面,限界上下文是不可或缺的,在設計層面,聚合才是不可或缺的。
降低依賴強度
當依賴不可避免時,需要將強依賴降低為弱依賴,也即所謂的“降低耦合度”。限界上下文作為基本的架構單元,要降低依賴強度,實則就是合理地管理限界上下文之間的協作關係,這是領域驅動設計的上下文對映模式所要處理的。防腐層(ACL)與開放主機服務(OHS)都降低了下游對上游的依賴,而釋出語言(PL)則作為開放主機模式的補充,引入了對領域模型的封裝。
上下文對映模式降低了限界上下文之間的耦合,強調了對內部領域模型的封裝;對於限界上下文內部,則透過分層架構,凸顯了領域模型的核心地位,利用層次(Layer)來分離關注點,並適當引入封裝和抽象,解除了外部資源對領域模型,以及領域模型對外部資源的依賴。可概括為:
封裝:引入應用服務,隱藏領域模型,包括領域模型中的聚合與領域服務,並保障應用層的輕和薄,嚴防死守,避免將領域知識洩露出去
抽象:引入資源庫的介面,隔離對資料庫的訪問,且將資源庫介面放到領域層,然後透過依賴注入實現依賴關係的反轉
在《解構領域驅動設計》書中,我透過引入菱形對稱架構將上述上下文對映模式、分層架構模式,以及應用服務與抽象資源庫等內容全部囊括其中。開放主機服務屬於北向閘道器,其中也涵蓋了應用服務;防腐層屬於南向閘道器,其中也涵蓋了資源庫,同時擴大了防腐層的外延,將所有對外部資源的訪問都視為南向閘道器。至於釋出語言,則介於外部閘道器層與內部領域層之間,就其本質而言,仍然屬於外部閘道器層的一部分。
自治性
減少依賴數量,降低依賴強度,一言以蔽之,其實就是我們耳熟能詳的六字法則“高內聚低耦合”。不管是架構原則還是設計原則,都是知易行難,知道“高內聚低耦合”的原則,並不能確保你做出符合該原則的設計。領域驅動設計透過限界上下文與聚合的核心模式,提供了相對可行的方法。若要解密領域驅動設計,此二者應為解密的鑰匙。
為了更好地解密二者,我總結了它們共同的特性,將其名為“自治性”。要設計好限界上下文與聚合,就需要確保它們的自治性。
自治的限界上下文
自治的限界上下文需要具備如下四個特徵:
最小完備:強調了每個限界上下文擁有的領域知識是最小完備的,它體現了限界上下文是領域模型的知識語境這一特徵。
自我履行:強調了限界上下文在擁有了合理領域知識後,能夠擁有一定智慧,聰明地應對外界的請求,判斷哪些該自己做,哪些該求助於別的限界上下文;自我履行的範圍不僅限於領域知識,故而它體現了限界上下文是業務能力的縱向切分這一特徵。
穩定空間:就限界上下文內部的領域模型而言,我們希望它是穩定的;在不考慮業務需求自身變化的情況下,要確保它的穩定性,就是要儘可能隔離它對外界的引用,將外界變化產生的影響降到最低;這就需要引入“抽象”,隔離外界,也就是菱形對稱架構南向閘道器要做的事情。
獨立進化:只要業務需求發生了變化,限界上下文內部的領域模型必然發生調整,但我們希望這種調整對於外界而言,可以變得靜悄悄,雖然版本發生了進化,外界卻可以無感知;其關鍵在於引入“封裝”,不要將領域模型隨便暴露在外,這實際上就是菱形對稱架構北向閘道器要做的事情。
最小完備和自我履行結合起來,可以合理地減少依賴數量;穩定空間與獨立進化配合起來,可以有效地降低依賴強度。顯然,限界上下文的自治性滿足了“高內聚低耦合”的架構原則。
自治的聚合
自治的聚合需要具備如下特徵:
完整性:一個設計合理的聚合應該體現為最小單元的完整領域概念
獨立性:每個自治的聚合都是相對獨立的,它的生命週期也是獨立的
不變數:聚合內部要維持各個物件之間關係的不變數,從而滿足業務規則的約束
一致性:保持聚合內部各個物件的資料一致性,以及生命週期的一致性
完整性、不變數與一致性體現了聚合之“合”的一面,它意味著合入到聚合邊界的模型物件是“高內聚”的;獨立性體現了聚合之“分”的一面,意味著不要將生命週期不一致、資料不一致的模型物件放在同一個聚合裡,聚合邊界具有一種排斥能力,要將“異質體”推出去,透過聚合根實體來維持彼此之間的關係,保證聚合之間是“低耦合”的。
來自 “ DevOps ”, 原文作者:逸言;原文連結:https://mp.weixin.qq.com/s/rJw3fHX3St2OL46ymnd3Cw,如有侵權,請聯絡管理員刪除。
相關文章
- DDD領域驅動設計:領域事件事件
- MasaFramework -- 領域驅動設計Framework
- 領域驅動設計示例
- 理解領域驅動設計
- JavaScript中的領域驅動設計JavaScript
- 領域驅動設計對軟體複雜度的應對複雜度
- 戲說領域驅動設計(廿五)——領域事件事件
- 領域驅動設計戰術模式--領域事件模式事件
- 領域驅動設計核心概念
- 領域驅動設計簡介
- DDD領域驅動設計pdf
- 再談領域驅動設計
- 實現領域驅動設計
- 整潔的領域驅動設計 - George
- 問題驅動設計與領域驅動設計的區別 - abdullin
- 戲說領域驅動設計(廿一)——領域服務
- 領域驅動設計戰術模式--領域服務模式
- DDD-領域驅動設計示例
- 微服務領域驅動設計 - semaphoreci微服務
- 淺談DDD(領域驅動設計)
- 淺談 DDD 領域驅動設計
- 何時使用領域驅動設計
- DDD領域驅動設計:倉儲
- 前端開發-領域驅動設計前端
- 戲說領域驅動設計(五)——子域
- 領域驅動設計中的異常 - Michał
- 領域驅動設計的DDD與ddd - nick
- 領域驅動設計的概念解釋 -DEVdev
- “親切照料”下的領域驅動設計
- 最常見領域驅動設計錯誤
- 領域驅動設計整合與架構架構
- dayatang/dddlib:DDD領域驅動設計庫
- 戲說領域驅動設計(廿二)——聚合
- 戲說領域驅動設計(三)——困境
- DDD-領域驅動設計簡談
- 領域驅動設計(DDD)入門&概要
- 戲說領域驅動設計(二)——修身
- 領域驅動設計 (DDD) 簡介 - jannikwempe