1 背景
業(yè)務(wù)初期召烂,我們的功能大都非常簡單碱工,普通的CRUD就能滿足,此時系統(tǒng)是清晰的奏夫。隨著迭代的不斷演化怕篷,業(yè)務(wù)邏輯變得越來越復(fù)雜,我們的系統(tǒng)也越來越冗雜酗昼。模塊彼此關(guān)聯(lián)廊谓,誰都很難說清模塊的具體功能意圖是啥。修改一個功能時麻削,往往光回溯該功能需要的修改點(diǎn)就需要很長時間蒸痹,更別提修改帶來的不可預(yù)知的影響面。
訂單服務(wù)接口中提供了查詢呛哟、創(chuàng)建訂單相關(guān)的接口叠荠,也提供了訂單評價、支付扫责、保險的接口榛鼎。同時我們的表也是一個訂單大表,包含了非常多字段鳖孤。在我們維護(hù)代碼時者娱,牽一發(fā)而動全身,很可能只是想改下評價相關(guān)的功能淌铐,卻影響到了創(chuàng)單核心路徑肺然。雖然我們可以通過測試保證功能完備性,但當(dāng)我們在訂單領(lǐng)域有大量需求同時并行開發(fā)時腿准,改動重疊际起、惡性循環(huán)拾碌、疲于奔命修改各種問題。
2 軟件系統(tǒng)復(fù)雜性應(yīng)對
解決復(fù)雜和大規(guī)模軟件的武器可以被粗略地歸為三類:
抽象
街望、分治
和知識
校翔。
分治
: 把問題空間分割為規(guī)模更小且易于處理的若干子問題。分割后的問題需要足夠小灾前,以便一個人單槍匹馬就能夠解決他們防症;其次,必須考慮如何將分割后的各個部分裝配為整體哎甲。分割得越合理越易于理解蔫敲,在裝配成整體時,所需跟蹤的細(xì)節(jié)也就越少炭玫。即更容易設(shè)計各部分的協(xié)作方式奈嘿。評判什么是分治得好,即高內(nèi)聚低耦合吞加。
抽象
: 使用抽象能夠精簡問題空間裙犹,而且問題越小越容易理解。舉個例子衔憨,從北京到上海出差叶圃,可以先理解為使用交通工具前往,但不需要一開始就想清楚到底是高鐵還是飛機(jī)践图,以及乘坐他們需要注意什么掺冠。
知識
: 顧名思義,DDD可以認(rèn)為是知識的一種平项。
DDD提供了這樣的知識手段赫舒,讓我們知道如何抽象出限界上下文以及如何去分治。
3 與微服務(wù)架構(gòu)相得益彰
微服務(wù)架構(gòu)眾所周知闽瓢,此處不做贅述接癌。我們創(chuàng)建微服務(wù)時,需要創(chuàng)建一個高內(nèi)聚扣讼、低耦合的微服務(wù)缺猛。而DDD中的限界上下文則完美匹配微服務(wù)要求,可以將該限界上下文理解為一個微服務(wù)進(jìn)程椭符。
上述是從更直觀的角度來描述兩者的相似處荔燎。
在系統(tǒng)復(fù)雜之后,我們都需要用分治來拆解問題销钝。一般有兩種方式有咨,技術(shù)維度和業(yè)務(wù)維度。技術(shù)維度是類似MVC這樣蒸健,業(yè)務(wù)維度則是指按業(yè)務(wù)領(lǐng)域來劃分系統(tǒng)座享。
微服務(wù)架構(gòu)更強(qiáng)調(diào)從業(yè)務(wù)維度去做分治來應(yīng)對系統(tǒng)復(fù)雜度婉商,而DDD也是同樣的著重業(yè)務(wù)視角。 如果兩者在追求的目標(biāo)(業(yè)務(wù)維度)達(dá)到了上下文的統(tǒng)一渣叛,那么在具體做法上有什么聯(lián)系和不同呢丈秩?
我們將架構(gòu)設(shè)計活動精簡為以下三個層面:
業(yè)務(wù)架構(gòu)——根據(jù)業(yè)務(wù)需求設(shè)計業(yè)務(wù)模塊及其關(guān)系
系統(tǒng)架構(gòu)——設(shè)計系統(tǒng)和子系統(tǒng)的模塊
技術(shù)架構(gòu)——決定采用的技術(shù)及框架
DDD的核心訴求就是將業(yè)務(wù)架構(gòu)映射到系統(tǒng)架構(gòu)上,在響應(yīng)業(yè)務(wù)變化調(diào)整業(yè)務(wù)架構(gòu)時淳衙,也隨之變化系統(tǒng)架構(gòu)蘑秽。而微服務(wù)追求業(yè)務(wù)層面的復(fù)用,設(shè)計出來的系統(tǒng)架構(gòu)和業(yè)務(wù)一致箫攀;在技術(shù)架構(gòu)上則系統(tǒng)模塊之間充分解耦肠牲,可以自由地選擇合適的技術(shù)架構(gòu),去中心化地治理技術(shù)和數(shù)據(jù)匠童。
4 業(yè)務(wù)拆分領(lǐng)域模型
1埂材、 根據(jù)需求劃分出初步的領(lǐng)域和限界上下文塑顺,以及上下文之間的關(guān)系汤求;
2、 進(jìn)一步分析每個上下文內(nèi)部严拒,識別出哪些是實(shí)體扬绪,哪些是值對象;
3 裤唠、對實(shí)體挤牛、值對象進(jìn)行關(guān)聯(lián)和聚合,劃分出聚合的范疇和聚合根种蘸;
4 墓赴、為聚合根設(shè)計倉儲,并思考實(shí)體或值對象的創(chuàng)建方式航瞭;
5 诫硕、在工程中實(shí)踐領(lǐng)域模型,并在實(shí)踐中檢驗(yàn)?zāi)P偷暮侠硇钥睿雇颇P椭胁蛔愕牡胤讲⒅貥?gòu)章办。
5 名詞介紹
限界上下文
一個由顯示邊界限定的特定職責(zé)。領(lǐng)域模型便存在于這個邊界之內(nèi)滨彻。在邊界內(nèi)藕届,每一個模型概念,包括它的屬性和操作亭饵,都具有特殊的含義休偶。
用來封裝通用語言和領(lǐng)域?qū)ο螅峁┥舷挛沫h(huán)境辜羊,保證在領(lǐng)域之內(nèi)的一些術(shù)語踏兜、業(yè)務(wù)相關(guān)對象等(通用語言)有一個確切的含義懂算,沒有二義性。這個邊界定義了模型的適用范圍庇麦,使團(tuán)隊所有成員能夠明確地知道什么應(yīng)該在模型中實(shí)現(xiàn)计技,什么不應(yīng)該在模型中實(shí)現(xiàn)。限界上下文劃分
我們的實(shí)踐是山橄,考慮產(chǎn)品所講的通用語言垮媒,從中提取一些術(shù)語稱之為概念對象,尋找對象之間的聯(lián)系航棱;或者從需求里提取一些動詞睡雇,觀察動詞和對象之間的關(guān)系;我們將緊耦合的各自圈在一起饮醇,觀察他們內(nèi)在的聯(lián)系它抱,從而形成對應(yīng)的界限上下文。形成之后朴艰,我們可以嘗試用語言來描述下界限上下文的職責(zé)观蓄,看它是否清晰、準(zhǔn)確祠墅、簡潔和完整侮穿。簡言之,限界上下文應(yīng)該從需求出發(fā)毁嗦,按領(lǐng)域劃分亲茅。
限界上下文之間的映射關(guān)系
合作關(guān)系(Partnership):兩個上下文緊密合作的關(guān)系,一榮俱榮狗准,一損俱損克锣。
共享內(nèi)核(Shared Kernel):兩個上下文依賴部分共享的模型。
客戶方-供應(yīng)方開發(fā)(Customer-Supplier Development):上下文之間有組織的上下游依賴腔长。
遵奉者(Conformist):下游上下文只能盲目依賴上游上下文袭祟。
防腐層(Anticorruption Layer 簡稱:ACL):一個上下文通過一些適配和轉(zhuǎn)換與另一個上下文交互。
開放主機(jī)服務(wù)(Open Host Service 簡稱:OHS):定義一種協(xié)議來讓其他上下文來對本上下文進(jìn)行訪問饼酿。
發(fā)布語言(Published Language 簡稱:PL):通常與OHS一起使用榕酒,用于定義開放主機(jī)的協(xié)議。
大泥球(Big Ball of Mud):混雜在一起的上下文關(guān)系故俐,邊界不清晰想鹰。
另謀他路(SeparateWay):兩個完全沒有任何聯(lián)系的上下文。
通過上下文映射關(guān)系药版,我們明確的限制了限界上下文的耦合性辑舷,即在抽獎平臺中,無論是上下文內(nèi)部交互(合作關(guān)系)還是與外部上下文交互(防腐層)槽片,耦合度都限定在數(shù)據(jù)耦合(Data Coupling)的層級何缓。
實(shí)體
當(dāng)一個對象由其標(biāo)識(而不是屬性)區(qū)分時肢础,這種對象稱為實(shí)體(Entity)。
值對象
特性 : 沒有唯一標(biāo)識 不變性
在值對象中碌廓,不關(guān)心標(biāo)識传轰,只要我們能確定值對象的屬性值都一樣,我們就可以說這兩個值對象是相同的谷婆。
比如說兩個學(xué)生的家庭地址(省市縣街道門牌號)是一樣的慨蛙,我們就可以認(rèn)為是同一個地址,這就是相等性比較纪挎。
如果學(xué)生在修改地址的時候期贫,無論修改的省或者市或者縣,我們不關(guān)心改了多少异袄,而是 將整個值對象覆蓋通砍,它具有不變性、相等性和可替換性烤蜕。
聚合根
Aggregate(聚合)是一組相關(guān)對象的集合封孙,作為一個整體被外界訪問,聚合根(Aggregate Root)是這個聚合的根節(jié)點(diǎn)玖绿。
聚合是一個非常重要的概念敛瓷,核心領(lǐng)域往往都需要用聚合來表達(dá)。
聚合由根實(shí)體斑匪,值對象和實(shí)體組成。
如何創(chuàng)建好的聚合?
邊界內(nèi)的內(nèi)容具有一致性:在一個事務(wù)中只修改一個聚合實(shí)例锋勺。如果你發(fā)現(xiàn)邊界內(nèi)很難接受強(qiáng)一致蚀瘸,不管是出于性能或產(chǎn)品需求的考慮,應(yīng)該考慮剝離出獨(dú)立的聚合庶橱,采用最終一致的方式贮勃。
設(shè)計小聚合:大部分的聚合都可以只包含根實(shí)體,而無需包含其他實(shí)體苏章。即使一定要包含寂嘉,可以考慮將其創(chuàng)建為值對象。
通過唯一標(biāo)識來引用其他聚合或?qū)嶓w:當(dāng)存在對象之間的關(guān)聯(lián)時枫绅,建議引用其唯一標(biāo)識而非引用其整體對象泉孩。如果是外部上下文中的實(shí)體,引用其唯一標(biāo)識或?qū)⑿枰膶傩詷?gòu)造值對象并淋。 如果聚合創(chuàng)建復(fù)雜寓搬,推薦使用工廠方法來屏蔽內(nèi)部復(fù)雜的創(chuàng)建邏輯。
領(lǐng)域服務(wù)
一些重要的領(lǐng)域行為或操作县耽,可以歸類為領(lǐng)域服務(wù)句喷。它既不是實(shí)體镣典,也不是值對象的范疇。
領(lǐng)域中的服務(wù)表示一個無狀態(tài)的操作唾琼,它用于實(shí)現(xiàn)特定于某個領(lǐng)域的任務(wù)兄春。這里我們要搞清楚什么樣的操作需要實(shí)體,值對象锡溯,什么樣的操作需要采用領(lǐng)域服務(wù)神郊。
另外,領(lǐng)域服務(wù)不是應(yīng)用服務(wù)趾唱,在應(yīng)用服務(wù)中我們不需要處理業(yè)務(wù)邏輯涌乳,業(yè)務(wù)邏輯都落在領(lǐng)域服務(wù)中。
領(lǐng)域服務(wù)發(fā)現(xiàn):
執(zhí)行一個顯著的業(yè)務(wù)操作過程
對領(lǐng)域?qū)ο筮M(jìn)行轉(zhuǎn)換
以多個領(lǐng)域?qū)ο笞鳛檩斎脒M(jìn)行計算甜癞,產(chǎn)生一個值對象夕晓。
過度使用領(lǐng)域服務(wù)將會產(chǎn)生一個貧血模型,例如數(shù)據(jù)建模時悠咱,我們的實(shí)體常用只含有g(shù)et/set方法蒸辆,所有的業(yè)務(wù)邏輯都包含在了service。這樣導(dǎo)致service變成了一個大泥球析既。注意區(qū)分領(lǐng)域服務(wù)與實(shí)體躬贡,值對象行為。
領(lǐng)域事件
領(lǐng)域事件是對領(lǐng)域內(nèi)發(fā)生的活動進(jìn)行的建模眼坏。
領(lǐng)域事件通常是用來與其他聚合解耦的拂玻,采用觀察者模式,一個聚合訂閱另外一個聚合的事件宰译。
實(shí)例 抽獎DDD
注意: U 代表上游 D 代表下游