DDD大家討論的比較多的一般都是DDD的思想和理論寨蹋,很少有文章討論具體是如何實(shí)施和落地迟隅,所以這也導(dǎo)致很多同學(xué)看完了Evans經(jīng)典巨著后對(duì)DDD還是不知道如何去實(shí)施贪庙。這篇文章我們討論下DDD的實(shí)施步驟牡昆,聊聊怎么一步步在項(xiàng)目中實(shí)施DDD砰诵。
在習(xí)慣了傳統(tǒng)的數(shù)據(jù)驅(qū)動(dòng)開發(fā)模式后雨席,View菩咨、Service、dao這種三層分層模式陡厘,開發(fā)者會(huì)很自然的寫出過程式代碼抽米,這種開發(fā)方式中的對(duì)象只是數(shù)據(jù)載體,而沒有行為糙置,是一種貧血對(duì)象模型云茸。以數(shù)據(jù)為中心,以數(shù)據(jù)庫ER圖為設(shè)計(jì)驅(qū)動(dòng)谤饭,分層架構(gòu)在這種開發(fā)模式下可以認(rèn)為是數(shù)據(jù)處理和實(shí)現(xiàn)的過程标捺。
數(shù)據(jù)驅(qū)動(dòng)模式業(yè)務(wù)邏輯都是寫在Service中的,定義的實(shí)體模型充其量只是個(gè)數(shù)據(jù)載體揉抵,沒有任何行為亡容。簡(jiǎn)單的業(yè)務(wù)系統(tǒng)采用這種貧血模型和過程化設(shè)計(jì)是沒有問題的,但在業(yè)務(wù)邏輯復(fù)雜了冤今,業(yè)務(wù)邏輯闺兢、狀態(tài)會(huì)散落到在大量方法中,原本的代碼意圖會(huì)漸漸不明確戏罢,我們將這種情況稱為由貧血癥引起的失憶癥屋谭。
所以才有了目前很多大牛推崇的DDD-領(lǐng)域驅(qū)動(dòng)開發(fā)模式,將實(shí)體模型的數(shù)據(jù)和行為封裝在一起龟糕,并與現(xiàn)實(shí)世界業(yè)務(wù)領(lǐng)域中的業(yè)務(wù)對(duì)象進(jìn)行映射戴而。將領(lǐng)域業(yè)務(wù)邏輯分散到領(lǐng)域?qū)ο笾小?/p>
在目前主流的微服務(wù)開發(fā)過程中,其實(shí)可以把DDD中的限界上下文看作一個(gè)微服務(wù)翩蘸,這個(gè)微服務(wù)對(duì)外提供的服務(wù)是高內(nèi)聚、低耦合的淮逊。微服務(wù)架構(gòu)強(qiáng)調(diào)的是從業(yè)務(wù)緯度去拆分系統(tǒng)催首,而DDD也是同樣看重業(yè)務(wù)領(lǐng)域扶踊。
DDD的核心訴求就是將業(yè)務(wù)架構(gòu)映射到系統(tǒng)架構(gòu)上,在響應(yīng)業(yè)務(wù)變化調(diào)整業(yè)務(wù)架構(gòu)時(shí)郎任,也隨之變化系統(tǒng)架構(gòu)秧耗。而微服務(wù)追求業(yè)務(wù)層面的復(fù)用,設(shè)計(jì)出來的系統(tǒng)架構(gòu)和業(yè)務(wù)一致舶治;在技術(shù)架構(gòu)上則系統(tǒng)模塊之間充分解耦分井,可以自由地選擇合適的技術(shù)架構(gòu),去中心化地治理技術(shù)和數(shù)據(jù)霉猛。
DDD設(shè)計(jì)流程
按照實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)一書中描述的DDD步驟主要有4步:
- 根據(jù)業(yè)務(wù)需求劃分出初步的領(lǐng)域和限界上下文尺锚,以及上下文之間的關(guān)系;
- 進(jìn)一步分析每個(gè)上下文內(nèi)部惜浅,識(shí)別出哪些是實(shí)體瘫辩,哪些是值對(duì)象;對(duì)實(shí)體坛悉、值對(duì)象進(jìn)行關(guān)聯(lián)和聚合伐厌,劃分出聚合的范疇和聚合根;
- 為聚合根設(shè)計(jì)倉(cāng)儲(chǔ)裸影,并思考實(shí)體或值對(duì)象的創(chuàng)建方式挣轨;
- 在工程中實(shí)踐領(lǐng)域模型,并在實(shí)踐中檢驗(yàn)?zāi)P偷暮侠硇孕桑雇颇P椭胁蛔愕牡胤讲⒅貥?gòu)卷扮。
1. 戰(zhàn)略建模
在拿到一個(gè)業(yè)務(wù)需求的時(shí)候,DDD首先需要進(jìn)行戰(zhàn)術(shù)建模界轩,戰(zhàn)術(shù)建模其實(shí)就是DDD的第一步画饥,根據(jù)具體業(yè)務(wù)區(qū)分子域,搞清楚各個(gè)限界上下文之間的關(guān)系浊猾。
限界上下文之間的映射關(guān)系主要有下面這幾種:
- 合作關(guān)系(Partnership):兩個(gè)上下文緊密合作的關(guān)系抖甘,一榮俱榮,一損俱損葫慎。
- 共享內(nèi)核(Shared Kernel):兩個(gè)上下文依賴部分共享的模型衔彻。
- 客戶方-供應(yīng)方開發(fā)(Customer-Supplier Development):上下文之間有組織的上下游依賴。
- 遵奉者(Conformist):下游上下文只能盲目依賴上游上下文偷办。
- 防腐層(Anticorruption Layer):一個(gè)上下文通過一些適配和轉(zhuǎn)換與另一個(gè)上下文交互艰额。
- 開放主機(jī)服務(wù)(Open Host Service):定義一種協(xié)議來讓其他上下文來對(duì)本上下文進(jìn)行訪問。
- 發(fā)布語言(Published Language):通常與OHS一起使用椒涯,用于定義開放主機(jī)的協(xié)議柄沮。
- 大泥球(Big Ball of Mud):混雜在一起的上下文關(guān)系,邊界不清晰。
- 另謀他路(SeparateWay):兩個(gè)完全沒有任何聯(lián)系的上下文祖搓。
通過繪制全局的限界上下文的關(guān)系狱意,能夠梳理清楚業(yè)務(wù)需求中的各個(gè)領(lǐng)域、子領(lǐng)域之間的關(guān)系拯欧。
2. 戰(zhàn)術(shù)建南甓冢—細(xì)化上下文
戰(zhàn)術(shù)建模很重要的一步就是區(qū)分出整個(gè)限界上下文中的實(shí)體、值對(duì)象镐作、聚合藏姐,戰(zhàn)術(shù)建模其實(shí)對(duì)應(yīng)了Entity、ValueObject该贾、Aggregate羔杨。我們要提煉出業(yè)務(wù)中的精華,合理的抽象為這3個(gè)概念靶庙,并且這種抽象是需隨著領(lǐng)域里的概念變化而變化的问畅。這3者的結(jié)合運(yùn)用會(huì)讓我們的項(xiàng)目活起來,這是DDD的核心六荒。這里再把這3個(gè)概念重新梳理一下护姆。
? Entity(實(shí)體): 每個(gè)實(shí)體是唯一的,并且可以相當(dāng)長(zhǎng)的一段時(shí)間內(nèi)持續(xù)地變化掏击。我們可以對(duì)實(shí)體做多次修改卵皂,故一個(gè)實(shí)體對(duì)象可能和它先前的狀態(tài)大不相同。但是砚亭,由于它們擁有相同的身份標(biāo)識(shí)灯变,他們依然是同一個(gè)實(shí)體。例如一件商品在電商商品上下文中是一個(gè)實(shí)體捅膘,通過商品中臺(tái)唯一的商品id來標(biāo)示這個(gè)實(shí)體添祸。
? ValueObject(值對(duì)象):值對(duì)象用于度量和描述事物,當(dāng)你只關(guān)心某個(gè)對(duì)象的屬性時(shí)寻仗,該對(duì)象便可作為一個(gè)值對(duì)象刃泌。實(shí)體與值對(duì)象的區(qū)別在于唯一的身份標(biāo)識(shí)和可變性。當(dāng)一個(gè)對(duì)象用于描述一個(gè)事物署尤,但是又沒有唯一標(biāo)示耙替,那么它就是一個(gè)值對(duì)象。例如商品中的商品類別曹体,類別就沒有一個(gè)唯一標(biāo)示俗扇,通過圖書、服裝箕别、3C這些值就能明確表示這個(gè)商品類別铜幽。
不同上下文領(lǐng)域中的實(shí)體滞谢,在當(dāng)前上下文中需要建模為值對(duì)象,因?yàn)楫?dāng)前領(lǐng)域無法直接修改這些對(duì)象的內(nèi)部屬性除抛。
? Aggregate(聚合):聚合類是實(shí)體的升級(jí)爹凹,是由一組與生俱來就密切相關(guān)實(shí)體和值對(duì)象組合而成的,這整個(gè)組合的最上層實(shí)體就是聚合镶殷。聚合類建議設(shè)計(jì)成小聚合,可以只包含根實(shí)體微酬,而不需要包含其他實(shí)體绘趋,即使一定要包含,可以考慮將其設(shè)計(jì)成值對(duì)象颗管。通過唯一標(biāo)識(shí)來引用其他聚合或者實(shí)體陷遮,如果是外部上下文中的實(shí)體,引用其唯一標(biāo)識(shí)或?qū)⑿枰膶傩詷?gòu)造成值對(duì)象垦江。如果聚合類創(chuàng)建過于復(fù)雜可以將其的創(chuàng)建動(dòng)作封裝在工廠方法里帽馋。
聚合內(nèi)部多個(gè)組成對(duì)象的關(guān)系可以用來指導(dǎo)數(shù)據(jù)庫建表,但是肯定會(huì)存在一些問題比吭,例如最常見的問題绽族,一個(gè)聚合類中有一個(gè)List的值對(duì)象,那么在數(shù)據(jù)庫中建立1:N的關(guān)聯(lián)需要將值對(duì)象單獨(dú)建表衩藤,這時(shí)候值對(duì)象表的id就不要暴露到資源庫外部吧慢,讓外部不可見。
領(lǐng)域服務(wù):一些重要的領(lǐng)域行為可以定義為領(lǐng)域服務(wù)赏表,簡(jiǎn)單的原則可以認(rèn)為一些操作不適合放在實(shí)體或值對(duì)象检诗,那么就可以把這些領(lǐng)域的組合行為定義為領(lǐng)域服務(wù),這里的領(lǐng)域服務(wù)也有點(diǎn)類似于我們常用的3層架構(gòu)的service層瓢剿,但是不同的是逢慌,領(lǐng)域服務(wù)中是不包含實(shí)體類中對(duì)實(shí)體自己操作的行為,實(shí)體自操作的行為都是封裝在實(shí)體類內(nèi)部的间狂。一切領(lǐng)域邏輯的對(duì)外暴露都需要通過領(lǐng)域服務(wù)來完成攻泼。
3. 數(shù)據(jù)倉(cāng)儲(chǔ)設(shè)計(jì)
DDD的設(shè)計(jì)過程中很多同學(xué)對(duì)于數(shù)據(jù)倉(cāng)儲(chǔ)的設(shè)計(jì)存在疑問,不知道DO如何存儲(chǔ)到數(shù)據(jù)庫中前标,其實(shí)業(yè)務(wù)DO最后落表到數(shù)據(jù)庫中也沒有什么特殊的處理方式坠韩,主要還是將DO轉(zhuǎn)成PO(持久化對(duì)象)來進(jìn)行數(shù)據(jù)存儲(chǔ),PO的結(jié)構(gòu)是和存儲(chǔ)表結(jié)構(gòu)對(duì)應(yīng)的炼列,這樣就將DO的業(yè)務(wù)模型保存到了技術(shù)數(shù)據(jù)庫中只搁。下面介紹下整個(gè)DDD中數(shù)據(jù)轉(zhuǎn)換的流程:
首先領(lǐng)域的開放服務(wù)通過信息傳輸對(duì)象(DTO)來完成與外界的數(shù)據(jù)交互;在領(lǐng)域內(nèi)部俭尖,我們通過領(lǐng)域?qū)ο螅―O)作為領(lǐng)域內(nèi)部的數(shù)據(jù)和行為載體氢惋;在資源庫內(nèi)部洞翩,我們沿襲了原有的數(shù)據(jù)庫持久化對(duì)象(PO)進(jìn)行數(shù)據(jù)庫資源的交互。同時(shí)焰望,DTO與DO的轉(zhuǎn)換發(fā)生在領(lǐng)域服務(wù)內(nèi)骚亿,DO與PO的轉(zhuǎn)換發(fā)生在資源庫內(nèi)。
與以往的業(yè)務(wù)服務(wù)相比熊赖,當(dāng)前的編碼規(guī)范可能多造成了一次數(shù)據(jù)轉(zhuǎn)換来屠,但每種數(shù)據(jù)對(duì)象職責(zé)明確,數(shù)據(jù)流轉(zhuǎn)更加清晰震鹉。
//數(shù)據(jù)庫資源
import com.shrb.mobile.pay.card.repo.dao.CardDao;//數(shù)據(jù)庫訪問對(duì)象-銀行卡
import com.shrb.mobile.pay.card.repo.dao.po.CardPO;//數(shù)據(jù)庫持久化對(duì)象-銀行卡
import com.shrb.mobile.pay.card.repo.dao.po.CardTransferPO;//數(shù)據(jù)庫持久化對(duì)象-獎(jiǎng)池
import com.shrb.mobile.pay.card.repo.cache.CardCacheAccessObj;//分布式緩存訪問對(duì)象-銀行卡緩存訪問
import com.shrb.mobile.pay.card.repo.repository.CardRepository;//資源庫訪問對(duì)象-銀行卡資源庫
4. 工程實(shí)施
通過模塊將限界上下文進(jìn)行區(qū)分俱笛,可以通過com.公司名.歸屬團(tuán)隊(duì).業(yè)務(wù).上下文.*
這樣的包命名結(jié)構(gòu)進(jìn)行命名,這樣可以將一個(gè)上下文限定在一個(gè)包的內(nèi)部传趾。
import com.shrb.mobile.pay.card.*;//支付卡上下文
import com.shrb.mobile.pay.riskcontrol.*;//支付交易風(fēng)控上下文
import com.shrb.mobile.pay.route.*;//支付路由上下文
import com.shrb.mobile.pay.thirdparty.*;//外部三方支付上下文
對(duì)于模塊內(nèi)的組織結(jié)構(gòu)迎膜,一般情況下我們是按照領(lǐng)域?qū)ο蟆㈩I(lǐng)域服務(wù)浆兰、領(lǐng)域資源庫磕仅、防腐層等組織方式進(jìn)行定義,這樣也可以和DDD中各個(gè)概念對(duì)應(yīng)起來簸呈。
import com.shrb.mobile.pay.card.domain.valobj.*;//領(lǐng)域?qū)ο?值對(duì)象
import com.shrb.mobile.pay.card.domain.entity.*;//領(lǐng)域?qū)ο?實(shí)體
import com.shrb.mobile.pay.card.domain.aggregate.*;//領(lǐng)域?qū)ο?聚合根
import com.shrb.mobile.pay.card.service.*;//領(lǐng)域服務(wù)
import com.shrb.mobile.pay.card.repo.*;//領(lǐng)域資源庫
import com.shrb.mobile.pay.card.acl.*;//領(lǐng)域防腐層
5. 防腐層
上面介紹了DDD在具體實(shí)施過程中的4個(gè)主要步驟榕订,但是在有些情況下還需要引入防腐層,有以下幾種情況會(huì)考慮引入防腐層:
- 需要將外部上下文中的模型翻譯成本上下文理解的模型蝶棋。
- 不同上下文之間的團(tuán)隊(duì)協(xié)作關(guān)系卸亮,如果是供奉者關(guān)系,建議引入防腐層玩裙,避免外部上下文變化對(duì)本上下文的侵蝕兼贸。
- 該訪問本上下文使用廣泛,為了避免改動(dòng)影響范圍過大吃溅。
將領(lǐng)域行為封裝到領(lǐng)域?qū)ο笾腥艿瑢①Y源管理行為封裝到資源庫中,將外部上下文的交互行為封裝到防腐層中决侈。能夠發(fā)現(xiàn)領(lǐng)域服務(wù)本身所承載的職責(zé)也就更加清晰了螺垢,即就是通過串聯(lián)領(lǐng)域?qū)ο蟆①Y源庫和防腐層等一系列領(lǐng)域內(nèi)的對(duì)象的行為赖歌,對(duì)其他上下文提供交互的接口枉圃。
其實(shí)我的理解防腐層就是一個(gè)不同限界上下文之間的Adapter,主要做的工作就是數(shù)據(jù)轉(zhuǎn)換庐冯,將其他領(lǐng)域的實(shí)體轉(zhuǎn)換成當(dāng)前限界上下文可以處理的實(shí)體孽亲,在不同上下文中做了一層隔離,這樣當(dāng)其他限界上下文業(yè)務(wù)實(shí)體有改動(dòng)的時(shí)候展父,可以將對(duì)本限界上下文的影響降到最小返劲。
總結(jié)
這是寫的關(guān)于DDD的第二篇文章玲昧,這端時(shí)間主要看了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)和領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)實(shí)現(xiàn)兩本書,兩本書總體還是偏抽象理論篮绿,但是一旦理解了核心理念孵延,再結(jié)合目前自己非常熟悉的業(yè)務(wù)領(lǐng)域,那么對(duì)DDD會(huì)有更深入的理解亲配。