本文為極客時(shí)間《DDD實(shí)戰(zhàn)》的讀書筆記
DDD-基礎(chǔ)
DDD基礎(chǔ)介紹
微服務(wù)設(shè)計(jì)和拆分的困境
對(duì)于微服務(wù)的拆分粒度很模糊,不知道業(yè)務(wù)或者微服務(wù)的邊界是什么,沒有一套方法論來支持微服務(wù)的拆分。
DDD適合微服務(wù)的原因
DDD 核心思想是通過<font color=red>領(lǐng)域驅(qū)動(dòng)</font>設(shè)計(jì)方法定義<font color=red>領(lǐng)域模型</font>,從而確定<font color=red>業(yè)務(wù)</font>和<font color=red>應(yīng)用邊界</font>,保證<font color=red>業(yè)務(wù)模型與代碼模型的一致性</font>,DDD是一種<font color=red>架構(gòu)設(shè)計(jì)的方法論</font>,它通過邊界劃分將復(fù)雜業(yè)務(wù)領(lǐng)域簡單化,幫我們?cè)O(shè)計(jì)出清晰的領(lǐng)域和應(yīng)用邊界,可以很容易地實(shí)現(xiàn)架構(gòu)演進(jìn)。
DDD的戰(zhàn)略設(shè)計(jì)和戰(zhàn)術(shù)設(shè)計(jì)
戰(zhàn)略設(shè)計(jì)主要從業(yè)務(wù)視角出發(fā),建立業(yè)務(wù)領(lǐng)域模型,劃分領(lǐng)域邊界,建立通用語言的限界上下文,限界上下文可以作為微服務(wù)設(shè)計(jì)的參考邊界。
戰(zhàn)術(shù)設(shè)計(jì)則從技術(shù)視角出發(fā),側(cè)重于領(lǐng)域模型的技術(shù)實(shí)現(xiàn),完成軟件開發(fā)和落地,包括:聚合根、實(shí)體、值對(duì)象、領(lǐng)域服務(wù)、應(yīng)用服務(wù)和資源庫等代碼邏輯的設(shè)計(jì)和實(shí)現(xiàn)。
建立領(lǐng)域模型的方法
DDD 戰(zhàn)略設(shè)計(jì)會(huì)建立領(lǐng)域模型,領(lǐng)域模型可以用于指導(dǎo)微服務(wù)的設(shè)計(jì)和拆分。
事件風(fēng)暴
事件風(fēng)暴是建立領(lǐng)域模型的主要方法,它是一個(gè)從發(fā)散到收斂的過程。
-
發(fā)散
- 它通常<font color=red>采用用例分析、場景分析和用戶旅程分析</font>,盡可能全面不遺漏地分解業(yè)務(wù)領(lǐng)域,并梳理領(lǐng)域?qū)ο笾g的關(guān)系,這是一個(gè)發(fā)散的過程。
-
收斂
- 事件風(fēng)暴過程會(huì)產(chǎn)生很多的實(shí)體、命令、事件等領(lǐng)域?qū)ο?,我們將這些領(lǐng)域?qū)ο髲牟煌木S度進(jìn)行聚類,形成如聚合、限界上下文等邊界,建立領(lǐng)域模型,這就是一個(gè)收斂的過程。
三步劃定領(lǐng)域模型和微服務(wù)的邊界
在事件風(fēng)暴中梳理業(yè)務(wù)過程中的用戶操作、事件以及外部依賴關(guān)系等,根據(jù)這些要素梳理出領(lǐng)域?qū)嶓w等領(lǐng)域?qū)ο蟆?/p>
根據(jù)領(lǐng)域?qū)嶓w之間的業(yè)務(wù)關(guān)聯(lián)性,將業(yè)務(wù)緊密相關(guān)的實(shí)體進(jìn)行組合形成聚合,同時(shí)確定聚合中的聚合根、值對(duì)象和實(shí)體。在下圖里,聚合之間的邊界是第一層邊界,它們?cè)谕粋€(gè)微服務(wù)實(shí)例中運(yùn)行,這個(gè)邊界是邏輯邊界,所以用虛線表示。
根據(jù)業(yè)務(wù)及語義邊界等因素,將一個(gè)或者多個(gè)聚合劃定在一個(gè)限界上下文內(nèi),形成領(lǐng)域模型。在這個(gè)圖里,限界上下文之間的邊界是第二層邊界,這一層邊界可能就是未來微服務(wù)的邊界,不同限界上下文內(nèi)的領(lǐng)域邏輯被隔離在不同的微服務(wù)實(shí)例中運(yùn)行,物理上相互隔離,所以是物理邊界,邊界之間用實(shí)線來表示。
DDD和微服務(wù)的關(guān)系
DDD 是一種<font color=red>架構(gòu)設(shè)計(jì)</font>方法,微服務(wù)是一種<font color=red>架構(gòu)風(fēng)格</font>,兩者從本質(zhì)上都是為了追求高響應(yīng)力,而從業(yè)務(wù)視角去分離應(yīng)用系統(tǒng)建設(shè)復(fù)雜度的手段。兩者都強(qiáng)調(diào)從業(yè)務(wù)出發(fā),其核心要義是強(qiáng)調(diào)根據(jù)業(yè)務(wù)發(fā)展,合理劃分領(lǐng)域邊界,持續(xù)調(diào)整現(xiàn)有架構(gòu),優(yōu)化現(xiàn)有代碼,以保持架構(gòu)和代碼的生命力,也就是我們常說的演進(jìn)式架構(gòu)。\
-
DDD 主要關(guān)注:
從業(yè)務(wù)領(lǐng)域視角劃分領(lǐng)域邊界,構(gòu)建通用語言進(jìn)行高效溝通;
通過業(yè)務(wù)抽象,建立領(lǐng)域模型,維持業(yè)務(wù)和代碼的邏輯一致性;
-
微服務(wù)主要關(guān)注:
運(yùn)行時(shí)的進(jìn)程間通信、容錯(cuò)和故障隔離,實(shí)現(xiàn)去中心化數(shù)據(jù)管理和去中心化服務(wù)治理;
關(guān)注微服務(wù)的獨(dú)立開發(fā)、測試、構(gòu)建和部署;
DDD架構(gòu)設(shè)計(jì)方法帶來的好處
DDD 是一套完整而系統(tǒng)的設(shè)計(jì)方法,它能帶給你從戰(zhàn)略設(shè)計(jì)到戰(zhàn)術(shù)設(shè)計(jì)的標(biāo)準(zhǔn)設(shè)計(jì)過程,使得你的設(shè)計(jì)思路能夠更加清晰,設(shè)計(jì)過程更加規(guī)范。
DDD 善于處理與領(lǐng)域相關(guān)的擁有高復(fù)雜度業(yè)務(wù)的產(chǎn)品開發(fā),通過它可以建立一個(gè)核心而穩(wěn)定的領(lǐng)域模型,有利于領(lǐng)域知識(shí)的傳遞與傳承。
DDD 強(qiáng)調(diào)團(tuán)隊(duì)與領(lǐng)域?qū)<业暮献鳎軌驇椭愕膱F(tuán)隊(duì)建立一個(gè)溝通良好的氛圍,構(gòu)建一致的架構(gòu)體系。
DDD 的設(shè)計(jì)思想、原則與模式有助于提高你的架構(gòu)設(shè)計(jì)能力。
無論是在新項(xiàng)目中設(shè)計(jì)微服務(wù),還是將系統(tǒng)從單體架構(gòu)演進(jìn)到微服務(wù),都可以遵循 DDD 的架構(gòu)原則。
DDD 不僅適用于微服務(wù),也適用于傳統(tǒng)的單體應(yīng)用。
DDD的領(lǐng)域、子域、核心域、通用域和支撐域
領(lǐng)域
領(lǐng)域是用來確定范圍的,領(lǐng)域等同于范圍,范圍即邊界,這也是DDD在設(shè)計(jì)中不斷強(qiáng)調(diào)邊界的原因。
DDD的領(lǐng)域就是這個(gè)邊界內(nèi)要解決的<font color=red>業(yè)務(wù)問題域</font>。
子域
我們把劃分出來的多個(gè)子領(lǐng)域稱為子域,每個(gè)子域?qū)?yīng)一個(gè)更小的問題域或更小的業(yè)務(wù)范圍。
在領(lǐng)域不斷劃分的過程中,領(lǐng)域會(huì)細(xì)分為不同的子域,子域可以根據(jù)自身重要性和功能屬性劃分為三類子域,它們分別是:核心域、通用域和支撐域。
核心域
決定產(chǎn)品和公司核心競爭力的子域是核心域,它是業(yè)務(wù)成功的主要因素和公司的核心競爭力。<font color=red>子域的核心業(yè)務(wù)</font>。
通用域
沒有太多個(gè)性化的訴求,同時(shí)被多個(gè)子域使用的通用功能子域是通用域。<font color=red>多個(gè)子域共用的業(yè)務(wù)</font>。
支撐域
既不包含決定產(chǎn)品和公司核心競爭力的功能,也不包含通用功能的子域,它就是支撐域。<font color=red>基礎(chǔ)支撐層,主要是一些功能性的代碼實(shí)現(xiàn),如工具類等</font>。
限界上下文:定義領(lǐng)域邊界的利器
通用語言定義上下文含義,限界上下文則定義領(lǐng)域邊界。
通用語言
? 在事件風(fēng)暴過程中,通過團(tuán)隊(duì)交流達(dá)成共識(shí)的,能夠<font color=red>簡單、清晰、準(zhǔn)確描述業(yè)務(wù)涵義和規(guī)則</font>的語言就是通用語言。也就是說,通用語言是團(tuán)隊(duì)統(tǒng)一的語言,不管你在團(tuán)隊(duì)中承擔(dān)什么角色,在同一個(gè)領(lǐng)域的軟件生命周期里都使用統(tǒng)一的語言進(jìn)行交流。
? 通用語言包含<font color=red>術(shù)語</font>和<font color=red>用例場景</font>,并且能夠直接反映在代碼中。
通用語言中的名詞可以給領(lǐng)域?qū)ο竺?,如商品、訂單等,?duì)應(yīng)實(shí)體對(duì)象;
而動(dòng)詞則表示一個(gè)動(dòng)作或事件,如商品已下單、訂單已付款等,對(duì)應(yīng)領(lǐng)域事件或者命令。
從事件風(fēng)暴建立通用語言到領(lǐng)域?qū)ο笤O(shè)計(jì)和代碼落地的完整過程:
在事件風(fēng)暴的過程中,領(lǐng)域?qū)<視?huì)和設(shè)計(jì)、開發(fā)人員一起建立領(lǐng)域模型,在領(lǐng)域建模的過程中會(huì)形成<font color=red>通用的業(yè)務(wù)術(shù)語</font>和<font color=red>用戶故事</font>。事件風(fēng)暴也是一個(gè)項(xiàng)目團(tuán)隊(duì)統(tǒng)一語言的過程。
通過用戶故事分析會(huì)形成一個(gè)個(gè)的領(lǐng)域?qū)ο?,這些<font color=red>領(lǐng)域?qū)ο?lt;/font>對(duì)應(yīng)領(lǐng)域模型的<font color=red>業(yè)務(wù)對(duì)象</font>,<font color=red>每一個(gè)業(yè)務(wù)對(duì)象和領(lǐng)域?qū)ο蠖加型ㄓ玫拿~術(shù)語,并且一一映射</font>。
微服務(wù)代碼模型來源于領(lǐng)域模型,每個(gè)代碼模型的代碼對(duì)象跟領(lǐng)域?qū)ο笠灰粚?duì)應(yīng)。
作者的經(jīng)驗(yàn)分享
設(shè)計(jì)過程中我們可以用一些表格,來記錄事件風(fēng)暴和微服務(wù)設(shè)計(jì)過程中產(chǎn)生的領(lǐng)域?qū)ο蠹捌鋵傩?/p>
DDD 分析和設(shè)計(jì)過程中的每一個(gè)環(huán)節(jié)都需要保證限界上下文內(nèi)術(shù)語的統(tǒng)一,在代碼模型設(shè)計(jì)的時(shí)侯就要建立領(lǐng)域?qū)ο蠛痛a對(duì)象的一一映射,從而保證業(yè)務(wù)模型和代碼模型的一致,實(shí)現(xiàn)業(yè)務(wù)語言與代碼語言的統(tǒng)一。
限界上下文
? 我們知道語言都有它的語義環(huán)境,同樣,通用語言也有它的上下文環(huán)境。為了避免同樣的概念或語義在不同的上下文環(huán)境中產(chǎn)生歧義,DDD 在戰(zhàn)略設(shè)計(jì)上提出了“限界上下文”這個(gè)概念,用來<font color=red>確定語義所在的領(lǐng)域邊界</font>。
? 我們可以將限界上下文拆解為兩個(gè)詞:限界和上下文。
限界就是領(lǐng)域的邊界
上下文則是語義環(huán)境。
? 通過領(lǐng)域的限界上下文,我們就可以在統(tǒng)一的領(lǐng)域邊界內(nèi)用統(tǒng)一的語言進(jìn)行交流。
作者認(rèn)為限界上下文的定義
? 用來封裝通用語言和領(lǐng)域?qū)ο螅峁┥舷挛沫h(huán)境,保證在領(lǐng)域之內(nèi)的一些術(shù)語、業(yè)務(wù)相關(guān)對(duì)象等(通用語言)有一個(gè)確切的含義,沒有二義性。這個(gè)邊界定義了模型的適用范圍,使團(tuán)隊(duì)所有成員能夠明確地知道什么應(yīng)該在模型中實(shí)現(xiàn),什么不應(yīng)該在模型中實(shí)現(xiàn)。
例子:
例子一:
? 在一個(gè)明媚的早晨,孩子起床問媽媽:“今天應(yīng)該穿幾件衣服呀?”媽媽回答:“能穿多少就穿多少!”那到底是穿多還是穿少呢?如果沒有具體的語義環(huán)境,還真不太好理解。但是,如果你已經(jīng)知道了這句話的語義環(huán)境,比如是寒冬臘月或者是炎炎夏日,那理解這句話的涵義就會(huì)很容易了。所以語言離不開它的語義環(huán)境。而業(yè)務(wù)的通用語言就有它的業(yè)務(wù)邊界,我們不大可能用一個(gè)簡單的術(shù)語沒有歧義地去描述一個(gè)復(fù)雜的業(yè)務(wù)領(lǐng)域。限界上下文就是用來細(xì)分領(lǐng)域,從而定義通用語言所在的邊界。
例子二:
現(xiàn)在我們用一個(gè)保險(xiǎn)領(lǐng)域的例子來說明下術(shù)語的邊界。保險(xiǎn)業(yè)務(wù)領(lǐng)域有投保單、保單、批單、賠案等保險(xiǎn)術(shù)語,它們分別應(yīng)用于保險(xiǎn)的不同業(yè)務(wù)流程。
客戶投保時(shí),業(yè)務(wù)人員記錄投保信息,系統(tǒng)對(duì)應(yīng)有投保單實(shí)體對(duì)象。
繳費(fèi)完成后,業(yè)務(wù)人員將投保單轉(zhuǎn)為保單,系統(tǒng)對(duì)應(yīng)有保單實(shí)體對(duì)象,保單實(shí)體與投保單實(shí)體關(guān)聯(lián)。
如客戶需要修改保單信息,保單變?yōu)榕鷨危到y(tǒng)對(duì)應(yīng)有批單實(shí)體對(duì)象,批單實(shí)體與保單實(shí)體關(guān)聯(lián)。
如果客戶發(fā)生理賠,生成賠案,系統(tǒng)對(duì)應(yīng)有報(bào)案實(shí)體對(duì)象,報(bào)案實(shí)體對(duì)象與保單或者批單實(shí)體關(guān)聯(lián)。
投保單、保單、批單、賠案等,這些術(shù)語雖然都跟保單有關(guān),但不能將保單這個(gè)術(shù)語作用在保險(xiǎn)全業(yè)務(wù)領(lǐng)域。因?yàn)樾g(shù)語有它的邊界,超出了邊界理解上就會(huì)出現(xiàn)問題。
個(gè)人理解限界上下文的作用:
1.確定通用語言的適用范圍和語義;
2.限定領(lǐng)域模型中的業(yè)務(wù)對(duì)象的適用范圍;
3.領(lǐng)域邊界就是通過限界上下文來定義的;
限界上下文和微服務(wù)的關(guān)系
保險(xiǎn)領(lǐng)域還是很復(fù)雜的,在這里我用一個(gè)簡化的保險(xiǎn)模型來說明下限界上下文和微服務(wù)的關(guān)系。
圖4
首先,領(lǐng)域可以拆分為多個(gè)子領(lǐng)域。一個(gè)領(lǐng)域相當(dāng)于一個(gè)問題域,領(lǐng)域拆分為子域的過程就是大問題拆分為小問題的過程。在這個(gè)圖里面保險(xiǎn)領(lǐng)域被拆分為:投保、支付、保單管理和理賠四個(gè)子域。
子域還可根據(jù)需要進(jìn)一步拆分為子子域,比如,支付子域可繼續(xù)拆分為收款和付款子子域。拆到一定程度后,有些子子域的領(lǐng)域邊界就可能變成限界上下文的邊界了。
子域可能會(huì)包含多個(gè)限界上下文,如理賠子域就包括報(bào)案、查勘和定損等多個(gè)限界上下文(限界上下文與理賠的子子域領(lǐng)域邊界重合)。也有可能子域本身的邊界就是限界上下文邊界,如投保子域。
每個(gè)領(lǐng)域模型都有它對(duì)應(yīng)的限界上下文,團(tuán)隊(duì)在限界上下文內(nèi)用通用語言交流。領(lǐng)域內(nèi)所有限界上下文的領(lǐng)域模型構(gòu)成整個(gè)領(lǐng)域的領(lǐng)域模型。
理論上限界上下文就是微服務(wù)的邊界。我們<font color=red>將限界上下文內(nèi)的領(lǐng)域模型映射到微服務(wù),就完成了從問題域到軟件的解決方案</font>??梢哉f,限界上下文是微服務(wù)設(shè)計(jì)和拆分的主要依據(jù)。在領(lǐng)域模型中,如果不考慮技術(shù)異構(gòu)、團(tuán)隊(duì)溝通等其它外部因素,一個(gè)限界上下文理論上就可以設(shè)計(jì)為一個(gè)微服務(wù)。
實(shí)體和值對(duì)象:從領(lǐng)域模型的基礎(chǔ)單元看系統(tǒng)設(shè)計(jì)
實(shí)體
? 在 DDD 中有這樣一類對(duì)象,它們擁有唯一標(biāo)識(shí)符,且標(biāo)識(shí)符在歷經(jīng)各種狀態(tài)變更后仍能保持一致。對(duì)這些對(duì)象而言,重要的不是其屬性,而是其<font color=red>延續(xù)性</font>和<font color=red>標(biāo)識(shí)</font>,對(duì)象的延續(xù)性和標(biāo)識(shí)會(huì)<font color=red>跨越甚至超出軟件的生命周期</font>。我們把這樣的對(duì)象稱為實(shí)體。
? <font color=red>實(shí)體是由屬性構(gòu)成,其有唯一性,即擁有唯一性的屬性,比如id</font>。
業(yè)務(wù)形態(tài)
實(shí)體是領(lǐng)域模型的一個(gè)業(yè)務(wù)對(duì)象的體現(xiàn),定義了一類業(yè)務(wù)對(duì)象,該業(yè)務(wù)對(duì)象對(duì)應(yīng)一類業(yè)務(wù),自創(chuàng)建起其屬性,除了唯一標(biāo)識(shí)屬性不可改變,剩下的屬性都可以改變,無論非唯一屬性如何改變,他依舊代表他被創(chuàng)建時(shí)的那個(gè)業(yè)務(wù)對(duì)象,也就是其最大的特點(diǎn),唯一性和延續(xù)性。
比如創(chuàng)建出一個(gè)用戶,無論他的年齡,身份,職業(yè)如何改變,但是他的id不變,他依舊是那個(gè)用戶。
代碼體現(xiàn)
一個(gè)擁有唯一標(biāo)識(shí)的DO對(duì)象,domain。
運(yùn)行的形態(tài)
擁有唯一標(biāo)識(shí)的domain,除了唯一標(biāo)識(shí)的屬性不可改變,其他屬性理論上都可以改變。
實(shí)體的數(shù)據(jù)庫形態(tài)
實(shí)體對(duì)應(yīng)的是一類業(yè)務(wù)對(duì)象,并不和數(shù)據(jù)庫表直接對(duì)應(yīng),持久層對(duì)象(dao/repository)直接對(duì)應(yīng)數(shù)據(jù)庫表。一個(gè)業(yè)務(wù)對(duì)象可以解析成多個(gè)持久層對(duì)象。
值對(duì)象
實(shí)體中的一些屬性可以按照<font color=red>一定的業(yè)務(wù)邏輯進(jìn)行聚合成一個(gè)集合(具體體現(xiàn)為對(duì)象或者一個(gè)json)</font>。<font color=red>其沒有唯一性,但是其自創(chuàng)建起不能被修改</font>,其由屬性或者json組成,其中的屬性不能被修改,只能被完全替換。
《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》一書中對(duì)值對(duì)象的定義:通過對(duì)象屬性值來識(shí)別的對(duì)象,它將多個(gè)相關(guān)屬性組合為一個(gè)概念整體。
<font color=pink>例子</font>
人員實(shí)體原本包括:姓名、年齡、性別以及人員所在的省、市、縣和街道等屬性。這樣顯示地址相關(guān)的屬性就很零碎了對(duì)不對(duì)?現(xiàn)在,我們可以將“省、市、縣和街道等屬性”拿出來構(gòu)成一個(gè)“地址屬性集合”,這個(gè)集合就是值對(duì)象了。
值對(duì)象的業(yè)務(wù)形態(tài)
其按照一定的業(yè)務(wù)邏輯對(duì)一些相關(guān)屬性進(jìn)行聚合。
值對(duì)象的代碼形態(tài)
可能是一個(gè)類
也可能是一個(gè)json
值對(duì)象的運(yùn)行形態(tài)
值對(duì)象沒有唯一標(biāo)志,其被創(chuàng)建后,不允許對(duì)其中屬性的更改,只能整體替換。
值對(duì)象按照嵌入實(shí)體的方式有兩種實(shí)現(xiàn):
1.將一些實(shí)體中的屬性聚合成類。
2.序列化大對(duì)象,將值對(duì)象序列化成json格式,存儲(chǔ)在某一個(gè)屬性上。
嵌入方式
值對(duì)象嵌入到實(shí)體的話,有這樣兩種不同的數(shù)據(jù)格式,也可以說是兩種方式,分別是屬性嵌入的方式和序列化大對(duì)象的方式。
案例 1:以屬性嵌入的方式形成的人員實(shí)體對(duì)象,地址值對(duì)象直接以屬性值嵌入人員實(shí)體中。
案例 2:以序列化大對(duì)象的方式形成的人員實(shí)體對(duì)象,地址值對(duì)象被序列化成大對(duì)象 Json 串后,嵌入人員實(shí)體中。
值對(duì)象的數(shù)據(jù)庫形態(tài)
值對(duì)象最大的特點(diǎn)體現(xiàn)就在這,其并不對(duì)應(yīng)某一個(gè)表,只對(duì)應(yīng)表中的某幾個(gè)字段或者其中的一個(gè)字段。
在領(lǐng)域建模時(shí),我們可以將部分對(duì)象設(shè)計(jì)為值對(duì)象,保留對(duì)象的業(yè)務(wù)涵義,同時(shí)又減少了實(shí)體的數(shù)量;在數(shù)據(jù)建模時(shí),我們可以將值對(duì)象嵌入實(shí)體,減少實(shí)體表的數(shù)量,簡化數(shù)據(jù)庫設(shè)計(jì)。
領(lǐng)域驅(qū)動(dòng)中實(shí)體和值對(duì)象與數(shù)據(jù)庫驅(qū)動(dòng)的區(qū)別
領(lǐng)域驅(qū)動(dòng)先設(shè)計(jì)領(lǐng)域,后設(shè)計(jì)表,弱化了表在業(yè)務(wù)中的體現(xiàn),通過設(shè)計(jì)領(lǐng)域?qū)ο?,解耦業(yè)務(wù)和表之間的直接關(guān)系。簡化數(shù)據(jù)庫設(shè)計(jì),不用像以前數(shù)據(jù)庫驅(qū)動(dòng)設(shè)計(jì),一個(gè)實(shí)體對(duì)應(yīng)一個(gè)表,減少了實(shí)體表的數(shù)量,可以簡單、清晰地表達(dá)業(yè)務(wù)概念。這種設(shè)計(jì)方式雖然降低了數(shù)據(jù)庫設(shè)計(jì)的復(fù)雜度,
值對(duì)象的優(yōu)點(diǎn)和局限性
優(yōu)點(diǎn)
通過值對(duì)象對(duì)一個(gè)實(shí)體中某一些屬性進(jìn)行聚合,可以直接簡化數(shù)據(jù)庫的設(shè)計(jì),減少數(shù)據(jù)庫表的數(shù)量,弱化數(shù)據(jù)庫在業(yè)務(wù)中的耦合性,弱化數(shù)據(jù)庫的作用,只把數(shù)據(jù)庫作為一個(gè)保存數(shù)據(jù)的倉庫即可。
局限性
值對(duì)象采用序列化大對(duì)象的方法簡化了數(shù)據(jù)庫設(shè)計(jì),減少了實(shí)體表的數(shù)量,可以簡單、清晰地表達(dá)業(yè)務(wù)概念。這種設(shè)計(jì)方式雖然降低了數(shù)據(jù)庫設(shè)計(jì)的復(fù)雜度,但卻無法滿足基于值對(duì)象的快速查詢,會(huì)導(dǎo)致搜索值對(duì)象屬性值變得異常困難。
實(shí)體和值對(duì)象的關(guān)系
實(shí)體是業(yè)務(wù)中具體的業(yè)務(wù)對(duì)象的體現(xiàn),而值對(duì)象是業(yè)務(wù)中對(duì)象中有一定相關(guān)性屬性的一個(gè)集合。
實(shí)體有唯一性和延續(xù)性。
值對(duì)象沒有唯一性,其屬性有不可變性,若要改變值對(duì)象,必須完全替換值對(duì)象,不能修改其中具體屬性。
聚合和聚合根
聚合
領(lǐng)域模型內(nèi)的實(shí)體和值對(duì)象就好比個(gè)體,而能讓實(shí)體和值對(duì)象協(xié)同工作的組織就是聚合,它用來確保這些領(lǐng)域?qū)ο笤趯?shí)現(xiàn)共同的業(yè)務(wù)邏輯時(shí),能保證數(shù)據(jù)的一致性。
你可以這么理解,聚合就是由業(yè)務(wù)和邏輯緊密關(guān)聯(lián)的實(shí)體和值對(duì)象組合而成的,聚合是數(shù)據(jù)修改和持久化的基本單元,每一個(gè)聚合對(duì)應(yīng)一個(gè)倉儲(chǔ),實(shí)現(xiàn)數(shù)據(jù)的持久化。
個(gè)人理解:
聚合:
按照一個(gè)功能/模塊/業(yè)務(wù)對(duì)項(xiàng)目進(jìn)行劃分,將符合同一個(gè)功能/模塊/業(yè)務(wù)的實(shí)體,值對(duì)象找出來,聚合到同一個(gè)領(lǐng)域內(nèi)。
聚合就是由業(yè)務(wù)和邏輯緊密關(guān)聯(lián)的實(shí)體和值對(duì)象組合而成的。
聚合屬于戰(zhàn)略設(shè)計(jì)上的一個(gè)概念,不直接對(duì)應(yīng)代碼實(shí)現(xiàn),聚合在代碼中的體現(xiàn)是聚合根。
聚合根
負(fù)責(zé)按照聚合的邏輯,將符合同一個(gè)業(yè)務(wù)邏輯的Entity,VO,聚合到一個(gè)class中。
如何設(shè)計(jì)聚合
第 1 步:
采用事件風(fēng)暴,根據(jù)業(yè)務(wù)行為,梳理出在投保過程中發(fā)生這些行為的所有的實(shí)體和值對(duì)象,比如投保單、標(biāo)的、客戶、被保人等等。
第 2 步:
從眾多實(shí)體中選出適合作為對(duì)象管理者的根實(shí)體,也就是聚合根。判斷一個(gè)實(shí)體是否是聚合根,你可以結(jié)合以下場景分析:是否有獨(dú)立的生命周期?是否有全局唯一 ID?是否可以創(chuàng)建或修改其它對(duì)象?是否有專門的模塊來管這個(gè)實(shí)體。圖中的聚合根分別是投保單和客戶實(shí)體。
第 3 步:
根據(jù)業(yè)務(wù)單一職責(zé)和高內(nèi)聚原則,找出與聚合根關(guān)聯(lián)的所有緊密依賴的實(shí)體和值對(duì)象。構(gòu)建出 1 個(gè)包含聚合根(唯一)、多個(gè)實(shí)體和值對(duì)象的對(duì)象集合,這個(gè)集合就是聚合。在圖中我們構(gòu)建了客戶和投保這兩個(gè)聚合。
第 4 步:
在聚合內(nèi)根據(jù)聚合根、實(shí)體和值對(duì)象的依賴關(guān)系,畫出對(duì)象的引用和依賴模型。這里我需要說明一下:投保人和被保人的數(shù)據(jù),是通過關(guān)聯(lián)客戶 ID 從客戶聚合中獲取的,在投保聚合里它們是投保單的值對(duì)象,這些值對(duì)象的數(shù)據(jù)是客戶的冗余數(shù)據(jù),即使未來客戶聚合的數(shù)據(jù)發(fā)生了變更,也不會(huì)影響投保單的值對(duì)象數(shù)據(jù)。從圖中我們還可以看出實(shí)體之間的引用關(guān)系,比如在投保聚合里投保單聚合根引用了報(bào)價(jià)單實(shí)體,報(bào)價(jià)單實(shí)體則引用了報(bào)價(jià)規(guī)則子實(shí)體。
第 5 步:
多個(gè)聚合根據(jù)業(yè)務(wù)語義和上下文一起劃分到同一個(gè)限界上下文內(nèi)。這就是一個(gè)聚合誕生的完整過程了。
聚合的一些設(shè)計(jì)原則
《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》一書中對(duì)聚合設(shè)計(jì)原則的描述,原文是有點(diǎn)不太好理解的,我來給你解釋一下。
1. 在一致性邊界內(nèi)建模真正的不變條件。
<font color=red>聚合用來封裝真正的不變性</font>,而不是簡單地將對(duì)象組合在一起。
- 聚合內(nèi)有一套不變的業(yè)務(wù)規(guī)則,各實(shí)體和值對(duì)象按照統(tǒng)一的業(yè)務(wù)規(guī)則運(yùn)行,實(shí)現(xiàn)對(duì)象數(shù)據(jù)的一致性,邊界之外的任何東西都與該聚合無關(guān),這就是聚合能實(shí)現(xiàn)業(yè)務(wù)高內(nèi)聚的原因。
<font color=apple green>個(gè)人理解: 聚合實(shí)際上是聚合了一類業(yè)務(wù)邏輯</font>
- 聚合內(nèi)的業(yè)務(wù)規(guī)則一般不變,實(shí)體和值對(duì)象都遵從于該業(yè)務(wù)邏輯。任何外界變化不會(huì)影響到聚合,以實(shí)現(xiàn)聚合的高聚合特性。
2. 設(shè)計(jì)小聚合
- 如果聚合設(shè)計(jì)得過大,聚合會(huì)因?yàn)榘^多的實(shí)體,導(dǎo)致實(shí)體之間的管理過于復(fù)雜,高頻操作時(shí)會(huì)出現(xiàn)并發(fā)沖突或者數(shù)據(jù)庫鎖,最終導(dǎo)致系統(tǒng)可用性變差。而小聚合設(shè)計(jì)則可以降低由于業(yè)務(wù)過大導(dǎo)致聚合重構(gòu)的可能性,讓領(lǐng)域模型更能適應(yīng)業(yè)務(wù)的變化。
<font color=apple green>個(gè)人理解: 小聚合</font>
- 盡量簡單化聚合,防止聚合過于龐大,聚合越小,業(yè)務(wù)重構(gòu)的成本越低。
3. 通過唯一標(biāo)識(shí)引用其它聚合
- 聚合之間是通過關(guān)聯(lián)外部聚合根 ID 的方式引用,而不是直接對(duì)象引用的方式。外部聚合的對(duì)象放在聚合邊界內(nèi)管理,容易導(dǎo)致聚合的邊界不清晰,也會(huì)增加聚合之間的耦合度。
<font color=apple green>個(gè)人理解: 防止聚合之間直接引用,而是通過唯一標(biāo)識(shí)引用其他聚合</font>
- 如果直接引用其他聚合對(duì)象,會(huì)增加耦合度,通過唯一標(biāo)識(shí)聚合其他聚合根。
4. 在邊界之外使用最終一致性
- 聚合內(nèi)數(shù)據(jù)強(qiáng)一致性,而聚合之間數(shù)據(jù)最終一致性。在一次事務(wù)中,最多只能更改一個(gè)聚合的狀態(tài)。如果一次業(yè)務(wù)操作涉及多個(gè)聚合狀態(tài)的更改,應(yīng)采用領(lǐng)域事件的方式異步修改相關(guān)的聚合,實(shí)現(xiàn)聚合之間的解耦。
<font color=apple green>個(gè)人理解: 同一個(gè)聚合內(nèi)數(shù)據(jù)強(qiáng)一致性,聚合之間數(shù)據(jù)最終一致性。</font>
5. 通過應(yīng)用層實(shí)現(xiàn)跨聚合的服務(wù)調(diào)用
- 為實(shí)現(xiàn)微服務(wù)內(nèi)聚合之間的解耦,以及未來以聚合為單位的微服務(wù)組合和拆分,應(yīng)避免跨聚合的領(lǐng)域服務(wù)調(diào)用和跨聚合的數(shù)據(jù)庫表關(guān)聯(lián)
<font color=apple green>個(gè)人理解: 通過應(yīng)用層實(shí)現(xiàn)跨聚合的服務(wù)調(diào)用</font>
- 對(duì)于跨聚合之間的調(diào)用,放在應(yīng)用層中,在聚合之間再加一層。
上面的這些原則是 DDD 的一些通用的設(shè)計(jì)原則,還是那句話:“適合自己的才是最好的。”在系統(tǒng)設(shè)計(jì)過程時(shí),你一定要考慮項(xiàng)目的具體情況,如果面臨使用的便利性、高性能要求、技術(shù)能力缺失和全局事務(wù)管理等影響因素,這些原則也并不是不能突破的,總之一切以解決實(shí)際問題為出發(fā)點(diǎn)。