淺談DDD,你學會了嗎?
?DDD 最近幾年越來越流行,大家都在聊這個話題,但是每個人對它的理解都不同,小汪哥這里根據之前在系統拆分、需求評估,以及遺留系統改造中的一點點經驗,來淺淺的聊下自己對DDD的理解。從認知定義、作用、領域建模方法、實現方法論幾個方面來聊聊。
認知定義
DDD 是一種處理高度復雜領域的設計思想,它試圖分離技術實現的復雜性,并圍繞業務概念構建領域模型來控制業務的復雜性,以解決軟件難以理解,難以演進的問題。
DDD 不是架構,而是一種架構設計方法論,它通過邊界劃分將復雜業務領域簡單化,幫我們設計出清晰的領域和應用邊界,可以很容易地實現架構演進和微服務的落地。
作用
優勢:
1、作為微服務的定義的指導思想。
2、理解業務的一種方法論,可以在接手遺留系統,以及遺留系統改造時快速理解業務。
3、解決領域知識被割裂肢解、代碼的業務語義表達能力弱的問題。
4、控制系統復雜度,控制代碼量。
劣勢:
1、DDD不能解決大部分的性能優化問題,甚至大部分的場景,我們需要為性能優化去做反DDD設計。
2、DDD不能解決開發技術水平的問題。
3、DDD需要我們在領域建模花費很多的時間和精力,而且還可能導致付出和收益不成正比的情況。
領域建模方法
領域建模解決的問題
領域建模的目的是統一大家的業務認知,讓業務、開發、測試、產品在同一個頻道上交流。其實要做到這一點是很難的,開發喜歡從技術層面去描述問題,產品習慣從業務層面描述問題,兩個不在同一個頻道怎么能好好溝通。在小團隊這種優勢表現不出來,在大團隊中,溝通成本是很高的。
領域建模,說的很簡單,但是做好確實很難,一個復雜的需求不是建幾個實體對象就能解決的。從全局看只在腦海中進行的建模實際上并不一定正確和穩定。因此我們需要找到正確的方法幫助對業務領域進行分析,得到建模結構,共享建模成果。值得慶幸的是,前輩及牛人已經總結了一些建模方法。
常用的建模方法有:用例分析法、四色建模法、事件風暴法。這個我就不一一贅述了,網上有很多內容,或者公眾號回復【DDD】獲取相關資料。
實現方法論
戰略設計:
戰略設計主要從業務視角出發,建立業務領域模型,劃分領域邊界,建立通用語言的限界上下文,限界上下文可以作為微服務設計的參考邊界。
各種域:
核心域、支撐域和通用域的主要目標是:通過領域劃分,區分不同子域在公司內的不同功能屬性和重要性,從而公司可對不同子域采取不同的資源投入和建設策略,其關注度也會不一樣。
統一語言:
統一語言提供了一種更好的協同方式的可能性。統一語言與其背后的領域模型賦予了研發人員通過重構定義業務的能力,在業務方大多強勢的環境中,難能可貴地建立了技術反饋業務的途徑,降低了知識消化過程失敗的風險。
圖片來源:《如何落地業務建模》
限界上下文:
限界上下文是微服務設計和拆分的主要依據。在領域模型中,如果不考慮技術異構、團隊溝通等其它外部因素,一個限界上下文理論上就可以設計為一個微服務。
限界上下文的定義就是:用來封裝通用語言和領域對象,提供上下文環境,保證在領域之內的一些術語、業務相關對象等(通用語言)有一個確切的含義,沒有二義性。
正如電商領域的商品一樣,商品在不同的階段有不同的術語,在銷售階段是商品,而在運輸階段則變成了貨物。同樣的一個東西,由于業務領域的不同,賦予了這些術語不同的涵義和職責邊界,這個邊界就可能會成為未來微服務設計的邊界。看到這,領域邊界就是通過限界上下文來定義的。
戰術設計:
戰術設計則從技術視角出發,側重于領域模型的技術實現,完成軟件開發和架構落地,包括:聚合根、實體、值對象等代碼邏輯及代碼分層的設計和實現。主要討論在一個服務內部,如何劃分和組織代碼。
實體和值對象:
實體和值對象:從領域模型的基礎單元看系統設計實體和值對象是組成領域模型的基礎單元。
實體的代碼形態
在代碼模型中,實體的表現形式是實體類,這個類包含了實體的屬性和方法,通過這些方法實現實體自身的業務邏輯。在 DDD 里,這些實體類通常采用充血模型,與這個實體相關的所有業務邏輯都在實體類的方法中實現,跨多個實體的領域邏輯則在領域服務中實現。
實體以 DO(領域對象)的形式存在,每個實體對象都有唯一的 ID。我們可以對一個實體對象進行多次修改,修改后的數據和原來的數據可能會大不相同。但是,由于它們擁有相同的 ID,它們依然是同一個實體。
實體的數據庫形態?
在領域模型映射到數據模型時,一個實體可能對應 0 個、1 個或者多個數據庫持久化對象。大多數情況下實體與持久化對象是一對一。在某些場景中,有些實體只是暫駐靜態內存的一個運行態實體,它不需要持久化。
值對象?
值對象相對實體來說,會更加抽象一些。簡單來說,值對象本質上就是一個集合。
值對象的代碼形態?
值對象在代碼中有這樣兩種形態。如果值對象是單一屬性,則直接定義為實體類的屬性;如果值對象是屬性集合,則把它設計為 Class 類,Class 將具有整體概念的多個屬性歸集到屬性集合,這樣的值對象沒有 ID,會被實體整體引用。
圖片來源:《DDD 實戰課》
例如上圖:
人員實體原本包括:姓名、年齡、性別以及人員所在的省、市、縣和街道等屬性。這樣顯示地址相關的屬性就很零碎了對不對?現在,我們可以將“省、市、縣和街道等屬性”拿出來構成一個“地址屬性集合”,這個集合就是值對象了。
聚合和聚合根:
領域模型內的實體和值對象就好比個體,而能讓實體和值對象協同工作的組織就是聚合,它用來確保這些領域對象在實現共同的業務邏輯時,能保證數據的一致性。聚合就是由業務和邏輯緊密關聯的實體和值對象組合而成的,聚合是數據修改和持久化的基本單元,每一個聚合對應一個倉儲,實現數據的持久化。聚合有一個聚合根和上下文邊界(一個聚合包含了多個實體對象和值對象,其中有一個實體對象做為聚合根。這些對象聚集在一起形成了一個比較完整獨立的業務邊界,稱為上下文邊界。),這個邊界根據業務單一職責和高內聚原則,定義了聚合內部應該包含哪些實體和值對象,而聚合之間的邊界是松耦合的。按照這種方式設計出來的微服務很自然就是“高內聚、低耦合”的。
我們以保險的投保業務場景為例,看一下聚合的構建過程主要都包括哪些步驟:
圖片來源:《DDD 實戰課》
聚合根?
聚合根 leave 中有屬性、值對象、關聯實體和自身的業務行為。Leave 實體采用充血模型 ,有自己的業務行為,具體就是聚合根實體類的方法,如代碼中的 getDuration 和 addHistoryApprovalInfo 等方法。
聚合根引用實體和值對象,它可以組合聚合內的多個實體,在聚合根實體類方法中完成復雜的業務行為,這種復雜的業務行為也可以在聚合領域服務里實現。但為了職責和邊界清晰,我建議聚合要根據自身的業務行為在實體類方法中實現,而涉及多個實體組合才能實現的業務能力由領域服務完成。下面是聚合根 leave 的實體類方法,它包含屬性、對實體和值對象的引用以及自己的業務行為和方法。
DDD分層架構
最后就是如何組織代碼的問題,這個時候就需要要到DDD的分層架構。
那么從之前的MVC三層架構如何演變成DDD的分層架構呢?
DDD分層架構與MVC架構的映射關系:
在《領域驅動設計——軟件核心復雜性應對之道》書中也描述了各層的關系:
不過小汪哥覺得,代碼的組織方式可以根據團隊的情況來調整,只要能符合領域驅動的思想即可。
各個層級的作用可以參考之前的文章:領域驅動落地實戰?,這里就不在一一贅述了。
小結
本文主要從DDD是什么,能干什么,不能干什么,怎么干(領域建模方法、實現方法論)幾個方面來聊了一下領域驅動,當然,一千個人有一千種對領域驅動的理解。?