專業(yè)解讀:DDD充血模型優(yōu)秀實(shí)踐
大家好,我是Jensen,見(jiàn)字如面。
想來(lái)已有五個(gè)月沒(méi)更新,每天被公司各種項(xiàng)目硬控住,憑著真實(shí)踐才有干貨的原則,年前再憋點(diǎn)干貨給大家。
最近半年我都在做共享租賃業(yè)務(wù),比如美團(tuán)共享充電寶(越南市場(chǎng))、共享洗衣機(jī)/烘干機(jī)(越南)、共享?yè)Q電柜(國(guó)內(nèi))、共享凈水器(國(guó)內(nèi)&國(guó)際)等等,圍繞著“租賃”業(yè)務(wù)去開(kāi)拓國(guó)內(nèi)與國(guó)際SaaS市場(chǎng),其中就大量用到充血模型來(lái)優(yōu)化代碼架構(gòu),也是迄今為止我在公司搭建十多個(gè)DDD工程最滿意的“作品”。
所以今天給大家分享的主題是充血模型,不敢私藏。
一、什么是充血模型
充血模型是一種面向?qū)ο蟮能浖O(shè)計(jì)方法,屬于領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)的核心概念之一。它強(qiáng)調(diào)將業(yè)務(wù)邏輯和行為封裝在領(lǐng)域?qū)ο髢?nèi)部,使對(duì)象不僅包含數(shù)據(jù),還包含與數(shù)據(jù)相關(guān)的操作和業(yè)務(wù)規(guī)則。
核心特點(diǎn)
- 封裝數(shù)據(jù)與行為:
- 在充血模型中,領(lǐng)域?qū)ο螅ㄈ鐚?shí)體或值對(duì)象)不僅包含數(shù)據(jù)屬性,還包含與這些數(shù)據(jù)相關(guān)的業(yè)務(wù)邏輯和行為。例如,一個(gè)訂單對(duì)象不僅包含訂單號(hào)、客戶ID等屬性,還包含計(jì)算總價(jià)、檢查庫(kù)存等方法。
- 這種設(shè)計(jì)更符合面向?qū)ο缶幊痰暮诵脑瓌t,如封裝和單一職責(zé)。
- 高內(nèi)聚、低耦合:
- 由于業(yè)務(wù)邏輯被封裝在領(lǐng)域?qū)ο髢?nèi)部,對(duì)象之間的耦合度降低,系統(tǒng)的可維護(hù)性和可擴(kuò)展性增強(qiáng)。
- 領(lǐng)域邏輯的自主性:
- 領(lǐng)域?qū)ο竽軌蜃灾鞴芾碜约旱臓顟B(tài)和行為,減少了對(duì)外部服務(wù)層的依賴。
應(yīng)用場(chǎng)景
充血模型適用于業(yè)務(wù)邏輯復(fù)雜且需要高度封裝的系統(tǒng)。例如,在電商系統(tǒng)中,訂單對(duì)象可以包含計(jì)算總價(jià)、檢查庫(kù)存等方法,而不是將這些邏輯放在外部的服務(wù)層。
與貧血模型的對(duì)比
- 貧血模型:
a.貧血模型是一種將數(shù)據(jù)和業(yè)務(wù)邏輯分離的設(shè)計(jì)模式。領(lǐng)域?qū)ο笾话瑪?shù)據(jù)屬性,而業(yè)務(wù)邏輯則放在服務(wù)層中。
b.這種設(shè)計(jì)簡(jiǎn)單易懂,但在業(yè)務(wù)邏輯復(fù)雜時(shí),服務(wù)層可能會(huì)變得過(guò)于龐大,難以維護(hù)。
- 充血模型:
a.充血模型將數(shù)據(jù)和業(yè)務(wù)邏輯封裝在同一對(duì)象中,更符合面向?qū)ο蟮脑O(shè)計(jì)原則。
b.它能夠更好地利用面向?qū)ο蟮姆庋b特性,使代碼更易于擴(kuò)展和維護(hù)。
相信有不少老鐵都接觸過(guò)貧血模型的工程,在Service類寫(xiě)了幾千上萬(wàn)行代碼,看個(gè)邏輯特費(fèi)勁,改個(gè)小需求都要梳理很久才敢動(dòng)里面的核心代碼,這對(duì)有代碼潔癖的程序猿太不友好了。
二、計(jì)費(fèi)租賃領(lǐng)域建模
以實(shí)際項(xiàng)目出發(fā),首先DDD領(lǐng)域建模是常規(guī)操作了,梳理好要怎么做這個(gè)租賃業(yè)務(wù),代碼才寫(xiě)得更快些:
在計(jì)費(fèi)租賃這個(gè)聚合里,核心業(yè)務(wù)就是:
- 后臺(tái)建計(jì)費(fèi)模板
- 投放設(shè)備:選設(shè)備、點(diǎn)位(門(mén)店)、計(jì)費(fèi)模板,把設(shè)備投放到某個(gè)點(diǎn)位
- C端下計(jì)費(fèi)訂單,先付后用模式要先走支付流程,充電寶/換電柜要先下押金單并支付(這種有子設(shè)備的情況,交了押金才能往下走)
- 使用設(shè)備,發(fā)指令給終端硬件啟動(dòng),比如充電寶要下發(fā)彈寶指令,凈水器要下發(fā)開(kāi)水指令,換電柜要下發(fā)開(kāi)倉(cāng)指令等等
- 使用設(shè)備結(jié)束,計(jì)費(fèi)訂單結(jié)算扣費(fèi)
大致流程如此,建模完事后發(fā)現(xiàn)誒,不難,可能麻煩點(diǎn)在于第四點(diǎn)——怎么讓一個(gè)下單流程支持不同的策略,發(fā)不同指令,這個(gè)稍后也會(huì)說(shuō)明。
接下來(lái)要用DDD落地了。
三、共享租賃DDD工程落地
先上代碼結(jié)構(gòu):
還是這套熟悉的經(jīng)典四層DDD架構(gòu),百用不爽,感興趣可以回看我之前寫(xiě)的DDD四層微服務(wù)架構(gòu),有變動(dòng)的是application.factory包,換成了listener包,放事件監(jiān)聽(tīng)器,主要用于解耦。
其中參與計(jì)費(fèi)租賃業(yè)務(wù)的就只有核心的這些類:
回到正題,什么情況下要用到充血模型?
先看最核心的計(jì)費(fèi)訂單應(yīng)用服務(wù):
正常來(lái)說(shuō)整個(gè)計(jì)費(fèi)業(yè)務(wù)肯定不止上面620行代碼就能寫(xiě)完的,但是這里我用了充血模型,把很多業(yè)務(wù)邏輯抽離出去了,抽出去的原則很簡(jiǎn)單:1.原子性的 2.可復(fù)用的。我們既可以抽象成靜態(tài)方法,也可以抽象為領(lǐng)域模型的成員方法。
比如計(jì)費(fèi)訂單的充血方法:
但像下單、支付回調(diào)、結(jié)單、定時(shí)任務(wù)這些邏輯,抽象為充血模型的方法就不太適合了,還是以貧血模型的思路去做,在應(yīng)用服務(wù)里做。
有細(xì)心的朋友就會(huì)問(wèn)了:在Spring工程下,方法內(nèi)需要依賴其他Bean怎么辦???模型的成員變量總不能@Autowired好幾個(gè)倉(cāng)庫(kù)或Mapper吧,不合適。
這位朋友問(wèn)得非常好,我以前也有這個(gè)困惑,總覺(jué)得充血方法不能做CURD,只能寫(xiě)一些簡(jiǎn)單邏輯,但自從我把D3Boot基礎(chǔ)框架搭好以后,這個(gè)問(wèn)題早已經(jīng)成為過(guò)去式了,看看我這里是如何寫(xiě)的:
看吧, 根本不需要@Autowired別的Bean,查詢就是一行代碼的事,查不到還能直接拋錯(cuò)返回友好提示給前端了,save也是一行代碼搞定,update封裝后也可以u(píng)pdateById,或支持update+where條件,當(dāng)然,我們也可以在方法內(nèi)部發(fā)Spring封裝的領(lǐng)域事件到另一個(gè)地方做處理。
需要這套DDD基礎(chǔ)框架的在公眾號(hào)后臺(tái)回復(fù)d3boot免費(fèi)領(lǐng)取哈。
四、領(lǐng)域事件解耦
回到上面說(shuō)的listener目錄,它作為領(lǐng)域事件或外部MQ事件的入口,做著解耦的事,在這個(gè)計(jì)費(fèi)租賃里也發(fā)揮了很大的作用。
試想一下,要對(duì)接不同的產(chǎn)品計(jì)費(fèi)下單,傳統(tǒng)的方式,要么拆開(kāi)不同的下單方法前端調(diào)不同的接口,要么在下單方法里很N多個(gè)ifelse去判斷,高級(jí)點(diǎn)的就再抽象個(gè)策略模式去處理。
NoNoNo,太麻煩了,在DDD中,領(lǐng)域事件就是干這個(gè)事的,計(jì)費(fèi)訂單下單只做它領(lǐng)域范圍內(nèi)的事情,發(fā)什么指令去給設(shè)備,不應(yīng)該讓它操心,于是領(lǐng)域事件就派上用場(chǎng)了:
比如在提交訂單后,有不同的處理邏輯,那么發(fā)個(gè)事件吧,不同的租戶自己去監(jiān)聽(tīng),做不同的事情,還能讓監(jiān)聽(tīng)者自己決定是同步做還是異步做:
凈水器的業(yè)務(wù),客戶下完單,凈水器監(jiān)聽(tīng)器去發(fā)個(gè)指令給設(shè)備,就能去打水啦,這里我支持了所有凈水器的廠商,都是同一套邏輯:
咳咳~如有雷同,純屬雷同。
五、寫(xiě)在最后
充血模型也并不是萬(wàn)能的,因?yàn)槲疫@里業(yè)務(wù)相對(duì)簡(jiǎn)單,我沒(méi)考慮加事務(wù)的情況,有些模型充血方法如果是靜態(tài)方法要考慮事務(wù)的話,還得加一些顯式的事務(wù)代碼,這個(gè)在做的過(guò)程中遇到問(wèn)題再優(yōu)化了,軟件工程領(lǐng)域,迭代思維很重要,小步快跑見(jiàn)效果,就是最好的架構(gòu)演進(jìn)。