新來(lái)個(gè)技術(shù)總監(jiān)例诀,把DDD落地的那叫一個(gè)高級(jí),服氣

不 BB裁着,直接上思維導(dǎo)圖繁涂!

image.png

1. 走進(jìn) DDD

1.1 為什么要用 DDD ?

  • 面向?qū)ο笤O(shè)計(jì)二驰,數(shù)據(jù)行為綁定扔罪,告別貧血模型;
  • 降低復(fù)雜度桶雀,分而治之矿酵;
  • 優(yōu)先考慮領(lǐng)域模型,而不是切割數(shù)據(jù)和行為矗积;
  • 準(zhǔn)確傳達(dá)業(yè)務(wù)規(guī)則全肮,業(yè)務(wù)優(yōu)先;
  • 代碼即設(shè)計(jì)棘捣;
  • 它通過(guò)邊界劃分將復(fù)雜業(yè)務(wù)領(lǐng)域簡(jiǎn)單化辜腺,幫我們?cè)O(shè)計(jì)出清晰的領(lǐng)域和應(yīng)用邊界,可以很容易地實(shí)現(xiàn)業(yè)務(wù)和技術(shù)統(tǒng)一的架構(gòu)演進(jìn)乍恐;
  • 領(lǐng)域知識(shí)共享评疗,提升協(xié)助效率;
  • 增加可維護(hù)性和可讀性禁熏,延長(zhǎng)軟件生命周期壤巷;
  • 中臺(tái)化的基石。
image.png

1.2 DDD 作用

說(shuō)到 DDD瞧毙,繞不開(kāi) MVC胧华,在 MVC 三層架構(gòu)中寄症,我們進(jìn)行功能開(kāi)發(fā)之前,拿到需求矩动,解讀需求有巧。往往最先做的一步就是先設(shè)計(jì)表結(jié)構(gòu),再逐層設(shè)計(jì)上層 dao悲没,service篮迎,controller。對(duì)于產(chǎn)品或者用戶的需求都做了一層自我理解的轉(zhuǎn)化示姿。

image.png

用戶需求在被提出之后經(jīng)過(guò)這么多層的轉(zhuǎn)化后甜橱,特別是研發(fā)需求在數(shù)據(jù)庫(kù)結(jié)構(gòu)這一層轉(zhuǎn)化后,將業(yè)務(wù)以主觀臆斷行為進(jìn)行了轉(zhuǎn)化栈戳。一旦業(yè)務(wù)邊界劃分模糊岂傲,考慮不全,大量的邏輯補(bǔ)充堆積到了代碼層面子檀,變得越來(lái)越難維護(hù)镊掖。

假如我們現(xiàn)在要做一個(gè)電商訂單下單的需求,涉及到用戶選定商品褂痰,下訂單亩进、支付訂單、對(duì)用戶下單時(shí)的訂單發(fā)貨:

  • MVC 架構(gòu):我們常見(jiàn)的做法是在分析好業(yè)務(wù)需求之后缩歪,就開(kāi)始設(shè)計(jì)表結(jié)構(gòu)了归薛,訂單表,支付表匪蝙,商品表等等苟翻。然后編寫業(yè)務(wù)邏輯。這是第一個(gè)版本的需求骗污,功能迭代了崇猫,訂單支付后我可以取消,下單的商品我們退換貨需忿,是不是又需要進(jìn)行加表诅炉,緊跟著對(duì)于的實(shí)現(xiàn)邏輯也進(jìn)行修改。功能不斷迭代屋厘,代碼就不斷地層層往上疊涕烧。
  • DDD 架構(gòu):我們先進(jìn)行劃分業(yè)務(wù)邊界。這里面核心是訂單汗洒。那么訂單就是這個(gè)業(yè)務(wù)領(lǐng)域里面的聚合邏輯的體現(xiàn)议纯。支付,商品信息溢谤,地址等等都是圍繞著訂單實(shí)體瞻凤。訂單本身的屬性決定之后憨攒,類似于地址只是一個(gè)屬性的體現(xiàn)。當(dāng)你將訂單的領(lǐng)域模型構(gòu)建好之后阀参,后續(xù)的邏輯邊界與倉(cāng)儲(chǔ)設(shè)計(jì)也就隨之而來(lái)了肝集。

DDD 整體作用總結(jié)如下:

  • 消除信息不對(duì)稱;
  • 常規(guī)MVC三層架構(gòu)中自底向上的設(shè)計(jì)方式做一個(gè)反轉(zhuǎn)蛛壳,以業(yè)務(wù)為主導(dǎo)杏瞻,自頂向下的進(jìn)行業(yè)務(wù)領(lǐng)域劃分;
  • 將大的業(yè)務(wù)需求進(jìn)行拆分衙荐,分而治之捞挥。

2. DDD 架構(gòu)

2.1 DDD 分層架構(gòu)

嚴(yán)格分層架構(gòu):某層只能與直接位于的下層發(fā)生耦合。

松散分層架構(gòu):允許上層與任意下層發(fā)生耦合忧吟。

在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)中采用的是松散分層架構(gòu)树肃,層間關(guān)系不那么嚴(yán)格。每層都可能使用它下面所有層的服務(wù)瀑罗,而不僅僅是下一層的服務(wù)。每層都可能是半透明的雏掠,這意味著有些服務(wù)只對(duì)上一層可見(jiàn)斩祭,而有些服務(wù)對(duì)上面的所有層都可見(jiàn)。

image.png

分層的作用乡话,從上往下:

  • 用戶交互層:web 請(qǐng)求摧玫,rpc 請(qǐng)求,mq 消息等外部輸入均被視為外部輸入的請(qǐng)求绑青,可能修改到內(nèi)部的業(yè)務(wù)數(shù)據(jù)诬像。
  • 業(yè)務(wù)應(yīng)用層:與 MVC 中的 service 不同的不是,service 中存儲(chǔ)著大量業(yè)務(wù)邏輯闸婴。但在應(yīng)用服務(wù)的實(shí)現(xiàn)中坏挠,它負(fù)責(zé)編排、轉(zhuǎn)發(fā)邪乍、校驗(yàn)等降狠。
  • 領(lǐng)域?qū)?/strong>:或稱為模型層,系統(tǒng)的核心庇楞,負(fù)責(zé)表達(dá)業(yè)務(wù)概念榜配,業(yè)務(wù)狀態(tài)信息以及業(yè)務(wù)規(guī)則。即包含了該領(lǐng)域所有復(fù)雜的業(yè)務(wù)知識(shí)抽象和規(guī)則定義吕晌。該層主要精力要放在領(lǐng)域?qū)ο蠓治錾系叭欤梢詮膶?shí)體,對(duì)象睛驳,聚合(聚合根)烙心,領(lǐng)域服務(wù)膜廊,領(lǐng)域事件,倉(cāng)儲(chǔ)弃理,工廠等方面入手溃论。
  • 基礎(chǔ)設(shè)施層:主要有 2 方面內(nèi)容,一是為領(lǐng)域模型提供持久化機(jī)制痘昌,當(dāng)軟件需要持久化能力的時(shí)候才需要進(jìn)行規(guī)劃钥勋;一是對(duì)其他層面提供通用的技術(shù)支持能力,如消息通信辆苔,通用工具算灸,配置等的實(shí)現(xiàn)。

在設(shè)計(jì)和開(kāi)發(fā)時(shí)驻啤,不要將本該放在領(lǐng)域?qū)拥臉I(yè)務(wù)邏輯放到應(yīng)用層中實(shí)現(xiàn)菲驴,因?yàn)辇嫶蟮膽?yīng)用層會(huì)使領(lǐng)域模型失焦,時(shí)間一長(zhǎng)你的服務(wù)就會(huì)演化為傳統(tǒng)的三層架構(gòu)骑冗,業(yè)務(wù)邏輯會(huì)變得混亂赊瞬。

2.2 各層數(shù)據(jù)轉(zhuǎn)換

每一層都有自己特定的數(shù)據(jù),可以做如下區(qū)分:

  • VO(View Object):視圖對(duì)象贼涩,主要對(duì)應(yīng)界面顯示的數(shù)據(jù)對(duì)象巧涧。對(duì)于一個(gè)WEB頁(yè)面,或者SWT遥倦、SWING的一個(gè)界面谤绳,用一個(gè)VO對(duì)象對(duì)應(yīng)整個(gè)界面的值。
  • DTO(Data Transfer Object):數(shù)據(jù)傳輸對(duì)象袒哥,主要用于遠(yuǎn)程調(diào)用等需要大量傳輸對(duì)象的地方缩筛。比如我們一張表上有 100 這這個(gè)字段,那么對(duì)應(yīng)的 PO 就有 100 個(gè)屬性堡称。但是我們界面上只要顯示 10 個(gè)字段瞎抛,客戶端用 WEB service 來(lái)獲取數(shù)據(jù),沒(méi)有必要把整個(gè) PO 對(duì)象傳遞到客戶端却紧,這時(shí)我們就可以用只有這 10 個(gè)屬性的 DTO 來(lái)傳遞結(jié)果到客戶端婿失,這樣也不會(huì)暴露服務(wù)端表結(jié)構(gòu)。到達(dá)客戶端以后啄寡,如果用這個(gè)對(duì)象來(lái)對(duì)應(yīng)界面顯示豪硅,那此時(shí)它的身份就轉(zhuǎn)為 VO。在這里挺物,我泛指用于展示層與服務(wù)層之間的數(shù)據(jù)傳輸對(duì)象懒浮。
  • DO(Domain Object):領(lǐng)域?qū)ο螅褪菑默F(xiàn)實(shí)世界中抽象出來(lái)的有形或無(wú)形的業(yè)務(wù)實(shí)體。
  • PO(Persistent Object):持久化對(duì)象砚著,它跟持久層(通常是關(guān)系型數(shù)據(jù)庫(kù))的數(shù)據(jù)結(jié)構(gòu)形成一一對(duì)應(yīng)的映射關(guān)系次伶,如果持久層是關(guān)系型數(shù)據(jù)庫(kù),那么稽穆,數(shù)據(jù)表中的每個(gè)字段(或若干個(gè))就對(duì)應(yīng) PO 的一個(gè)(或若干個(gè))屬性冠王。形象的理解就是一個(gè) PO 就是數(shù)據(jù)庫(kù)中的一條記錄,好處是可以把一條記錄作為一個(gè)對(duì)象處理舌镶,可以方便地轉(zhuǎn)為其它對(duì)象柱彻。

3. DDD 基礎(chǔ)

image.png

學(xué)習(xí) DDD 前,有很多基礎(chǔ)概念需要掌握餐胀,這幅圖總結(jié)得很全哟楷,他把 DDD 劃分不同的層級(jí):

  • 最里層是值、屬性否灾、唯一標(biāo)識(shí)等卖擅,這個(gè)是最基本的數(shù)據(jù)單位,但不能直接使用墨技。
  • 然后是實(shí)體惩阶,這個(gè)是把基礎(chǔ)的數(shù)據(jù)進(jìn)行封裝,可以直接使用扣汪,在代碼中就是封裝好的一個(gè)個(gè)實(shí)體對(duì)象断楷。
  • 之后就是領(lǐng)域?qū)樱凑諛I(yè)務(wù)劃分為不同的領(lǐng)域私痹,比如訂單領(lǐng)域、商品領(lǐng)域统刮、支付領(lǐng)域等紊遵。
  • 最后是應(yīng)用服務(wù),它對(duì)業(yè)務(wù)邏輯進(jìn)行編排侥蒙,也可以理解為業(yè)務(wù)層暗膜。

3.1 領(lǐng)域和子域

在研究和解決業(yè)務(wù)問(wèn)題時(shí),DDD 會(huì)按照一定的規(guī)則將業(yè)務(wù)領(lǐng)域進(jìn)行細(xì)分鞭衩,當(dāng)領(lǐng)域細(xì)分到一定的程度后学搜,DDD 會(huì)將問(wèn)題范圍限定在特定的邊界內(nèi),在這個(gè)邊界內(nèi)建立領(lǐng)域模型论衍,進(jìn)而用代碼實(shí)現(xiàn)該領(lǐng)域模型瑞佩,解決相應(yīng)的業(yè)務(wù)問(wèn)題。簡(jiǎn)言之坯台,DDD 的領(lǐng)域就是這個(gè)邊界內(nèi)要解決的業(yè)務(wù)問(wèn)題域炬丸。

領(lǐng)域可以進(jìn)一步劃分為子領(lǐng)域。我們把劃分出來(lái)的多個(gè)子領(lǐng)域稱為子域,每個(gè)子域?qū)?yīng)一個(gè)更小的問(wèn)題域或更小的業(yè)務(wù)范圍稠炬。

領(lǐng)域的核心思想就是將問(wèn)題域逐級(jí)細(xì)分焕阿,來(lái)降低業(yè)務(wù)理解和系統(tǒng)實(shí)現(xiàn)的復(fù)雜度。通過(guò)領(lǐng)域細(xì)分首启,逐步縮小服務(wù)需要解決的問(wèn)題域暮屡,構(gòu)建合適的領(lǐng)域模型。

舉個(gè)簡(jiǎn)單的例子毅桃,對(duì)于保險(xiǎn)領(lǐng)域褒纲,我們可以把保險(xiǎn)細(xì)分為承保、收付疾嗅、再保以及理賠等子域外厂,而承保子域還可以繼續(xù)細(xì)分為投保、保全(壽險(xiǎn))代承、批改(財(cái)險(xiǎn))等子子域汁蝶。

3.2 核心域、通用域和支撐域

子域可以根據(jù)重要程度和功能屬性劃分為如下:

  • 核心域:決定產(chǎn)品和公司核心競(jìng)爭(zhēng)力的子域论悴,它是業(yè)務(wù)成功的主要因素和公司的核心競(jìng)爭(zhēng)力掖棉。
  • 通用域:沒(méi)有太多個(gè)性化的訴求,同時(shí)被多個(gè)子域使用的通用功能的子域膀估。
  • 支撐域:但既不包含決定產(chǎn)品和公司核心競(jìng)爭(zhēng)力的功能幔亥,也不包含通用功能的子域。

核心域察纯、支撐域和通用域的主要目標(biāo):通過(guò)領(lǐng)域劃分帕棉,區(qū)分不同子域在公司內(nèi)的不同功能屬性和重要性,從而公司可對(duì)不同子域采取不同的資源投入和建設(shè)策略饼记,其關(guān)注度也會(huì)不一樣香伴。

很多公司的業(yè)務(wù),表面看上去相似具则,但商業(yè)模式和戰(zhàn)略方向是存在很大差異的即纲,因此公司的關(guān)注點(diǎn)會(huì)不一樣,在劃分核心域博肋、通用域和支撐域時(shí)低斋,其結(jié)果也會(huì)出現(xiàn)非常大的差異。

比如同樣都是電商平臺(tái)的淘寶匪凡、天貓膊畴、京東和蘇寧易購(gòu),他們的商業(yè)模式是不同的病游。淘寶是 C2C 網(wǎng)站巴比,個(gè)人賣家對(duì)個(gè)人買家,而天貓、京東和蘇寧易購(gòu)則是 B2C 網(wǎng)站轻绞,是公司賣家對(duì)個(gè)人買家采记。即便是蘇寧易購(gòu)與京東都是 B2C 的模式,蘇寧易購(gòu)是典型的傳統(tǒng)線下賣場(chǎng)轉(zhuǎn)型成為電商政勃,京東則是直營(yíng)加部分平臺(tái)模式唧龄。

因此,在公司建立領(lǐng)域模型時(shí)奸远,我們就要結(jié)合公司戰(zhàn)略重點(diǎn)和商業(yè)模式既棺,重點(diǎn)關(guān)注核心域。

3.3 通用語(yǔ)言和限界上下文

  • 通用語(yǔ)言:就是能夠簡(jiǎn)單懒叛、清晰丸冕、準(zhǔn)確描述業(yè)務(wù)涵義和規(guī)則的語(yǔ)言。
  • 限界上下文:用來(lái)封裝通用語(yǔ)言和領(lǐng)域?qū)ο笱峁┥舷挛沫h(huán)境胖烛,保證在領(lǐng)域之內(nèi)的一些術(shù)語(yǔ)、業(yè)務(wù)相關(guān)對(duì)象等(通用語(yǔ)言)有一個(gè)確切的含義诅迷,沒(méi)有二義性佩番。

3.3.1 通用語(yǔ)言

通用語(yǔ)言是團(tuán)隊(duì)統(tǒng)一的語(yǔ)言,不管你在團(tuán)隊(duì)中承擔(dān)什么角色罢杉,在同一個(gè)領(lǐng)域的軟件生命周期里都使用統(tǒng)一的語(yǔ)言進(jìn)行交流趟畏。那么,通用語(yǔ)言的價(jià)值也就很明了滩租,它可以解決交流障礙這個(gè)問(wèn)題赋秀,使領(lǐng)域?qū)<液烷_(kāi)發(fā)人員能夠協(xié)同合作,從而確保業(yè)務(wù)需求的正確表達(dá)律想。

這個(gè)通用語(yǔ)言到場(chǎng)景落地猎莲,大家可能還很模糊,其實(shí)就是把領(lǐng)域?qū)ο笾┯傩砸婷肌⒋a模型對(duì)象等晌柬,通過(guò)代碼和文字建立映射關(guān)系姥份,可以通過(guò) Excel 記錄這個(gè)關(guān)系,這樣研發(fā)可以通過(guò)代碼知道這個(gè)含義年碘,產(chǎn)品或者業(yè)務(wù)方可以通過(guò)文字知道這個(gè)含義澈歉,溝通起來(lái)就不會(huì)有歧義,說(shuō)的簡(jiǎn)單一點(diǎn)屿衅,其實(shí)就是統(tǒng)一產(chǎn)品和研發(fā)的話術(shù)埃难。

直接看下面這幅圖(來(lái)源于極客時(shí)間歐創(chuàng)新的 DDD 實(shí)戰(zhàn)課):

image.png

3.3.2 限界上下文

通用語(yǔ)言也有它的上下文環(huán)境,為了避免同樣的概念或語(yǔ)義在不同的上下文環(huán)境中產(chǎn)生歧義,DDD 在戰(zhàn)略設(shè)計(jì)上提出了“限界上下文”這個(gè)概念涡尘,用來(lái)確定語(yǔ)義所在的領(lǐng)域邊界忍弛。

限界上下文是一個(gè)顯式的語(yǔ)義和語(yǔ)境上的邊界,領(lǐng)域模型便存在于邊界之內(nèi)考抄。邊界內(nèi)细疚,通用語(yǔ)言中的所有術(shù)語(yǔ)和詞組都有特定的含義。把限界上下文拆解開(kāi)看川梅,限界就是領(lǐng)域的邊界疯兼,而上下文則是語(yǔ)義環(huán)境。

通過(guò)領(lǐng)域的限界上下文贫途,我們就可以在統(tǒng)一的領(lǐng)域邊界內(nèi)用統(tǒng)一的語(yǔ)言進(jìn)行交流吧彪。

3.4 實(shí)體和值對(duì)象

3.4.1 實(shí)體

實(shí)體 = 唯一身份標(biāo)識(shí) + 可變性【狀態(tài) + 行為】

DDD 中要求實(shí)體是唯一的且可持續(xù)變化的。意思是說(shuō)在實(shí)體的生命周期內(nèi)丢早,無(wú)論其如何變化姨裸,其仍舊是同一個(gè)實(shí)體。唯一性由唯一的身份標(biāo)識(shí)來(lái)決定的香拉±惭铮可變性也正反映了實(shí)體本身的狀態(tài)和行為。

實(shí)體以 DO(領(lǐng)域?qū)ο螅┑男问酱嬖?/strong>凫碌,每個(gè)實(shí)體對(duì)象都有唯一的 ID扑毡。我們可以對(duì)一個(gè)實(shí)體對(duì)象進(jìn)行多次修改,修改后的數(shù)據(jù)和原來(lái)的數(shù)據(jù)可能會(huì)大不相同盛险。

但是瞄摊,由于它們擁有相同的 ID,它們依然是同一個(gè)實(shí)體苦掘。比如商品是商品上下文的一個(gè)實(shí)體换帜,通過(guò)唯一的商品 ID 來(lái)標(biāo)識(shí),不管這個(gè)商品的數(shù)據(jù)如何變化鹤啡,商品的 ID 一直保持不變惯驼,它始終是同一個(gè)商品。

3.4.2 值對(duì)象

值對(duì)象 = 將一個(gè)值用對(duì)象的方式進(jìn)行表述递瑰,來(lái)表達(dá)一個(gè)具體的固定不變的概念祟牲。

當(dāng)你只關(guān)心某個(gè)對(duì)象的屬性時(shí),該對(duì)象便可作為一個(gè)值對(duì)象抖部。我們需要將值對(duì)象看成不變對(duì)象说贝,不要給它任何身份標(biāo)識(shí),還應(yīng)該盡量避免像實(shí)體對(duì)象一樣的復(fù)雜性慎颗。

還是舉個(gè)訂單的例子乡恕,訂單是一個(gè)實(shí)體言询,里面包含地址,這個(gè)地址可以只通過(guò)屬性嵌入的方式形成的訂單實(shí)體對(duì)象傲宜,也可以將地址通過(guò) json 序列化一個(gè) string 類型的數(shù)據(jù)运杭,存到 DB 的一個(gè)字段中,那么這個(gè) Json 串就是一個(gè)值對(duì)象函卒,是不是很好理解县习?

下面給個(gè)簡(jiǎn)單的圖(同樣是源于極客時(shí)間歐創(chuàng)新的 DDD 實(shí)戰(zhàn)課):

image.png
image.png

3.5 聚合和聚合根

3.5.1 聚合

聚合:我們把一些關(guān)聯(lián)性極強(qiáng)、生命周期一致的實(shí)體谆趾、值對(duì)象放到一個(gè)聚合里躁愿。聚合是領(lǐng)域?qū)ο蟮娘@式分組,旨在支持領(lǐng)域模型的行為和不變性沪蓬,同時(shí)充當(dāng)一致性和事務(wù)性邊界彤钟。

聚合有一個(gè)聚合根和上下文邊界,這個(gè)邊界根據(jù)業(yè)務(wù)單一職責(zé)和高內(nèi)聚原則跷叉,定義了聚合內(nèi)部應(yīng)該包含哪些實(shí)體和值對(duì)象逸雹,而聚合之間的邊界是松耦合的。按照這種方式設(shè)計(jì)出來(lái)的服務(wù)很自然就是“高內(nèi)聚云挟、低耦合”的梆砸。

聚合在 DDD 分層架構(gòu)里屬于領(lǐng)域?qū)樱I(lǐng)域?qū)影硕鄠€(gè)聚合园欣,共同實(shí)現(xiàn)核心業(yè)務(wù)邏輯帖世。跨多個(gè)實(shí)體的業(yè)務(wù)邏輯通過(guò)領(lǐng)域服務(wù)來(lái)實(shí)現(xiàn)沸枯,跨多個(gè)聚合的業(yè)務(wù)邏輯通過(guò)應(yīng)用服務(wù)來(lái)實(shí)現(xiàn)日矫。

比如有的業(yè)務(wù)場(chǎng)景需要同一個(gè)聚合的 A 和 B 兩個(gè)實(shí)體來(lái)共同完成,我們就可以將這段業(yè)務(wù)邏輯用領(lǐng)域服務(wù)來(lái)實(shí)現(xiàn)绑榴;而有的業(yè)務(wù)邏輯需要聚合 C 和聚合 D 中的兩個(gè)服務(wù)共同完成哪轿,這時(shí)你就可以用應(yīng)用服務(wù)來(lái)組合這兩個(gè)服務(wù)。

3.5.2 聚合根

如果把聚合比作組織翔怎,那聚合根就是這個(gè)組織的負(fù)責(zé)人窃诉。聚合根也稱為根實(shí)體,它不僅是實(shí)體赤套,還是聚合的管理者飘痛。

  • 首先它作為實(shí)體本身,擁有實(shí)體的屬性和業(yè)務(wù)行為于毙,實(shí)現(xiàn)自身的業(yè)務(wù)邏輯敦冬。
  • 其次它作為聚合的管理者辅搬,在聚合內(nèi)部負(fù)責(zé)協(xié)調(diào)實(shí)體和值對(duì)象按照固定的業(yè)務(wù)規(guī)則協(xié)同完成共同的業(yè)務(wù)邏輯唯沮。
  • 最后在聚合之間脖旱,它還是聚合對(duì)外的接口人,以聚合根 ID 關(guān)聯(lián)的方式接受外部任務(wù)和請(qǐng)求介蛉,在上下文內(nèi)實(shí)現(xiàn)聚合之間的業(yè)務(wù)協(xié)同萌庆。也就是說(shuō),聚合之間通過(guò)聚合根 ID 關(guān)聯(lián)引用币旧,如果需要訪問(wèn)其它聚合的實(shí)體践险,就要先訪問(wèn)聚合根,再導(dǎo)航到聚合內(nèi)部實(shí)體吹菱,外部對(duì)象不能直接訪問(wèn)聚合內(nèi)實(shí)體巍虫。

上面講的還是有些抽象,下面看一個(gè)圖就能很好理解(同樣是源于極客時(shí)間歐創(chuàng)新的DDD實(shí)戰(zhàn)課):

image.png

簡(jiǎn)單概括一下:

  • 通過(guò)事件風(fēng)暴(我理解就是頭腦風(fēng)暴鳍刷,不過(guò)我們一般都是先通過(guò)個(gè)人理解,然后再和相關(guān)核心同學(xué)進(jìn)行溝通),得到實(shí)體和值對(duì)象拨与;
  • 將這些實(shí)體和值對(duì)象聚合為“投保聚合”和“客戶聚合”猿规,其中“投保單”和“客戶”是兩者的聚合根;
  • 找出與聚合根“投保單”和“客戶”關(guān)聯(lián)的所有緊密依賴的實(shí)體和值對(duì)象尤揣;
  • 在聚合內(nèi)根據(jù)聚合根搔啊、實(shí)體和值對(duì)象的依賴關(guān)系,畫出對(duì)象的引用和依賴模型北戏。

3.6 領(lǐng)域服務(wù)和應(yīng)用服務(wù)

3.6.1 領(lǐng)域服務(wù)

當(dāng)一些邏輯不屬于某個(gè)實(shí)體時(shí)负芋,可以把這些邏輯單獨(dú)拿出來(lái)放到領(lǐng)域服務(wù)中,理想的情況是沒(méi)有領(lǐng)域服務(wù)嗜愈,如果領(lǐng)域服務(wù)使用不恰當(dāng)示罗,慢慢又演化回了以前邏輯都在 service 層的局面。

可以使用領(lǐng)域服務(wù)的情況:

  • 執(zhí)行一個(gè)顯著的業(yè)務(wù)操作
  • 對(duì)領(lǐng)域?qū)ο筮M(jìn)行轉(zhuǎn)換
  • 以多個(gè)領(lǐng)域?qū)ο笞鳛檩斎雲(yún)?shù)進(jìn)行計(jì)算芝硬,結(jié)果產(chǎn)生一個(gè)值對(duì)象

3.6.2 應(yīng)用服務(wù)

應(yīng)用層作為展現(xiàn)層與領(lǐng)域?qū)拥臉蛄貉恋悖怯脕?lái)表達(dá)用例和用戶故事的主要手段。

應(yīng)用層通過(guò)應(yīng)用服務(wù)接口來(lái)暴露系統(tǒng)的全部功能拌阴。在應(yīng)用服務(wù)的實(shí)現(xiàn)中绍绘,它負(fù)責(zé)編排和轉(zhuǎn)發(fā),它將要實(shí)現(xiàn)的功能委托給一個(gè)或多個(gè)領(lǐng)域?qū)ο髞?lái)實(shí)現(xiàn)迟赃,它本身只負(fù)責(zé)處理業(yè)務(wù)用例的執(zhí)行順序以及結(jié)果的拼裝陪拘。通過(guò)這樣一種方式,它隱藏了領(lǐng)域?qū)拥膹?fù)雜性及其內(nèi)部實(shí)現(xiàn)機(jī)制纤壁。

應(yīng)用層相對(duì)來(lái)說(shuō)是較“薄”的一層左刽,除了定義應(yīng)用服務(wù)之外,在該層我們可以進(jìn)行安全認(rèn)證酌媒,權(quán)限校驗(yàn)欠痴,持久化事務(wù)控制迄靠,或者向其他系統(tǒng)發(fā)生基于事件的消息通知,另外還可以用于創(chuàng)建郵件以發(fā)送給客戶等喇辽。

3.7 領(lǐng)域事件

領(lǐng)域事件 = 事件發(fā)布 + 事件存儲(chǔ) + 事件分發(fā) + 事件處理掌挚。

領(lǐng)域事件是一個(gè)領(lǐng)域模型中極其重要的部分,用來(lái)表示領(lǐng)域中發(fā)生的事件菩咨。忽略不相關(guān)的領(lǐng)域活動(dòng)吠式,同時(shí)明確領(lǐng)域?qū)<乙櫥蛳M煌ㄖ氖虑椋蚺c其他模型對(duì)象中的狀態(tài)更改相關(guān)聯(lián)抽米。

下面簡(jiǎn)單說(shuō)明領(lǐng)域事件:

  • 事件發(fā)布:構(gòu)建一個(gè)事件特占,需要唯一標(biāo)識(shí),然后發(fā)布云茸;
  • 事件存儲(chǔ):發(fā)布事件前需要存儲(chǔ)摩钙,因?yàn)榻邮蘸蟮氖陆ㄒ矔?huì)存儲(chǔ),可用于重試或?qū)~等查辩;
  • 事件分發(fā):服務(wù)內(nèi)直接發(fā)布給訂閱者胖笛,服務(wù)外需要借助消息中間件,比如Kafka宜岛,RabbitMQ等长踊;
  • 事件處理:先將事件存儲(chǔ),然后再處理萍倡。

比如下訂單后身弊,給用戶增長(zhǎng)積分與贈(zèng)送優(yōu)惠券的需求。如果使用瀑布流的方式寫代碼列敲。一個(gè)個(gè)邏輯調(diào)用阱佛,那么不同用戶,贈(zèng)送的東西不同戴而,邏輯就會(huì)變得又臭又長(zhǎng)凑术。

這里的比較好的方式是,用戶下訂單成功后所意,發(fā)布領(lǐng)域事件淮逊,積分聚合與優(yōu)惠券聚合監(jiān)聽(tīng)訂單發(fā)布的領(lǐng)域事件進(jìn)行處理。

3.8 資源庫(kù)【倉(cāng)儲(chǔ)】

倉(cāng)儲(chǔ)介于領(lǐng)域模型和數(shù)據(jù)模型之間扶踊,主要用于聚合的持久化和檢索泄鹏。它隔離了領(lǐng)域模型和數(shù)據(jù)模型,以便我們關(guān)注于領(lǐng)域模型而不需要考慮如何進(jìn)行持久化秧耗。

我們將暫時(shí)不使用的領(lǐng)域?qū)ο髲膬?nèi)存中持久化存儲(chǔ)到磁盤中备籽。當(dāng)日后需要再次使用這個(gè)領(lǐng)域?qū)ο髸r(shí),根據(jù) key 值到數(shù)據(jù)庫(kù)查找到這條記錄分井,然后將其恢復(fù)成領(lǐng)域?qū)ο蟪碘瑧?yīng)用程序就可以繼續(xù)使用它了霉猛,這就是領(lǐng)域?qū)ο蟪志没鎯?chǔ)的設(shè)計(jì)思想。

是不是感覺(jué)這塊內(nèi)容比較抽象诈唬?直接對(duì)著Demo學(xué)習(xí)吧,很多東西你就會(huì)豁然開(kāi)朗缩麸。

4. DDD實(shí)戰(zhàn)

4.1 項(xiàng)目介紹

  • 主要是圍繞用戶铸磅、角色和兩者的關(guān)系,構(gòu)建權(quán)限分配領(lǐng)域模型杭朱。
  • 采用 DDD 4 層架構(gòu)阅仔,包括用戶接口層、應(yīng)用層弧械、領(lǐng)域?qū)雍突A(chǔ)服務(wù)層八酒。
  • 數(shù)據(jù)通過(guò) VO、DTO刃唐、DO羞迷、PO 轉(zhuǎn)換,進(jìn)行分層隔離画饥。
  • 采用 SpringBoot + MyBatis Plus 框架衔瓮,存儲(chǔ)用 MySQL。

4.2 工程目錄

項(xiàng)目劃分為用戶接口層抖甘、應(yīng)用層热鞍、領(lǐng)域?qū)雍突A(chǔ)服務(wù)層,每一層的代碼結(jié)構(gòu)都非常清晰衔彻,包括每一層 VO薇宠、DTO、DO艰额、PO 的數(shù)據(jù)定義澄港,對(duì)于每一層的公共代碼,比如常量柄沮、接口等慢睡,都抽離到 ddd-common 中。

./ddd-application  // 應(yīng)用層
├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── ddd
                    └── applicaiton
                        ├── converter
                        │   └── UserApplicationConverter.java // 類型轉(zhuǎn)換器
                        └── impl
                            └── AuthrizeApplicationServiceImpl.java // 業(yè)務(wù)邏輯
./ddd-common
├── ddd-common // 通用類庫(kù)
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── ddd
│                       └── common
│                           ├── exception // 異常
│                           │   ├── ServiceException.java
│                           │   └── ValidationException.java
│                           ├── result // 返回結(jié)果集
│                           │   ├── BaseResult.javar
│                           │   ├── Page.java
│                           │   ├── PageResult.java
│                           │   └── Result.java
│                           └── util // 通用工具
│                               ├── GsonUtil.java
│                               └── ValidationUtil.java
├── ddd-common-application // 業(yè)務(wù)層通用模塊
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── ddd
│                       └── applicaiton
│                           ├── dto // DTO
│                           │   ├── RoleInfoDTO.java
│                           │   └── UserRoleDTO.java
│                           └── servic // 業(yè)務(wù)接口
│                               └── AuthrizeApplicationService.java
├── ddd-common-domain
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── ddd
│                       └── domain
│                           ├── event // 領(lǐng)域事件
│                           │   ├── BaseDomainEvent.java
│                           │   └── DomainEventPublisher.java
│                           └── service // 領(lǐng)域接口
│                               └── AuthorizeDomainService.java
└── ddd-common-infra
    ├── pom.xml
    └── src
        └── main
            └── java
                └── com
                    └── ddd
                        └── infra
                            ├── domain // DO
                            │   └── AuthorizeDO.java
                            ├── dto 
                            │   ├── AddressDTO.java
                            │   ├── RoleDTO.java
                            │   ├── UnitDTO.java
                            │   └── UserRoleDTO.java
                            └── repository
                                ├── UserRepository.java // 領(lǐng)域倉(cāng)庫(kù)
                                └── mybatis
                                    └── entity // PO
                                        ├── BaseUuidEntity.java
                                        ├── RolePO.java
                                        ├── UserPO.java
                                        └── UserRolePO.java
./ddd-domian  // 領(lǐng)域?qū)?├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── ddd
                    └── domain
                        ├── event // 領(lǐng)域事件
                        │   ├── DomainEventPublisherImpl.java
                        │   ├── UserCreateEvent.java
                        │   ├── UserDeleteEvent.java
                        │   └── UserUpdateEvent.java
                        └── impl // 領(lǐng)域邏輯
                            └── AuthorizeDomainServiceImpl.java
./ddd-infra  // 基礎(chǔ)服務(wù)層
├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── ddd
                    └── infra
                        ├── config
                        │   └── InfraCoreConfig.java  // 掃描Mapper文件
                        └── repository
                            ├── converter
                            │   └── UserConverter.java // 類型轉(zhuǎn)換器
                            ├── impl
                            │   └── UserRepositoryImpl.java
                            └── mapper
                                ├── RoleMapper.java
                                ├── UserMapper.java
                                └── UserRoleMapper.java
./ddd-interface
├── ddd-api  // 用戶接口層
│   ├── pom.xml
│   └── src
│       └── main
│           ├── java
│           │   └── com
│           │       └── ddd
│           │           └── api
│           │               ├── DDDFrameworkApiApplication.java // 啟動(dòng)入口
│           │               ├── converter
│           │               │   └── AuthorizeConverter.java // 類型轉(zhuǎn)換器
│           │               ├── model
│           │               │   ├── req // 入?yún)?req
│           │               │   │   ├── AuthorizeCreateReq.java
│           │               │   │   └── AuthorizeUpdateReq.java
│           │               │   └── vo  // 輸出 VO
│           │               │       └── UserAuthorizeVO.java
│           │               └── web     // API
│           │                   └── AuthorizeController.java
│           └── resources // 系統(tǒng)配置
│               ├── application.yml
│           └── resources // Sql文件
│               └── init.sql
└── ddd-task
    └── pom.xml
./pom.xml

4.3 數(shù)據(jù)庫(kù)

包括 3 張表铡溪,分別為用戶漂辐、角色和用戶角色表,一個(gè)用戶可以擁有多個(gè)角色棕硫,一個(gè)角色可以分配給多個(gè)用戶髓涯。

create table t_user
(
    id           bigint auto_increment comment '主鍵' primary key,
    user_name    varchar(64)                        null comment '用戶名',
    password     varchar(255)                       null comment '密碼',
    real_name    varchar(64)                        null comment '真實(shí)姓名',
    phone        bigint                             null comment '手機(jī)號(hào)',
    province     varchar(64)                        null comment '用戶名',
    city         varchar(64)                        null comment '用戶名',
    county       varchar(64)                        null comment '用戶名',
    unit_id      bigint                             null comment '單位id',
    unit_name    varchar(64)                        null comment '單位名稱',
    gmt_create   datetime default CURRENT_TIMESTAMP not null comment '創(chuàng)建時(shí)間',
    gmt_modified datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '修改時(shí)間',
    deleted      bigint   default 0                 not null comment '是否刪除,非0為已刪除'
)comment '用戶表' collate = utf8_bin;

create table t_role
(
    id           bigint auto_increment comment '主鍵' primary key,
    name         varchar(256)                       not null comment '名稱',
    code         varchar(64)                        null comment '角色code',
    gmt_create   datetime default CURRENT_TIMESTAMP not null comment '創(chuàng)建時(shí)間',
    gmt_modified datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '修改時(shí)間',
    deleted      bigint   default 0                 not null comment '是否已刪除'
)comment '角色表' charset = utf8;

create table t_user_role (
    id           bigint auto_increment comment '主鍵id' primary key,
    user_id      bigint                             not null comment '用戶id',
    role_id      bigint                             not null comment '角色id',
    gmt_create   datetime default CURRENT_TIMESTAMP not null comment '創(chuàng)建時(shí)間',
    gmt_modified datetime default CURRENT_TIMESTAMP not null comment '修改時(shí)間',
    deleted      bigint   default 0                 not null comment '是否已刪除'
)comment '用戶角色關(guān)聯(lián)表' charset = utf8;

4.4 基礎(chǔ)服務(wù)層

倉(cāng)儲(chǔ)(資源庫(kù))介于領(lǐng)域模型和數(shù)據(jù)模型之間哈扮,主要用于聚合的持久化和檢索纬纪。它隔離了領(lǐng)域模型和數(shù)據(jù)模型蚓再,以便我們關(guān)注于領(lǐng)域模型而不需要考慮如何進(jìn)行持久化。

比如保存用戶包各,需要將用戶和角色一起保存摘仅,也就是創(chuàng)建用戶的同時(shí),需要新建用戶的角色權(quán)限问畅,這個(gè)可以直接全部放到倉(cāng)儲(chǔ)中:

public AuthorizeDO save(AuthorizeDO user) {
    UserPO userPo = userConverter.toUserPo(user);
    if(Objects.isNull(user.getUserId())){
        userMapper.insert(userPo);
        user.setUserId(userPo.getId());
    } else {
        userMapper.updateById(userPo);
        userRoleMapper.delete(Wrappers.<UserRolePO>lambdaQuery()
                .eq(UserRolePO::getUserId, user.getUserId()));
    }
    List<UserRolePO> userRolePos = userConverter.toUserRolePo(user);
    userRolePos.forEach(userRoleMapper::insert);
    return this.query(user.getUserId());
}

倉(cāng)儲(chǔ)對(duì)外暴露的接口如下:

// 用戶領(lǐng)域倉(cāng)儲(chǔ)
public interface UserRepository {
    // 刪除
    void delete(Long userId);
    // 查詢
    AuthorizeDO query(Long userId);
    // 保存
    AuthorizeDO save(AuthorizeDO user);
}

基礎(chǔ)服務(wù)層不僅僅包括資源庫(kù)娃属,與第三方的調(diào)用,都需要放到該層护姆,Demo 中沒(méi)有該示例矾端,我們可以看一個(gè)小米內(nèi)部具體的實(shí)際項(xiàng)目,他把第三方的調(diào)用放到了 remote 目錄中:

4.5 領(lǐng)域?qū)?/h1>

4.5.1 聚合&聚合根

我們有用戶和角色兩個(gè)實(shí)體卵皂,可以將用戶秩铆、角色和兩者關(guān)系進(jìn)行聚合,然后用戶就是聚合根灯变,聚合之后的屬性殴玛,我們稱之為“權(quán)限”。

對(duì)于地址 Address添祸,目前是作為字段屬性存儲(chǔ)到 DB 中族阅,如果對(duì)地址無(wú)需進(jìn)行檢索,可以把地址作為“值對(duì)象”進(jìn)行存儲(chǔ)膝捞,即把地址序列化為 Json 存坦刀,存儲(chǔ)到 DB 的一個(gè)字段中。

public class AuthorizeDO {
    // 用戶ID
    private Long userId;
    // 用戶名
    private String userName;
    // 真實(shí)姓名
    private String realName;
    // 手機(jī)號(hào)
    private String phone;
    // 密碼
    private String password;
    // 用戶單位
    private UnitDTO unit;
    // 用戶地址
    private AddressDTO address;
    // 用戶角色
    private List<RoleDTO> roles;
}

4.5.2 領(lǐng)域服務(wù)

Demo中的領(lǐng)域服務(wù)比較薄蔬咬,通過(guò)單位ID后去獲取單位名稱鲤遥,構(gòu)建單位信息:

@Service
public class AuthorizeDomainServiceImpl implements AuthorizeDomainService {
    @Override
    // 設(shè)置單位信息
    public void associatedUnit(AuthorizeDO authorizeDO) {
        String unitName = "武漢小米";// TODO: 通過(guò)第三方獲取
        authorizeDO.getUnit().setUnitName(unitName);
    }
}

我們其實(shí)可以把領(lǐng)域服務(wù)再進(jìn)一步抽象,可以抽象出領(lǐng)域能力林艘,通過(guò)這些領(lǐng)域能力去構(gòu)建應(yīng)用層邏輯盖奈,比如賬號(hào)相關(guān)的領(lǐng)域能力可以包括授權(quán)領(lǐng)域能力、身份認(rèn)證領(lǐng)域能力等狐援,這樣每個(gè)領(lǐng)域能力相對(duì)獨(dú)立钢坦,就不會(huì)全部揉到一個(gè)文件中,下面是實(shí)際項(xiàng)目的領(lǐng)域?qū)咏貓D:

image.png

4.5.3 領(lǐng)域事件

領(lǐng)域事件 = 事件發(fā)布 + 事件存儲(chǔ) + 事件分發(fā) + 事件處理啥酱。

這個(gè) Demo 中爹凹,對(duì)領(lǐng)域事件的處理非常簡(jiǎn)單,還是一個(gè)應(yīng)用內(nèi)部的領(lǐng)域事件镶殷,就是每次執(zhí)行一次具體的操作時(shí)禾酱,把行為記錄下來(lái)。Demo 中沒(méi)有記錄事件的庫(kù)表,事件的分發(fā)還是同步的方式颤陶,所以 Demo 中的領(lǐng)域事件還不完善颗管,后面我會(huì)再繼續(xù)完善 Demo 中的領(lǐng)域事件,通過(guò) Java 消息機(jī)制實(shí)現(xiàn)解耦滓走,甚至可以借助消息隊(duì)列垦江,實(shí)現(xiàn)異步。

/**
 * 領(lǐng)域事件基類
 *
 * @author louzai
 * @since 2021/11/22
 */
@Getter
@Setter
@NoArgsConstructor
public abstract class BaseDomainEvent<T> implements Serializable {
    private static final long serialVersionUID = 1465328245048581896L;
    /**
     * 發(fā)生時(shí)間
     */
    private LocalDateTime occurredOn;
    /**
     * 領(lǐng)域事件數(shù)據(jù)
     */
    private T data;
    public BaseDomainEvent(T data) {
        this.data = data;
        this.occurredOn = LocalDateTime.now();
    }
}

/**
 * 用戶新增領(lǐng)域事件
 *
 * @author louzai
 * @since 2021/11/20
 */
public class UserCreateEvent extends BaseDomainEvent<AuthorizeDO> {
    public UserCreateEvent(AuthorizeDO user) {
        super(user);
    }
}

/**
 * 領(lǐng)域事件發(fā)布實(shí)現(xiàn)類
 *
 * @author louzai
 * @since 2021/11/20
 */
@Component
@Slf4j
public class DomainEventPublisherImpl implements DomainEventPublisher {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void publishEvent(BaseDomainEvent event) {
        log.debug("發(fā)布事件,event:{}", GsonUtil.gsonToString(event));
        applicationEventPublisher.publishEvent(event);
    }
}

4.4 應(yīng)用層

應(yīng)用層就非常好理解了搅方,只負(fù)責(zé)簡(jiǎn)單的邏輯編排比吭,比如創(chuàng)建用戶授權(quán):

@Transactional(rollbackFor = Exception.class)
public void createUserAuthorize(UserRoleDTO userRoleDTO){
    // DTO轉(zhuǎn)為DO
    AuthorizeDO authorizeDO = userApplicationConverter.toAuthorizeDo(userRoleDTO);
    // 關(guān)聯(lián)單位單位信息
    authorizeDomainService.associatedUnit(authorizeDO);
    // 存儲(chǔ)用戶
    AuthorizeDO saveAuthorizeDO = userRepository.save(authorizeDO);
    // 發(fā)布用戶新建的領(lǐng)域事件
    domainEventPublisher.publishEvent(new UserCreateEvent(saveAuthorizeDO));
}

查詢用戶授權(quán)信息:

@Override
  public UserRoleDTO queryUserAuthorize(Long userId) {
      // 查詢用戶授權(quán)領(lǐng)域數(shù)據(jù)
      AuthorizeDO authorizeDO = userRepository.query(userId);
      if (Objects.isNull(authorizeDO)) {
          throw ValidationException.of("UserId is not exist.", null);
      }
      // DO轉(zhuǎn)DTO
      return userApplicationConverter.toAuthorizeDTO(authorizeDO);
  }

細(xì)心的同學(xué)可以發(fā)現(xiàn),我們應(yīng)用層和領(lǐng)域?qū)友ㄟ^(guò) DTO 和 DO 進(jìn)行數(shù)據(jù)轉(zhuǎn)換梗逮。

4.5 用戶接口層

最后就是提供 API 接口:

@GetMapping("/query")
public Result<UserAuthorizeVO> query(@RequestParam("userId") Long userId){
    UserRoleDTO userRoleDTO = authrizeApplicationService.queryUserAuthorize(userId);
    Result<UserAuthorizeVO> result = new Result<>();
    result.setData(authorizeConverter.toVO(userRoleDTO));
    result.setCode(BaseResult.CODE_SUCCESS);
    return result;
}

@PostMapping("/save")
public Result<Object> create(@RequestBody AuthorizeCreateReq authorizeCreateReq){
    authrizeApplicationService.createUserAuthorize(authorizeConverter.toDTO(authorizeCreateReq));
    return Result.ok(BaseResult.INSERT_SUCCESS);
}

數(shù)據(jù)的交互项秉,包括入?yún)⑿辶铩TO 和 VO,都需要對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換娄蔼。

4.6 項(xiàng)目運(yùn)行

  • 新建庫(kù)表:通過(guò)文件 "ddd-interface/ddd-api/src/main/resources/init.sql" 新建庫(kù)表怖喻。

  • 修改 SQL 配置:修改 "ddd-interface/ddd-api/src/main/resources/application.yml" 的數(shù)據(jù)庫(kù)配置。

  • 啟動(dòng)服務(wù):直接啟動(dòng)服務(wù)即可岁诉。

  • 測(cè)試用例:

  • 請(qǐng)求 URL:http://127.0.0.1:8087/api/user/save

  • Post body:{"userName":"louzai","realName":"樓","phone":13123676844,"password":"***","unitId":2,"province":"湖北省","city":"鄂州市","county":"葛店開(kāi)發(fā)區(qū)","roles":[{"roleId":2}]}

4.7 項(xiàng)目地址

DDD Demo 代碼已經(jīng)上傳到 GitHub 中:

https://github.com/lml200701158/ddd-framework

或者通過(guò)下面命令直接獲让小:

git clone git@github.com:lml200701158/ddd-framework.git

5. 結(jié)語(yǔ)

談?wù)勎覍?duì) DDD 的理解,我覺(jué)得 DDD 不像一門技術(shù)涕癣,我理解的技術(shù)比如高并發(fā)哗蜈、緩存、消息隊(duì)列等坠韩,DDD 更像是一項(xiàng)軟技能距潘,一種方法論,包含了很多設(shè)計(jì)理念只搁。

這篇文章寫于去年音比,所以當(dāng)時(shí)對(duì) DDD 理解的其實(shí)還不夠深入,今年做過(guò)一些 DDD 的項(xiàng)目氢惋,所以現(xiàn)在對(duì) DDD 的理解又加深了幾分洞翩。

大家不要認(rèn)為,掌握了一些概念焰望,以及 DDD 的基本思想骚亿,就掌握了 DDD,然后做項(xiàng)目時(shí)熊赖,照葫蘆畫瓢循未,這樣你會(huì)死的很慘!

只掌握 DDD 表面的東西,其實(shí)是不夠的的妖,我覺(jué)得 DDD 最復(fù)雜的地方绣檬,其實(shí)是在它的領(lǐng)域設(shè)計(jì)部分,項(xiàng)目啟動(dòng)前嫂粟,你一定要設(shè)計(jì)各個(gè)領(lǐng)域?qū)ο蠼课矗约八鼈冎苯拥慕换リP(guān)系。

比如我們之前做過(guò)一個(gè)項(xiàng)目星虹,因?yàn)檫@塊沒(méi)有做好零抬,大家一邊寫代碼,一邊還在思考宽涌,這個(gè)領(lǐng)域?qū)ο笤撊绾螛?gòu)造平夜,嚴(yán)重影響開(kāi)發(fā)效率,最后又不得不回退到 MVC 的模式卸亮。

不要為了炫技忽妒,啥都要搞個(gè) DDD,兩者如何選擇:

  • MVC:上來(lái)就可以開(kāi)干兼贸,短平快段直,前期用起來(lái)很香,整體開(kāi)發(fā)效率也更高溶诞,所以對(duì)于緊急鸯檬,或者不那么重要的項(xiàng)目,我會(huì)直接用 MVC 懟螺垢,不好的地方就是喧务,后面會(huì)越來(lái)越復(fù)雜,可能最后就是一坨屎山枉圃,但是很多時(shí)候功茴,比如老板進(jìn)度催的緊,我哪想到那么多以后呢讯蒲?
  • DDD:前期需要花大量時(shí)間設(shè)計(jì)好領(lǐng)域模型痊土,對(duì)于一些基礎(chǔ)組件,或者一些核心服務(wù)墨林,如果對(duì)象模型非常復(fù)雜赁酝,建議采用 DDD,前期可能會(huì)稍微痛苦一些旭等,但是后期維護(hù)起來(lái)會(huì)非常方便酌呆。

6. 參考文章

原文鏈接:
https://mp.weixin.qq.com/s/sOkXn2kr60sztmpb5_MsvQ

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痰娱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子菩收,更是在濱河造成了極大的恐慌梨睁,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娜饵,死亡現(xiàn)場(chǎng)離奇詭異坡贺,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)箱舞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門遍坟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人晴股,你說(shuō)我怎么就攤上這事愿伴。” “怎么了电湘?”我有些...
    開(kāi)封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵隔节,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我胡桨,道長(zhǎng)官帘,這世上最難降的妖魔是什么瞬雹? 我笑而不...
    開(kāi)封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任昧谊,我火速辦了婚禮,結(jié)果婚禮上酗捌,老公的妹妹穿的比我還像新娘呢诬。我一直安慰自己,他們只是感情好胖缤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布尚镰。 她就那樣靜靜地躺著,像睡著了一般哪廓。 火紅的嫁衣襯著肌膚如雪狗唉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天涡真,我揣著相機(jī)與錄音分俯,去河邊找鬼。 笑死哆料,一個(gè)胖子當(dāng)著我的面吹牛缸剪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播东亦,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼杏节,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起奋渔,我...
    開(kāi)封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤镊逝,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后嫉鲸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蹋半,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年充坑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了减江。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捻爷,死狀恐怖辈灼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情也榄,我是刑警寧澤巡莹,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站甜紫,受9級(jí)特大地震影響降宅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜囚霸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一腰根、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拓型,春花似錦额嘿、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至压固,卻和暖如春球拦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帐我。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工坎炼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人焚刚。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓点弯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親矿咕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抢肛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容