DDD
至少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)題。
敏捷宣言
http://www.extremeprogramming.cn/content/xp/test-first.html
https://www.inflectra.com/Ideas/Topic/User-Stories.aspx
復(fù)雜性的挑戰(zhàn)
有很多因素會(huì)使軟件開(kāi)發(fā)復(fù)雜化,但最根本的原因是問(wèn)題領(lǐng)域本身的錯(cuò)綜復(fù)雜。但我們無(wú)法回避這種復(fù)雜,只能控制這種復(fù)雜。
很多因素可能導(dǎo)致項(xiàng)目偏離軌道,如官僚主義、目標(biāo)不清、資源不足等。但真正決定軟件復(fù)雜性的是設(shè)計(jì)方法。很多應(yīng)用的復(fù)雜性并不在技術(shù)上,而是來(lái)自領(lǐng)域本身,用戶的活動(dòng)或業(yè)務(wù)。當(dāng)這種領(lǐng)域復(fù)雜性在設(shè)計(jì)中沒(méi)有得到解決時(shí),基礎(chǔ)技術(shù)的構(gòu)思再好也無(wú)濟(jì)于事。成功的設(shè)計(jì)必須系統(tǒng)地考慮這個(gè)核心方面。
設(shè)計(jì)過(guò)程與開(kāi)發(fā)過(guò)程
本向“敏捷開(kāi)發(fā)過(guò)程”這一新的體系。特別地, 我們假定項(xiàng)目必須遵循兩個(gè)開(kāi)發(fā)實(shí)踐
- 迭代開(kāi)發(fā)
- 開(kāi)發(fā)人員必須與領(lǐng)域?qū)<揖哂忻芮械年P(guān)系。
極限編程承認(rèn)設(shè)計(jì)決策的重要性,但強(qiáng)烈反對(duì)預(yù)告設(shè)計(jì)。相反,它將相當(dāng)大的精力投入到促銷溝通和提高快速變更能力的工作中。具有這種反應(yīng)能力之后,開(kāi)發(fā)人員就可以在項(xiàng)目的任何階段只利用“最簡(jiǎn)單而管用的方案”,然后不斷進(jìn)行重構(gòu),一步一步做出小的設(shè)計(jì)改進(jìn),最終得到滿足客戶真正需求的設(shè)計(jì)。
運(yùn)用領(lǐng)域模型
領(lǐng)域
模型被用來(lái)描繪人們所關(guān)注的現(xiàn)實(shí)或想法的某個(gè)方面。模型是一種簡(jiǎn)化。它是對(duì)現(xiàn)實(shí)的解釋。
它與解決問(wèn)題密切相關(guān)的方面的抽象相關(guān),而忽略無(wú)關(guān)的細(xì)節(jié)。
每個(gè)軟件程序是為了執(zhí)行用戶的某項(xiàng)活動(dòng),或是滿足用戶的某種需求。這些用戶應(yīng)用軟件的問(wèn)題區(qū)域就是軟件的“領(lǐng)域”。一些領(lǐng)域涉及物質(zhì)世界,有些領(lǐng)域則是無(wú)形的。
為了創(chuàng)建真正能為用戶活動(dòng)所用的軟件,開(kāi)發(fā)團(tuán)隊(duì)必須運(yùn)用一整套與這些活動(dòng)有關(guān)的知識(shí)體系。所需的知識(shí)的廣度可能令人望而生畏,龐大而復(fù)雜的信息也可能超乎想象。模型正是解決此類信息超載問(wèn)題的工具。模型這種知識(shí)形式對(duì)知識(shí)進(jìn)行了選擇性的簡(jiǎn)化和有意的結(jié)構(gòu)化。適當(dāng)?shù)哪P涂梢允谷死斫庑畔⒌囊饬x,并專注于問(wèn)題。
模型在領(lǐng)域設(shè)計(jì)中的作用
在DDD 中三個(gè)基本的用戶決定了模型的選擇:
- 模型的設(shè)計(jì)的核心互相影響。模型與實(shí)現(xiàn)之間的緊密聯(lián)系才使模型變得有用:
- 確保我們?cè)谀P椭兴M(jìn)行的分析能夠轉(zhuǎn)化為代碼。
- 在后期維護(hù)期間可以基于對(duì)模型的理解來(lái)解釋代碼。
- 模型是團(tuán)隊(duì)所有成員使用的通用語(yǔ)言中樞(共識(shí)的結(jié)果),技術(shù)團(tuán)隊(duì)與領(lǐng)域?qū)<覠o(wú)翻譯溝通。
- 模型是濃縮的知識(shí),
- 來(lái)自軟件早期的經(jīng)驗(yàn)可以作為反饋應(yīng)用到建模過(guò)程中(迭代)
軟件的核心
軟件的核心是其為用戶解決領(lǐng)域相關(guān)問(wèn)題的能力。所有其他特性,不管有多么重要,都要服務(wù)于這個(gè)基本目的。(大部分開(kāi)發(fā)人員只專注于技術(shù)本身不關(guān)心業(yè)務(wù)知識(shí),只見(jiàn)樹(shù)木,不見(jiàn)森林)
消化知識(shí)
有效建模的要素
- 模型和實(shí)現(xiàn)的綁定
- 建立了一種模型的語(yǔ)言
- 開(kāi)發(fā)一個(gè)蘊(yùn)含豐富知識(shí)的模型
- 提供模型
在模型日益完善的過(guò)程中,重要的概念不斷被添加到模型中,但同樣重要的是,不再使用的或不重要的概念則從模型中被移除。當(dāng)一個(gè)不需要的概念與一個(gè)需要的概念有關(guān)聯(lián)時(shí),則把重要的概念提取到一個(gè)新模型中,
- 頭腦風(fēng)暴和實(shí)驗(yàn)
知識(shí)消化
知識(shí)消息不是一項(xiàng)孤立的活動(dòng) ,它一般是在開(kāi)發(fā)人員的領(lǐng)導(dǎo)下,由開(kāi)發(fā)人員與領(lǐng)域?qū)<医M成的團(tuán)隊(duì)來(lái)完成的。他們共同收集信息,并通過(guò)消化而將知識(shí)組織為有用的形式。信息的原始資源來(lái)自于:
- 領(lǐng)域?qū)<翌^腦里的知識(shí)
- 現(xiàn)有系統(tǒng)的用戶
- 技術(shù)團(tuán)隊(duì)以前在相關(guān)遺留系統(tǒng)或同領(lǐng)域的其他項(xiàng)目中積累的經(jīng)驗(yàn)。
信息的形式也多種多樣
傳統(tǒng)的瀑布流方式因?yàn)闆](méi)有反饋,所以項(xiàng)目經(jīng)驗(yàn)失敗。知識(shí)只是朝一個(gè)方向流量,而且不會(huì)累積。
模型在不斷改時(shí)的同時(shí),也成為組織項(xiàng)目信息流(統(tǒng)一能用語(yǔ)言)的工具。模型聚集于需求分析。 它與編程和設(shè)計(jì)緊密交互。它通過(guò)良性循環(huán)加深團(tuán)隊(duì)成員對(duì)領(lǐng)域的理解,使他們更透徹地理解模型,并對(duì)其進(jìn)一步精化。模型永遠(yuǎn)都不不會(huì)是完美的,因?yàn)樗且粋€(gè)不斷演化完善的過(guò)程。模型對(duì)理解領(lǐng)域必須是切實(shí)可用的。它們必須非常精確,以便使應(yīng)用程序易于實(shí)現(xiàn)和理解。
SDLC (System Development Life Cycle)系統(tǒng)開(kāi)發(fā)生命周期
Waterffull vs agile
領(lǐng)域模型并不是按照“選建模,后實(shí)現(xiàn)”這個(gè)次序來(lái)工作的。象很多人一樣,我也反對(duì)“先設(shè)計(jì)、再構(gòu)建”這種固化的思維模式。Eric的經(jīng)驗(yàn)告訴我們,真正強(qiáng)大的領(lǐng)域模型是隨著時(shí)間演進(jìn)的,即使是最有經(jīng)驗(yàn)的建模人員也往往發(fā)現(xiàn)他們是在系統(tǒng)初始版本完成之后才了最好的想法。
Martin fowler
http://www.agilenutshell.com/agile_vs_waterfall
https://baijiahao.baidu.com/s?id=1720106868823032268&wfr=spider&for=pc
https://cloud.tencent.com/developer/article/1861060
存儲(chǔ)庫(kù)是為聚合而服務(wù)的
存儲(chǔ)庫(kù)不是一個(gè)對(duì)象。它是一個(gè)程序邊界以及一個(gè)明確的約定,在其上命名方法時(shí)它需要的工作量與領(lǐng)域模型中的對(duì)象所需的工作量一樣多。你的存儲(chǔ)庫(kù)約定應(yīng)該是特定的以及能夠揭示意圖并對(duì)領(lǐng)域?qū)<揖哂幸饬x。
我一般的思考方式是:domainService是規(guī)則引擎,appService才是流程引擎。Repo跟規(guī)則無(wú)關(guān)
業(yè)務(wù)規(guī)則與業(yè)務(wù)流程怎么區(qū)分?
有個(gè)很簡(jiǎn)單的辦法區(qū)分,業(yè)務(wù)規(guī)則是有if/else的,業(yè)務(wù)流程沒(méi)有
https://cloud.tencent.com/developer/article/1803939
DDD 第二彈
架構(gòu)這個(gè)詞源于英文里的“Architecture“,源頭是土木工程里的“建筑”和“結(jié)構(gòu)”,而架構(gòu)里的”架“同時(shí)又包含了”架子“(scaffolding)的含義,意指能快速搭建起來(lái)的固定結(jié)構(gòu)。而今天的應(yīng)用架構(gòu),意指軟件系統(tǒng)中固定不變的代碼結(jié)構(gòu)、設(shè)計(jì)模式、規(guī)范和組件間的通信方式。在應(yīng)用開(kāi)發(fā)中架構(gòu)之所以是最重要的第一步,因?yàn)橐粋€(gè)好的架構(gòu)能讓系統(tǒng)安全、穩(wěn)定、快速迭代。在一個(gè)團(tuán)隊(duì)內(nèi)通過(guò)規(guī)定一個(gè)固定的架構(gòu)設(shè)計(jì),可以讓團(tuán)隊(duì)內(nèi)能力參差不齊的同學(xué)們都能有一個(gè)統(tǒng)一的開(kāi)發(fā)規(guī)范,降低溝通成本,提升效率和代碼質(zhì)量。
在做架構(gòu)設(shè)計(jì)時(shí),一個(gè)好的架構(gòu)應(yīng)該需要實(shí)現(xiàn)以下幾個(gè)目標(biāo):
獨(dú)立于框架:架構(gòu)不應(yīng)該依賴某個(gè)外部的庫(kù)或框架,不應(yīng)該被框架的結(jié)構(gòu)所束縛。
獨(dú)立于UI:前臺(tái)展示的樣式可能會(huì)隨時(shí)發(fā)生變化(今天可能是網(wǎng)頁(yè)、明天可能變成console、后天是獨(dú)立app),但是底層架構(gòu)不應(yīng)該隨之而變化。
獨(dú)立于底層數(shù)據(jù)源:無(wú)論今天你用MySQL、Oracle還是MongoDB、CouchDB,甚至使用文件系統(tǒng),軟件架構(gòu)不應(yīng)該因?yàn)椴煌牡讓訑?shù)據(jù)儲(chǔ)存方式而產(chǎn)生巨大改變。
獨(dú)立于外部依賴:無(wú)論外部依賴如何變更、升級(jí),業(yè)務(wù)的核心邏輯不應(yīng)該隨之而大幅變化。
可測(cè)試:無(wú)論外部依賴了什么數(shù)據(jù)庫(kù)、硬件、UI或者服務(wù),業(yè)務(wù)的邏輯應(yīng)該都能夠快速被驗(yàn)證正確性。
我們可以發(fā)現(xiàn),通過(guò)對(duì)外部依賴的抽象和內(nèi)部邏輯的封裝重構(gòu),應(yīng)用整體的依賴關(guān)系變了:
最底層不再是數(shù)據(jù)庫(kù),而是Entity、Domain Primitive和Domain Service。這些對(duì)象不依賴任何外部服務(wù)和框架,而是純內(nèi)存中的數(shù)據(jù)和操作。這些對(duì)象我們打包為Domain Layer(領(lǐng)域?qū)樱?。領(lǐng)域?qū)記](méi)有任何外部依賴關(guān)系。
再其次的是負(fù)責(zé)組件編排的Application Service,但是這些服務(wù)僅僅依賴了一些抽象出來(lái)的ACL類和Repository類,而其具體實(shí)現(xiàn)類是通過(guò)依賴注入注進(jìn)來(lái)的。Application Service、Repository、ACL等我們統(tǒng)稱為Application Layer(應(yīng)用層)。應(yīng)用層 依賴 領(lǐng)域?qū)?,但不依賴具體實(shí)現(xiàn)。
最后是ACL,Repository等的具體實(shí)現(xiàn),這些實(shí)現(xiàn)通常依賴外部具體的技術(shù)實(shí)現(xiàn)和框架,所以統(tǒng)稱為Infrastructure Layer(基礎(chǔ)設(shè)施層)。Web框架里的對(duì)象如Controller之類的通常也屬于基礎(chǔ)設(shè)施層。
https://mp.weixin.qq.com/s/MU1rqpQ1aA1p7OtXqVVwxQ
聚合根的設(shè)計(jì)?
策略模式與充血模型設(shè)計(jì)?
DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)總結(jié)先說(shuō)下傳統(tǒng)系統(tǒng)設(shè)計(jì),大部分從數(shù)據(jù)庫(kù)開(kāi)始--自底向上的設(shè)計(jì),這種設(shè)計(jì)會(huì)使系統(tǒng)的設(shè)計(jì)受到數(shù)據(jù)庫(kù)的影響,會(huì)有比較大的局限性,比如說(shuō):數(shù)據(jù)庫(kù)僅有數(shù)據(jù),沒(méi)有行為,而對(duì)現(xiàn)實(shí)世界的描述則會(huì)更加抽象,更加遠(yuǎn)離業(yè)務(wù).開(kāi)發(fā)團(tuán)隊(duì)通過(guò)與產(chǎn)品或客戶的溝通,直接設(shè)計(jì)表模型,由于會(huì)受到溝通的效率的影響,客戶不懂技術(shù),開(kāi)發(fā)從技術(shù)角度思考,整個(gè)溝通過(guò)程則會(huì)有較大的信息損失,所設(shè)計(jì)出的表模型則很可能面臨后期業(yè)務(wù)變動(dòng)的挑戰(zhàn).導(dǎo)致系統(tǒng)的成敗主要取決于架構(gòu)師在那個(gè)領(lǐng)域的業(yè)務(wù)水平而不是技術(shù)水平.
而DDD是采用自頂而下的設(shè)計(jì),戰(zhàn)略設(shè)計(jì)時(shí)業(yè)務(wù)為王,不再受到技術(shù)的局限性(我想可能跟現(xiàn)在技術(shù)的發(fā)展有關(guān)系,極大豐富的底層技術(shù)框架,使得技術(shù)選型更加自由),設(shè)計(jì)過(guò)程中,需要業(yè)務(wù)專家(不用懂技術(shù),比如客戶和產(chǎn)品等)和開(kāi)發(fā)團(tuán)隊(duì)一塊僅僅從業(yè)務(wù)的角度分析需求,不要受到任何技術(shù)的局限性,采用事件風(fēng)暴(類似頭腦風(fēng)暴)的方式,討論整個(gè)系統(tǒng)面臨的各種用戶場(chǎng)景,整個(gè)系統(tǒng)提供的各個(gè)功能模塊,做業(yè)務(wù)上的歸集分類分級(jí),在討論過(guò)程中,各個(gè)團(tuán)隊(duì)(客戶 產(chǎn)品 開(kāi)發(fā) 測(cè)試 運(yùn)維 交付)逐漸統(tǒng)一語(yǔ)言(同一個(gè)限界上下文中),使得業(yè)務(wù)場(chǎng)景更加清晰的被描述和歸納分類出來(lái),在整個(gè)溝通中更加有效率,更加準(zhǔn)確,后續(xù)維護(hù)階段的持續(xù)交付迭代都將由此而獲益.
https://zhuanlan.zhihu.com/p/91525839
美團(tuán)
import com.company.team.bussiness.lottery.domain.valobj.;//領(lǐng)域?qū)ο?值對(duì)象*
import com.company.team.bussiness.lottery.domain.entity.;//領(lǐng)域?qū)ο?實(shí)體*
import com.company.team.bussiness.lottery.domain.aggregate.;//領(lǐng)域?qū)ο?聚合根*
import com.company.team.bussiness.lottery.service.;//領(lǐng)域服務(wù)*
import com.company.team.bussiness.lottery.repo.;//領(lǐng)域資源庫(kù)*
import com.company.team.bussiness.lottery.facade.;//領(lǐng)域防腐層*
https://martinfowler.com/articles/microservices.html
領(lǐng)域事件:
可能發(fā)生在同一個(gè)微服務(wù)內(nèi)(跨聚合),一般微服務(wù)之間才采用領(lǐng)域事件實(shí)現(xiàn)異步通知.
值對(duì)象是內(nèi)聚并且可以具有行為
https://www.cnblogs.com/uoyo/p/11951840.html
https://zhuanlan.zhihu.com/p/80921515
對(duì)于一個(gè)架構(gòu)師來(lái)說(shuō),在軟件開(kāi)發(fā)中如何降低系統(tǒng)復(fù)雜度是一個(gè)永恒的挑戰(zhàn),無(wú)論是 94 年 GoF 的 Design Patterns , 99 年的 Martin Fowler 的 Refactoring , 02 年的 P of EAA ,還是 03 年的 Enterprise Integration Patterns ,都是通過(guò)一系列的設(shè)計(jì)模式或范例來(lái)降低一些常見(jiàn)的復(fù)雜度。但是問(wèn)題在于,這些書(shū)的理念是通過(guò)技術(shù)手段解決技術(shù)問(wèn)題,但并沒(méi)有從根本上解決業(yè)務(wù)的問(wèn)題。所以 03 年 Eric Evans 的 Domain Driven Design 一書(shū),以及后續(xù) Vaughn Vernon 的 Implementing DDD , Uncle Bob 的 Clean Architecture 等書(shū),真正的從業(yè)務(wù)的角度出發(fā),為全世界絕大部分做純業(yè)務(wù)的開(kāi)發(fā)提供了一整套的架構(gòu)思路。
作者這里有一個(gè)說(shuō)法是不準(zhǔn)確的:“DDD 不是一套框架,而是一種架構(gòu)思想”。DDD不是一種架構(gòu)思想。DDD和架構(gòu)之間是正交的關(guān)系,DDD可以和多種架構(gòu)方式配合使用,包括n層架構(gòu),端口和連接器架構(gòu),Clean架構(gòu),微服務(wù)架構(gòu)等等。架構(gòu)關(guān)乎軟件結(jié)構(gòu),從各個(gè)維度定義軟件的組成部分以及各部分之間的關(guān)系。DDD是一種設(shè)計(jì)范式,主張以領(lǐng)域模型為中心驅(qū)動(dòng)整個(gè)軟件的設(shè)計(jì)。在DDD中,業(yè)務(wù)分析和領(lǐng)域建模是軟件開(kāi)發(fā)的關(guān)鍵活動(dòng)。它不關(guān)心軟件的架構(gòu)是怎樣的。隨著技術(shù)的發(fā)展,我們可能在新版本中更換軟件的架構(gòu),但是只要業(yè)務(wù)沒(méi)有變更,領(lǐng)域模型就是穩(wěn)定的,無(wú)需改動(dòng)。
Robert C. Martin 在《整潔架構(gòu)》一書(shū)中認(rèn)為,架構(gòu)和業(yè)務(wù)功能無(wú)關(guān),只聚焦于軟件質(zhì)量,尤其是內(nèi)部質(zhì)量。而DDD完全聚焦于業(yè)務(wù)功能——發(fā)現(xiàn)問(wèn)題域的內(nèi)部組成、結(jié)構(gòu)、規(guī)則和機(jī)制。
https://zhuanlan.zhihu.com/p/356518017
盡量避免public setter
一個(gè)最容易導(dǎo)致不一致性的原因是實(shí)體暴露了public的setter方法,特別是set單一參數(shù)會(huì)導(dǎo)致?tīng)顟B(tài)不一致的情況。比如,一個(gè)訂單可能包含訂單狀態(tài)(下單、已支付、已發(fā)貨、已收貨)、支付單、物流單等子實(shí)體,如果一個(gè)調(diào)用方能隨意去set訂單狀態(tài),就有可能導(dǎo)致訂單狀態(tài)和子實(shí)體匹配不上,導(dǎo)致業(yè)務(wù)流程走不通的情況。所以在實(shí)體里,需要通過(guò)行為方法來(lái)修改內(nèi)部狀態(tài):
繼承雖然能Open for extension,但很難做到Closed for modification。所以今天解決OCP的主要方法是通過(guò)Composition-over-inheritance,即通過(guò)組合來(lái)做到擴(kuò)展性,而不是通過(guò)繼承。
Player.attack(monster) 還是 Monster.receiveDamage(Weapon, Player)?
在這個(gè)例子里,其實(shí)業(yè)務(wù)規(guī)則的邏輯到底應(yīng)該寫(xiě)在哪里是有異議的:當(dāng)我們?nèi)タ匆粋€(gè)對(duì)象和另一個(gè)對(duì)象之間的交互時(shí),到底是Player去攻擊Monster,還是Monster被Player攻擊?目前的代碼主要將邏輯寫(xiě)在Monster的類中,主要考慮是Monster會(huì)受傷降低Health,但如果是Player拿著一把雙刃劍會(huì)同時(shí)傷害自己呢?是不是發(fā)現(xiàn)寫(xiě)在Monster類里也有問(wèn)題?代碼寫(xiě)在哪里的原則是什么?
行為抽離
這個(gè)在游戲系統(tǒng)里有個(gè)比較明顯的優(yōu)勢(shì)。如果按照OOP的方式,一個(gè)游戲?qū)ο罄锟赡軙?huì)包括移動(dòng)代碼、戰(zhàn)斗代碼、渲染代碼、AI代碼等,如果都放在一個(gè)類里會(huì)很長(zhǎng),且很難去維護(hù)。通過(guò)將通用邏輯抽離出來(lái)為單獨(dú)的System類,可以明顯提升代碼的可讀性。另一個(gè)好處則是抽離了一些和對(duì)象代碼無(wú)關(guān)的依賴,比如上文的delta,這個(gè)delta如果是放在Entity的update方法,則需要作為入?yún)⒆⑷?,而放在System里則可以統(tǒng)一管理。在第一章的有個(gè)問(wèn)題,到底是應(yīng)該P(yáng)layer.attack(monster) 還是 Monster.receiveDamage(Weapon, Player)。在ECS里這個(gè)問(wèn)題就變的很簡(jiǎn)單,放在CombatSystem里就可以了。
攻擊行為
在上文中曾經(jīng)有提起過(guò),到底應(yīng)該是Player.attack(Monster)還是Monster.receiveDamage(Weapon, Player)?在DDD里,因?yàn)檫@個(gè)行為可能會(huì)影響到Player、Monster和Weapon,所以屬于跨實(shí)體的業(yè)務(wù)邏輯。在這種情況下需要通過(guò)一個(gè)第三方的領(lǐng)域服務(wù)(Domain Service)來(lái)完成。
public interface CombatService {
void performAttack(Player player, Monster monster);
}
public class CombatServiceImpl implements CombatService {
private WeaponRepository weaponRepository;
private DamageManager damageManager;
@Override
public void performAttack(Player player, Monster monster) {
Weapon weapon = weaponRepository.find(player.getWeaponId());
int damage = damageManager.calculateDamage(player, weapon, monster);
if (damage > 0) {
monster.takeDamage(damage); // (Note 1)在領(lǐng)域服務(wù)里變更Monster
}
// 省略掉Player和Weapon可能受到的影響
}
}
DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)總結(jié)
contextmapper-contextmap-d6cnufuknfn.ws-us30.gitpod.io
- 戰(zhàn)略設(shè)計(jì)階段:
限界上下文(Bounded Context)和上下文映射(Context Map),簡(jiǎn)單來(lái)講,對(duì)問(wèn)題域進(jìn)行劃分,此時(shí)可暫時(shí)簡(jiǎn)單認(rèn)為限界上下文就是微服務(wù).包含了一組高內(nèi)聚的服務(wù).需要根據(jù)耦合的強(qiáng)弱程度和業(yè)務(wù)相關(guān)性來(lái)劃分.
統(tǒng)一語(yǔ)言,同一個(gè)上下文中統(tǒng)一概念定義,保持各團(tuán)隊(duì)間準(zhǔn)確高效的溝通.這個(gè)很重要,是頭腦風(fēng)暴(事件風(fēng)暴)和后續(xù)溝通的基礎(chǔ).
聚合根的家——資源庫(kù)
通俗點(diǎn)講,資源庫(kù)(Repository)就是用來(lái)持久化聚合根的。從技術(shù)上講,Repository和DAO所扮演的角色相似,不過(guò)DAO的設(shè)計(jì)初衷只是對(duì)數(shù)據(jù)庫(kù)的一層很薄的封裝,而Repository是更偏向于領(lǐng)域模型。另外,在所有的領(lǐng)域?qū)ο笾?,只有聚合根才“配得上”擁有Repository,而DAO沒(méi)有這種約束。
https://zhuanlan.zhihu.com/p/75931257
DDD中的讀操作
軟件中的讀模型和寫(xiě)模型是很不一樣的,我們通常所講的業(yè)務(wù)邏輯更多的時(shí)候是在寫(xiě)操作過(guò)程中需要關(guān)注的東西,而讀操作更多關(guān)注的是如何向客戶方返回恰當(dāng)?shù)臄?shù)據(jù)展現(xiàn)。
在DDD的寫(xiě)操作中,我們需要嚴(yán)格地按照“應(yīng)用服務(wù) -> 聚合根 -> 資源庫(kù)”的結(jié)構(gòu)進(jìn)行編碼,而在讀操作中,采用與寫(xiě)操作相同的結(jié)構(gòu)有時(shí)不但得不到好處,反而使整個(gè)過(guò)程變得冗繁。這里介紹3種讀操作的方式:
基于領(lǐng)域模型的讀操作
基于數(shù)據(jù)模型的讀操作
CQRS
首先,無(wú)論哪種讀操作方式,都需要遵循一個(gè)原則:領(lǐng)域模型中的對(duì)象不能直接返回給客戶端,因?yàn)檫@樣領(lǐng)域模型的內(nèi)部便暴露給了外界,而對(duì)領(lǐng)域模型的修改將直接影響到客戶端。因此,在DDD中我們通常為讀操作專門(mén)創(chuàng)建相應(yīng)的模型用于數(shù)據(jù)展現(xiàn)。在寫(xiě)操作中,我們通過(guò)Command后綴進(jìn)行請(qǐng)求數(shù)據(jù)的統(tǒng)一,在讀操作中,我們通過(guò)Representation后綴進(jìn)行展現(xiàn)數(shù)據(jù)的統(tǒng)一,這里的Representation也即REST中的“R”。
基于領(lǐng)域模型的讀操作
這種方式將讀模型和寫(xiě)模型糅合到一起,先通過(guò)資源庫(kù)獲取到領(lǐng)域模型,然后將其轉(zhuǎn)換為Representation對(duì)象,這也是當(dāng)前被大量使用的方式,比如對(duì)于“獲取Order詳情的接口”,OrderApplicationService實(shí)現(xiàn)如下:
@Transactional(readOnly = true)
public OrderRepresentation byId(String id) {
Order order = orderRepository.byId(orderId(id));
return orderRepresentationService.toRepresentation(order);
}
我們先通過(guò)orderRepository.byId()獲取到Order聚合根對(duì)象,然后調(diào)用orderRepresentationService.toRepresentation()將Order轉(zhuǎn)換為展現(xiàn)對(duì)象OrderRepresentation,OrderRepresentationService.toRepresentation()實(shí)現(xiàn)如下:
public OrderRepresentation toRepresentation(Order order) {
List<OrderItemRepresentation> itemRepresentations = order.getItems().stream()
.map(orderItem -> new OrderItemRepresentation(orderItem.getProductId().toString(),
orderItem.getCount(),
orderItem.getItemPrice()))
.collect(Collectors.toList());
return new OrderRepresentation(order.getId().toString(),
itemRepresentations,
order.getTotalPrice(),
order.getStatus(),
order.getCreatedAt());
}
這種方式的優(yōu)點(diǎn)是非常直接明了,也不用創(chuàng)建新的數(shù)據(jù)讀取機(jī)制,直接使用Repository讀取數(shù)據(jù)即可。然而缺點(diǎn)也很明顯:一是讀操作完全束縛于聚合根的邊界劃分,比如,如果客戶端需要同時(shí)獲取Order及其所包含的Product,那么我們需要同時(shí)將Order聚合根和Product聚合根加載到內(nèi)存再做轉(zhuǎn)換操作,這種方式既繁瑣又低效;二是在讀操作中,通常需要基于不同的查詢條件返回?cái)?shù)據(jù),比如通過(guò)Order的日期進(jìn)行查詢或者通過(guò)Product的名稱進(jìn)行查詢等,這樣導(dǎo)致的結(jié)果是Repository上處理了太多的查詢邏輯,變得越來(lái)越復(fù)雜,也逐漸偏離了Repository本應(yīng)該承擔(dān)的職責(zé)。
基于數(shù)據(jù)模型的讀操作
這種方式繞開(kāi)了資源庫(kù)和聚合,直接從數(shù)據(jù)庫(kù)中讀取客戶端所需要的數(shù)據(jù),此時(shí)寫(xiě)操作和讀操作共享的只是數(shù)據(jù)庫(kù)。比如,對(duì)于“獲取Product列表”接口,通過(guò)一個(gè)專門(mén)的ProductRepresentationService直接從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù):
@Transactional(readOnly = true)
public PagedResource<ProductSummaryRepresentation> listProducts(int pageIndex, int pageSize) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("limit", pageSize);
parameters.addValue("offset", (pageIndex - 1) * pageSize);
List<ProductSummaryRepresentation> products = jdbcTemplate.query(SELECT_SQL, parameters,
(rs, rowNum) -> new ProductSummaryRepresentation(rs.getString("ID"),
rs.getString("NAME"),
rs.getBigDecimal("PRICE")));
int total = jdbcTemplate.queryForObject(COUNT_SQL, newHashMap(), Integer.class);
return PagedResource.of(total, pageIndex, products);
}
然后在Controller中直接返回:
@GetMapping
public PagedResource<ProductSummaryRepresentation> pagedProducts(@RequestParam(required = false, defaultValue = "1") int pageIndex,
@RequestParam(required = false, defaultValue = "10") int pageSize) {
return productRepresentationService.listProducts(pageIndex, pageSize);
}
可以看到,真?zhèn)€過(guò)程并沒(méi)有使用到ProductRepository和Product,而是將SQL獲取到的數(shù)據(jù)直接新建為ProductSummaryRepresentation對(duì)象。
這種方式的優(yōu)點(diǎn)是讀操作的過(guò)程不用囿于領(lǐng)域模型,而是基于讀操作本身的需求直接獲取需要的數(shù)據(jù)即可,一方面簡(jiǎn)化了整個(gè)流程,另一方面大大提升了性能。但是,由于讀操作和寫(xiě)操作共享了數(shù)據(jù)庫(kù),而此時(shí)的數(shù)據(jù)庫(kù)主要是對(duì)應(yīng)于聚合根的結(jié)構(gòu)創(chuàng)建的,因此讀操作依然會(huì)受到寫(xiě)操作的數(shù)據(jù)模型的牽制。不過(guò)這種方式是一種很好的折中,微軟也提倡過(guò)這種方式,更多細(xì)節(jié)請(qǐng)參考微軟官網(wǎng)。
CQRS
CQRS(Command Query Responsibility Segregation),即命令查詢職責(zé)分離,這里的命令可以理解為寫(xiě)操作,而查詢可以理解為讀操作。與“基于數(shù)據(jù)模型的讀操作”不同的是,在CQRS中寫(xiě)操作和讀操作使用了不同的數(shù)據(jù)庫(kù),數(shù)據(jù)從寫(xiě)模型數(shù)據(jù)庫(kù)同步到讀模型數(shù)據(jù)庫(kù),通常通過(guò)領(lǐng)域事件的形式同步變更信息。
(CQRS架構(gòu))
這樣一來(lái),讀操作便可以根據(jù)自身所需獨(dú)立設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu),而不用受寫(xiě)模型數(shù)據(jù)結(jié)構(gòu)的牽制。CQRS本身是一個(gè)很大的話題,已經(jīng)超出了本文的范圍,讀者可以自行研究。
關(guān)于倉(cāng)儲(chǔ)(Repository),你必須知道的幾個(gè)概念。
1,倉(cāng)儲(chǔ)的兩種設(shè)計(jì)方式:面向集合和面向持久化
面向集合和面向持久化,這兩種類型的倉(cāng)儲(chǔ)設(shè)計(jì)方式,在《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》中有很詳細(xì)的講解,作者還附帶了幾個(gè)具體的實(shí)現(xiàn),比如 Hibernate 實(shí)現(xiàn)、TopLink 實(shí)現(xiàn)等等,這個(gè)必須贊一個(gè),感興趣的朋友,可以進(jìn)行閱讀下。這面我簡(jiǎn)單說(shuō)明下,這兩種設(shè)計(jì)方式的不同之處,舉個(gè)最直白的例子。
面向集合方式:
this.UserRepository.Add(user);
面向持久化方式:
this.UserRepository.Save(user);
可能很多朋友看到這,會(huì)不以為然,需要明確一點(diǎn),在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中,不論是變量或是方法的命名規(guī)則都非常重要,因?yàn)槠浯a就是代表著一種通用語(yǔ)言,你要讓人家可以看懂。在面向集合方式中,新對(duì)象的添加使用的是 Add,而在面向持久化方式中,不論是新對(duì)象的添加或是修改,都是使用的 Save,如果是基于 Unit Of Work(工作單元),會(huì)有 Commit。
http://m.itdecent.cn/p/acb7c8026c21
DDD 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)學(xué)習(xí)(三)- 領(lǐng)域事件
Using the Specification pattern
https://enterprisecraftsmanship.com/posts/cqrs-vs-specification-pattern/
https://blog.csdn.net/sd7o95o/article/details/118864779
DDD戰(zhàn)略設(shè)計(jì)
https://github.com/ddd-crew/context-mapping
https://www.cnblogs.com/powercto/p/14020435.html
戰(zhàn)略設(shè)計(jì)工具
https://contextmapper.org/docs/online-ide/
DDD中問(wèn)題空間和解決方案空間是一個(gè)偽命題
示例
DDD-領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)示例
Context Mapper - Visual Studio Marketplace
Vscode
https://github.com/ContextMapper/context-mapper-examples
DDD as Code:如何用代碼詮釋領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)?-阿里云開(kāi)發(fā)者社區(qū)
Ouml
https://github.com/damonsk/onlinewardleymaps
https://blog.csdn.net/yunqiinsight/article/details/114691659
如花開(kāi)始DDD的第一步,也就是Subdomain的劃分。當(dāng)然DDD中包括三種類型的SubDomain,分別為通用(Generic)、支撐(Supporting)和核心(Core)三種類型,這里稍微說(shuō)明一下這幾者的區(qū)別:
通用(Generic) Domain: 通用Domain通常被認(rèn)為已經(jīng)被行業(yè)解決的問(wèn)題,如架構(gòu)設(shè)計(jì)中的可觀測(cè)性的Logging、Metrics和Tracing,各種云服務(wù)(Cloud Service)等,這些都已經(jīng)有比較好的實(shí)現(xiàn)方案,對(duì)接就可以。當(dāng)然業(yè)務(wù)上也有,如成熟的行業(yè)解決方案,如ERP、CRM、成熟硬件系統(tǒng)等,你購(gòu)買(mǎi)就可以啦。
支撐(Supporting) Domain:和通用Domain類似,但是系統(tǒng)更來(lái)自內(nèi)部或者還需要在通用的基礎(chǔ)上進(jìn)行一些定制開(kāi)發(fā)。如一個(gè)電商系統(tǒng),會(huì)員、商品、訂單、物流等業(yè)務(wù)系統(tǒng),當(dāng)然還有一些內(nèi)部開(kāi)發(fā)的技術(shù)類型支撐系統(tǒng)。
核心(Core) Domain: 也就是我們常說(shuō)的業(yè)務(wù)核心,當(dāng)然如果是技術(shù)產(chǎn)品,就是技術(shù)核心,這個(gè)也就是你最要關(guān)注的。
這三者整體關(guān)系如下:Core是最與眾不同且花費(fèi)精力比較多的,在復(fù)雜性Y維度,我們要避免高復(fù)雜度的通用和支撐Domain,這樣會(huì)分散你的注意力,同時(shí)還要投入非常大的精力,如果確實(shí)需要,購(gòu)買(mǎi)服務(wù)的方式可能最佳。
工程架構(gòu)
Testing Strategies in a Microservice Architecture
Ddd 戰(zhàn)略設(shè)計(jì)
Ddd 戰(zhàn)術(shù)設(shè)計(jì)
https://contextmapper.org/docs/tactic-ddd/
http://sculptorgenerator.org/documentation/
http://sculptorgenerator.org/documentation/advanced-tutorial
Ddd 語(yǔ)法
https://contextmapper.org/docs/tactic-ddd/
https://contextmapper.org/docs/plant-uml/
Dot demo
https://www.planttext.com/ plattext 也可以畫(huà) graphviz dot
http://bj.91join.com/color.html 在線配色
http://magjac.com/graphviz-visual-editor/ 在線可視化
https://graphviz.org/gallery/ dot 示例
https://www.cnblogs.com/itzxy/p/7860165.html
DSL
https://martinfowler.com/books/dsl.html
http://sculptorgenerator.org/documentation/overview#introduction
http://sculptorgenerator.org/documentation/ddd-sample
BPMN-SKETCH 可視化
https://www.bpmn-sketch-miner.ai/
使用DOT語(yǔ)言和Graphviz繪圖(翻譯) - zxyblog - 博客園
#http://bj.91join.com/color.html
digraph G1 {
rankdir=TB;
graph [compound=true]
node [color=black,shape=egg fillcolor="#FFFFFF" style="filled" shape=egg fontcolor="#000000"] //All nodes will this shape and colour
edge [color=black] //All the lines look like this
subgraph cluster_basic {
fillcolor="#FFAB00"
fontcolor="white"
style="filled"
label = "Infrastructure Layer";
SpringBoot[shape=egg]
Dubbo[shape=egg]
#_[color="white" fontcolor="#ffffff"]
ElasticSearchRepositories[shape=egg]
RedisRepositories[shape=egg]
};
subgraph cluster_domain {
fillcolor="#01939A"
style="filled"
fontcolor="white"
label = "Domain Layer"
ENTITY[shape=egg]
VALUE_OBJECT[shape=egg]
DOMAIN_SERVICE[shape=egg]
Repositories[shape=egg]
};
subgraph cluster_protocol{
fillcolor="#9FEE00"
fontcolor="white"
style="filled"
label = "Protocol Layer"
Protocol[shape=egg]
}
subgraph cluster_app{
fillcolor="#9FEE00"
fontcolor="white"
style="filled"
label = "Application Layer"
{rank="same";Valid;Shuffler;Merge;ReRank;Rank;Filter;Recall;}
Recall->Filter->Rank->ReRank->Merge->Shuffler->Valid
}
subgraph cluster_commons{
fillcolor="#CD0074"
fontcolor="white"
style="filled"
label = "Commons"
commons[fillcolor="white"
style=filled shape=egg]
}
SpringBoot -> Shuffler[label=" " ltail=cluster_basic lhead=cluster_app]
Shuffler -> DOMAIN_SERVICE [label=" " lhead=cluster_domain ltail=cluster_app];
Shuffler -> Protocol [label=" " ltail=cluster_app];
Repositories->commons[label=" " ltail=cluster_domain];
ElasticSearchRepositories-> Repositories
[label="DI" fontcolor="#9FEE00" style="dotted"];
RedisRepositories-> Repositories
[label="DI" style="dotted" fontcolor="#9FEE00"];
}