最近學(xué)習(xí)了領(lǐng)域模型炸卑,雖然對(duì)領(lǐng)域模型中的每個(gè)概念都有大致的一個(gè)了解亡蓉,但是總是在腦海里面模棱兩可,看了極客?歐老師的DDD模型講解理論太多瞳浦,導(dǎo)致自己總是摸不著門(mén)路担映,不知道從何下手,怎么打破平時(shí)我們習(xí)慣性的E-R圖建模思維叫潦,故在美團(tuán)DDD實(shí)踐的基礎(chǔ)上在嘗試一下自己模擬一下業(yè)務(wù)場(chǎng)景從頭到尾的搭建一個(gè)DDD領(lǐng)域模型的業(yè)務(wù)場(chǎng)景蝇完。美團(tuán)DDD實(shí)戰(zhàn)項(xiàng)目鏈接:https://tech.meituan.com/2017/12/22/ddd-in-practice.html
文章就不從領(lǐng)域模型的概念開(kāi)始說(shuō)起了,在下面實(shí)際的項(xiàng)目里面會(huì)加入自己對(duì)于各個(gè)領(lǐng)域模塊的理解诅挑,如果有什么不對(duì)的地方希望可以得到大家的糾正四敞。
開(kāi)場(chǎng)白:
至少30年以前泛源,一些軟件設(shè)計(jì)人員就已經(jīng)意識(shí)到領(lǐng)域建模和設(shè)計(jì)的重要性拔妥,并形成一種思潮,Eric Evans將其定義為領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain-Driven Design达箍,簡(jiǎn)稱DDD)没龙。在互聯(lián)網(wǎng)開(kāi)發(fā)“小步快跑,迭代試錯(cuò)”的大環(huán)境下缎玫,DDD似乎是一種比較“古老而緩慢”的思想硬纤。然而,由于互聯(lián)網(wǎng)公司也逐漸深入實(shí)體經(jīng)濟(jì)赃磨,業(yè)務(wù)日益復(fù)雜筝家,我們?cè)陂_(kāi)發(fā)中也越來(lái)越多地遇到傳統(tǒng)行業(yè)軟件開(kāi)發(fā)中所面臨的問(wèn)題。在《復(fù)雜軟件設(shè)計(jì)之道》里面作者說(shuō)到了技術(shù)負(fù)債一詞邻辉,我在一家千萬(wàn)級(jí)別的電商公司上班溪王,聽(tīng)到這個(gè)詞的時(shí)候立馬就產(chǎn)生了共鳴腮鞍,公司前期處于發(fā)展階段,開(kāi)發(fā)人員為了能將產(chǎn)品快速上市莹菱,導(dǎo)致各種業(yè)務(wù)雜亂無(wú)章移国,平時(shí)改一個(gè)很簡(jiǎn)單的功能點(diǎn),改了一個(gè)地方道伟,另外一個(gè)業(yè)務(wù)也用到了(老員工也不熟悉業(yè)務(wù)迹缀,代碼毫無(wú)關(guān)聯(lián)的地方),導(dǎo)致上線總是出現(xiàn)一些這沒(méi)生效蜜徽,那沒(méi)反應(yīng)的問(wèn)題祝懂。等等
場(chǎng)景需求:
? ??抽獎(jiǎng)平臺(tái)
?? ?1.抽獎(jiǎng)活動(dòng)有活動(dòng)限制,例如用戶的抽獎(jiǎng)次數(shù)限制拘鞋,抽獎(jiǎng)的開(kāi)始和結(jié)束的時(shí)間嫂易,抽獎(jiǎng)人來(lái)源的抽獎(jiǎng)機(jī)會(huì)類型(例如分享xx才能抽獎(jiǎng)),等掐禁;
?? ?2.一個(gè)抽獎(jiǎng)活動(dòng)包含多個(gè)獎(jiǎng)品怜械,可以針對(duì)一個(gè)或多個(gè)用戶群體,獎(jiǎng)品可以指定人群(標(biāo)簽傅事,會(huì)員級(jí)別)發(fā)放缕允;
?? ?3.獎(jiǎng)品有自身的獎(jiǎng)品配置,例如庫(kù)存量蹭越,被抽中的概率等障本,最多被一個(gè)用戶抽中的次數(shù)等等;
?? ?4.用戶群體有多種區(qū)別方式响鹃,如按照用戶所在城市區(qū)分驾霜,按照新老客區(qū)分,會(huì)員等級(jí)买置,活躍度等粪糙;
?? ?5.活動(dòng)具有風(fēng)控配置,能夠限制用戶參與抽獎(jiǎng)的頻率忿项。
大家先是思考下如果按照我們平時(shí)的開(kāi)發(fā)習(xí)慣肯定就是先把數(shù)據(jù)結(jié)構(gòu)建好(E-R),然之后開(kāi)始用curd進(jìn)行過(guò)程式代碼的編寫(xiě)蓉冈。
1、建立活動(dòng)表(t_activity),活動(dòng)規(guī)則表(a_activity_condition),活動(dòng)獎(jiǎng)品表(t_activity_prize)等
2轩触、生成對(duì)應(yīng)的mapp寞酿,dao等
3、建立一個(gè)lotteryService脱柱,里面存放了大量的mapper伐弹,各種if邏輯.
這種業(yè)務(wù)模型簡(jiǎn)單的時(shí)候還好,等業(yè)務(wù)復(fù)雜起來(lái)的話榨为,會(huì)出現(xiàn)大量的業(yè)務(wù)邏輯存放在service里面惨好,業(yè)務(wù)邊界不明確椅邓,可能其中的某個(gè)方法某個(gè)判斷也存放在其他的service里面,導(dǎo)致改某個(gè)片段的代碼昧狮,其他的service也影響到了景馁,或者說(shuō)改ServiceA中的某段代碼,ServiceB也影響到了逗鸣。
? ? 平時(shí)我們開(kāi)發(fā)維護(hù)的時(shí)候產(chǎn)品說(shuō)加一個(gè)xx需求合住,我們每次都需要想這樣子改動(dòng)會(huì)不會(huì)影響到其他地方,最后的出的結(jié)果就是撒璧,不確定會(huì)不會(huì)影響透葛,或者直接和產(chǎn)品說(shuō):做不了等。導(dǎo)致產(chǎn)品為了兼顧開(kāi)發(fā)的難度導(dǎo)致調(diào)整了需求卿樱,本來(lái)應(yīng)該是讓技術(shù)去適應(yīng)市場(chǎng)的僚害,現(xiàn)在卻倒轉(zhuǎn)過(guò)來(lái),導(dǎo)致做出來(lái)的東西不符合原本的想法繁调。
DDD領(lǐng)域模型的核心思想就是“高內(nèi)聚萨蚕,低耦合”,或者面向?qū)ο蟮脑O(shè)計(jì)說(shuō)的“”蹄胰,我們平時(shí)開(kāi)發(fā)總是將業(yè)務(wù)邏輯和底層的模塊(如mapper,redis的工具代碼岳遥,es的工具代碼)耦合起來(lái),例如上面中的獎(jiǎng)品裕寨,先出現(xiàn)redis,沒(méi)有數(shù)據(jù)就查詢mysql浩蓉,我們一般寫(xiě)成
這方法一般是放在上面的 ?lotteryService里面,各位想下這本身是一個(gè)抽獎(jiǎng)應(yīng)用層宾袜,獲取獎(jiǎng)品本身的邏輯為什么會(huì)出現(xiàn)在這里捻艳,或者說(shuō)抽獎(jiǎng)邏輯為什么需要知道從redis取還是mysql拿呢?如果改成es庆猫?或者是其他的nosql认轨,這樣是不是要去修改lotteryService這個(gè)類,但是這個(gè)本身就不屬于抽獎(jiǎng)邏輯的阅悍,為什么最后需要去修改這個(gè)類呢好渠?這就是面向?qū)ο笳f(shuō)的昨稼,封閉原則节视。職責(zé)單一性。
接下來(lái)開(kāi)始使用DDD領(lǐng)域模型去拆解這個(gè)抽獎(jiǎng)業(yè)務(wù)
?戰(zhàn)略設(shè)計(jì)
? ? ? ? ? ?戰(zhàn)略設(shè)計(jì)是根據(jù)用戶旅程分析假栓,找出領(lǐng)域?qū)ο蠛途酆细靶校瑢?duì)實(shí)體和值對(duì)象進(jìn)行聚類組成聚合,劃分限界上下文匾荆,建立領(lǐng)域模型的過(guò)程拌蜘。戰(zhàn)略設(shè)計(jì)采用的方法是事件風(fēng)暴杆烁,包括:產(chǎn)品愿景、場(chǎng)景分析简卧、領(lǐng)域建模和微服務(wù)拆分等幾個(gè)主要過(guò)程兔魂。戰(zhàn)略設(shè)計(jì)階段建議參與人員:領(lǐng)域?qū)<摇I(yè)務(wù)需求方举娩、產(chǎn)品經(jīng)理析校、架構(gòu)師、項(xiàng)目經(jīng)理铜涉、開(kāi)發(fā)經(jīng)理和測(cè)試經(jīng)理智玻。
? ? ? ? ? ? 是不是很難懂?別急下面我們一步一步來(lái)芙代。我們可以把抽獎(jiǎng)拆解成C端和M端吊奢,M端就是我們常說(shuō)的管理后臺(tái),設(shè)計(jì)抽獎(jiǎng)活動(dòng)的相關(guān)配置纹烹。接下來(lái)講解的是C端
根據(jù)需求描述我們不難想到核心子域是抽獎(jiǎng)子域页滚。我們想下抽獎(jiǎng)的邏輯,可以分為1)抽獎(jiǎng)前判斷用戶是否滿足抽獎(jiǎng)條件铺呵;2)獲取獎(jiǎng)品池中的獎(jiǎng)品逻谦,根據(jù)概率抽獎(jiǎng),得出中獎(jiǎng)的獎(jiǎng)品? 3)得到的獎(jiǎng)品進(jìn)行判斷用戶是否瞞住得到獎(jiǎng)品的資格陪蜻;4)根據(jù)類型發(fā)放獎(jiǎng)品邦马。
可以得出,這個(gè)業(yè)務(wù)可以分成抽獎(jiǎng)前宴卖,抽獎(jiǎng)后兩個(gè)領(lǐng)域滋将。
2、戰(zhàn)術(shù)設(shè)計(jì)
針對(duì)抽獎(jiǎng)的子域部分症昏,我們可以得出如下的集合根和實(shí)體
UserInfoFacade:防腐層的含義可以理解為適配器随闽,由于UserInfo這個(gè)信息是在另外的微服務(wù)里面,我們需要通過(guò)http去獲取相對(duì)于的信息肝谭,所有需要使用一個(gè)適配器去請(qǐng)求掘宪,理解為微服務(wù)里面我們服務(wù)間的接口調(diào)用。
同理我們對(duì)于發(fā)放獎(jiǎng)品領(lǐng)域服務(wù)可以得出如下的建模
上面就是領(lǐng)域模型的建模過(guò)程了攘烛,現(xiàn)在把剛剛的業(yè)務(wù)整理到對(duì)應(yīng)的模塊里面魏滚,接下來(lái)直接上偽代碼了。
DDD工程實(shí)現(xiàn)
在對(duì)上下文進(jìn)行細(xì)化后坟漱,我們開(kāi)始在工程中真正落地DDD鼠次。
模塊
模塊(Module)是DDD中明確提到的一種控制限界上下文的手段,在我們的工程中,一般盡量用一個(gè)模塊來(lái)表示一個(gè)領(lǐng)域的限界上下文腥寇。
如代碼中所示成翩,一般的工程中包的組織方式為{com.公司名.組織架構(gòu).業(yè)務(wù).上下文.*},這樣的組織結(jié)構(gòu)能夠明確的將一個(gè)上下文限定在包的內(nèi)部赦役。
代碼演示1? 抽獎(jiǎng)模塊
代碼演示1:LotteryCondition(活動(dòng)限制聚合根)
? ? ? 從中可以看到抽獎(jiǎng)限制聚合根的主要功能就是整合幾個(gè)實(shí)體或健對(duì)象對(duì)用戶的活動(dòng)權(quán)限進(jìn)行控制麻敌。這一步是純業(yè)務(wù)性的,沒(méi)有涉及到db掂摔,緩存庸论,等的操作,因?yàn)榛A(chǔ)資源性的東西不應(yīng)該耦合在業(yè)務(wù)里面棒呛。
代碼模塊2:UserInfoFacde (用戶信息防腐層)
這一步可以理解為就是使用適配的方式調(diào)用第三方服務(wù)(微服務(wù))聂示。因?yàn)榈谌降姆?wù)的參數(shù)總是和我們需要使用到的時(shí)候是不一樣的,我們需要使用適配器的思維調(diào)用簇秒。
代碼模塊3:ActivityCondition
重點(diǎn)是checkActivityCondition()方法鱼喉,做邏輯處理
代碼模塊4?:LotteryChance
發(fā)放獎(jiǎng)品聚合根就不上代碼了,關(guān)于資源庫(kù)如何引入趋观,我們我們可以看下Prize代碼
將資源庫(kù)的部分從原來(lái)的service中抽取出來(lái)扛禽,和業(yè)務(wù)分離開(kāi)來(lái),這是很有必要的皱坛,業(yè)務(wù)本身就不用管你的數(shù)據(jù)怎么樣來(lái)编曼,只是負(fù)責(zé)業(yè)務(wù)的邏輯。包括現(xiàn)在很流行的六邊形架構(gòu)剩辟,整潔架構(gòu)等都是這個(gè)思想掐场。把業(yè)務(wù)和底層分離,這個(gè)底層可以是框架本身贩猎,數(shù)據(jù)庫(kù)等熊户。
我之前公司由于需要把mysql讀取的換成redis或者是本地緩存。如果把mapper代碼放到service里面的話吭服,這改動(dòng)起來(lái)是非常巨大的一個(gè)工作嚷堡,還會(huì)導(dǎo)致邏輯代碼的改動(dòng)。如果使用DDD的話是不是更加的符合面向?qū)ο蟮拈_(kāi)閉原則呢艇棕?
最后上一下整個(gè)抽獎(jiǎng)服務(wù)的代碼
把具體的邏輯封裝到獨(dú)立的領(lǐng)域里面蝌戒,這樣看起來(lái)不是比各種狀態(tài)散落在service里面舒服的多?服務(wù)里面只有領(lǐng)域?qū)ο笳恿穑蛘呤蔷酆系缺惫丁<词挂膭?dòng)數(shù)據(jù)的存儲(chǔ)也不用改動(dòng)到業(yè)務(wù)邏輯。