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