【系统架构】领域驱动DDD(Domain-Driven Design)- 软件核心复杂性应对之道
前言
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)是一個(gè)開放的設(shè)計(jì)方法體系,目的是對(duì)軟件所涉及到的領(lǐng)域進(jìn)行建模,以應(yīng)對(duì)系統(tǒng)規(guī)模過大時(shí)引起的軟件復(fù)雜性的問題,本文將介紹領(lǐng)域驅(qū)動(dòng)的相關(guān)概念。
一.軟件復(fù)雜度的根源
1.業(yè)務(wù)復(fù)雜度(軟件的規(guī)模)
軟件的需求決定了系統(tǒng)的規(guī)模。當(dāng)需求呈現(xiàn)線性增長的趨勢(shì)時(shí),為了實(shí)現(xiàn)這些功能,軟件規(guī)模也會(huì)以近似的速度增長。由于需求不可能做到完全獨(dú)立,導(dǎo)致出現(xiàn)相互影響相互依賴的關(guān)系,修改一處就會(huì)牽一發(fā)而動(dòng)全身。就好似城市的一條道路因?yàn)槭┕ば枰R時(shí)關(guān)閉,此路不通,通行的車輛只能改道繞行,這又導(dǎo)致了其他原本已經(jīng)飽和的道路,因?yàn)橛咳敫嘬囕v,超出道路的負(fù)載從而變得更加擁堵,這種擁堵現(xiàn)象又會(huì)順勢(shì)向這些道路的其他分叉道路蔓延,形成一種輻射效應(yīng)的擁堵現(xiàn)象。
2.技術(shù)復(fù)雜度(軟件的結(jié)構(gòu))
結(jié)構(gòu)之所以變得復(fù)雜,在多數(shù)情況下還是因?yàn)橄到y(tǒng)的質(zhì)量屬性決定的。例如,我們需要滿足高性能、高并發(fā)的需求,就需要考慮在系統(tǒng)中引入緩存、并行處理、CDN、異步消息以及支持分區(qū)的可伸縮結(jié)構(gòu)。倘若我們需要支持對(duì)海量數(shù)據(jù)的高效分析,就得考慮這些海量數(shù)據(jù)該如何分布存儲(chǔ),并如何有效地利用各個(gè)節(jié)點(diǎn)的內(nèi)存與 CPU 資源執(zhí)行運(yùn)算。
從系統(tǒng)結(jié)構(gòu)的視角看,單體架構(gòu)一定比微服務(wù)架構(gòu)更簡單。
3.人為的因素
不存在一致性、不存在風(fēng)格、也沒有統(tǒng)一的概念能夠?qū)⒉煌牟糠纸M織在一起。缺少必要的注釋,沒有字段說明和數(shù)據(jù)字典,意大利面條式的代碼,缺乏統(tǒng)一編碼風(fēng)格,導(dǎo)致錯(cuò)綜復(fù)雜和不可維護(hù)的程序。
4.需求引起的軟件復(fù)雜度
需求分為業(yè)務(wù)需求與質(zhì)量屬性需求,因而需求引起的復(fù)雜度可以分為兩個(gè)方面:技術(shù)復(fù)雜度與業(yè)務(wù)復(fù)雜度。
技術(shù)復(fù)雜度來自需求的質(zhì)量屬性,諸如安全、高性能、高并發(fā)、高可用性等需求,為軟件設(shè)計(jì)帶來了極大的挑戰(zhàn),讓人痛苦的是這些因素彼此之間可能又互相矛盾、互相影響。例如,系統(tǒng)安全性要求對(duì)訪問進(jìn)行控制,無論是增加防火墻,還是對(duì)傳遞的消息進(jìn)行加密,又或者對(duì)訪問請(qǐng)求進(jìn)行認(rèn)證和授權(quán)等,都需要為整個(gè)系統(tǒng)架構(gòu)添加額外的間接層,這不可避免會(huì)對(duì)訪問的低延遲產(chǎn)生影響,拖慢了系統(tǒng)的整體性能。又例如,為了滿足系統(tǒng)的高并發(fā)訪問,我們需要對(duì)應(yīng)用服務(wù)進(jìn)行物理分解,通過橫向增加更多的機(jī)器來分散訪問負(fù)載;同時(shí),還可以將一個(gè)同步的訪問請(qǐng)求拆分為多級(jí)步驟的異步請(qǐng)求,再通過引入消息中間件對(duì)這些請(qǐng)求進(jìn)行整合和分散處理。這種分離一方面增加了系統(tǒng)架構(gòu)的復(fù)雜性,另一方面也因?yàn)橐肓烁嗟馁Y源,使得系統(tǒng)的高可用面臨挑戰(zhàn),并增加了維護(hù)數(shù)據(jù)一致性的難度。
業(yè)務(wù)復(fù)雜度對(duì)應(yīng)了客戶的業(yè)務(wù)需求,因而這種復(fù)雜度往往會(huì)隨著需求規(guī)模的增大而增加。由于需求不可能做到完全獨(dú)立,一旦規(guī)模擴(kuò)大到一定程度,不僅產(chǎn)生了功能數(shù)量的增加,還會(huì)因?yàn)楣δ芑ハ嘀g的依賴與影響使得這種復(fù)雜度產(chǎn)生疊加,進(jìn)而影響到整個(gè)系統(tǒng)的質(zhì)量屬性,比如系統(tǒng)的可維護(hù)性與可擴(kuò)展性。在考慮系統(tǒng)的業(yè)務(wù)需求時(shí),還會(huì)因?yàn)闇贤ú粫场⒖蛻粜枨蟛磺逦榷喾N局外因素而帶來的需求變更和修改。如果不能很好地控制這種變更,則可能會(huì)因?yàn)槎啻涡薷亩鴮?dǎo)致業(yè)務(wù)邏輯糾纏不清,系統(tǒng)可能開始慢慢腐爛而變得不可維護(hù),最終形成一種如 Brian Foote 和 Joseph Yoder 所說的“大泥球”系統(tǒng)。
以電商系統(tǒng)的促銷規(guī)則為例。針對(duì)不同類型的顧客與產(chǎn)品,商家會(huì)提供不同的促銷力度;促銷的形式多種多樣,包括贈(zèng)送積分、紅包、優(yōu)惠券、禮品;促銷的周期需要支持定制,既可以是特定的日期,如雙十一促銷,也可以是節(jié)假日的固定促銷模式。如果我們?cè)谠O(shè)計(jì)時(shí)沒有充分考慮促銷規(guī)則的復(fù)雜度,并處理好促銷規(guī)則與商品、顧客、賣家與支付乃至于物流、倉儲(chǔ)之間的關(guān)系,開發(fā)過程則會(huì)變得踉踉蹌蹌、舉步維艱。
技術(shù)復(fù)雜度與業(yè)務(wù)復(fù)雜度并非完全獨(dú)立,二者混合在一起產(chǎn)生的化合作用更讓系統(tǒng)的復(fù)雜度變得不可預(yù)期,難以掌控。
二.控制軟件復(fù)雜度的原則
1.保持結(jié)構(gòu)的清晰與一致.
2.分而治之、控制規(guī)模
3.擁抱變化(變化對(duì)軟件系統(tǒng)帶來的影響可以說是無解)
除了在開發(fā)過程中,我們應(yīng)盡可能做到敏捷與快速迭代,以此來抵消變化帶來的影響;在架構(gòu)設(shè)計(jì)層面,我們還可以分析哪些架構(gòu)質(zhì)量屬性與變化有關(guān),這些質(zhì)量屬性包括:
可進(jìn)化性(Evolvability):有句話這么說的好的架構(gòu)是進(jìn)化來的,不是設(shè)計(jì)出來的。
可擴(kuò)展性(Extensibility)
三.傳統(tǒng)開發(fā)設(shè)計(jì)的模式
1.傳統(tǒng)的設(shè)計(jì)分層結(jié)構(gòu):
Model層:
包含數(shù)據(jù)對(duì)象,是service操縱的對(duì)象,model層中的對(duì)象被建模成業(yè)務(wù)對(duì)象,這些對(duì)象是對(duì)DB中表的映射,一個(gè)表對(duì)應(yīng)一個(gè)model,表中的字段就對(duì)應(yīng)成model對(duì)象的屬性,然后在加上get() / set()方法,但是并沒有包含這個(gè)對(duì)象的業(yè)務(wù)上的行為,不知道它會(huì)做什么,這樣就是一個(gè)很典型的貧血模式。
Dao層(數(shù)據(jù)訪問層,DTO對(duì)象:數(shù)據(jù)傳輸對(duì)象):
Dao層主要是和數(shù)據(jù)庫打交道,做數(shù)據(jù)持久化的工作,也包括一些數(shù)據(jù)過濾,為model層服務(wù)的,比如php里面的mysqli和pdo。
Service層:
公開一些接口給外部服務(wù)調(diào)用的,放置所有的服務(wù)類,它會(huì)調(diào)用Dao層去處理數(shù)據(jù)(獲取設(shè)置數(shù)據(jù))。
展現(xiàn)層(UI):
前端的一些業(yè)務(wù)邏輯展現(xiàn),使用各種UI框架,如Layzui,smarty,twing等模版js框架去渲染頁面。
Controller層:
層負(fù)責(zé)具體的業(yè)務(wù)模塊流程的控制,在這層調(diào)用可以調(diào)用service層的接口來控制業(yè)務(wù)流程,也可以訪問model層獲取數(shù)據(jù)。
現(xiàn)在主流的php框架都是按照這樣的分層去設(shè)計(jì)和開發(fā),thinkphp,laveral,ci。
2.傳統(tǒng)的設(shè)計(jì)方式和開發(fā)框架及其問題
1.由于設(shè)計(jì)或者編碼的不當(dāng),核心業(yè)務(wù)邏輯容易散布在各處。
由于業(yè)務(wù)邏輯混散在各處,帶來的麻煩維護(hù)很困難,有可能在model層 service層做一些業(yè)務(wù)方面的東西,或者在action里面寫一些業(yè)務(wù)相關(guān)的代碼,比如有些業(yè)務(wù)寫在展現(xiàn)層,那么就要去改展現(xiàn)層里面的代碼,寫在service層就要去改service的邏輯。當(dāng)想要了解這里業(yè)務(wù)邏輯的時(shí)候得看下上下文,翻閱很多類,需要追代碼各個(gè)文件去看才能大概的明白。
2.過度耦合
業(yè)務(wù)初期,我們的功能大都非常簡單,普通的CRUD就能滿足,此時(shí)系統(tǒng)是清晰的。隨著迭代的不斷演化,業(yè)務(wù)邏輯變得越來越復(fù)雜,我們的系統(tǒng)也越來越冗雜。模塊彼此關(guān)聯(lián),誰都很難說清模塊的具體功能意圖是啥。修改一個(gè)功能時(shí),往往光回溯該功能需要的修改點(diǎn)就需要很長時(shí)間,更別提修改帶來的不可預(yù)知的影響面。
下圖是一個(gè)常見的系統(tǒng)耦合病例。
訂單服務(wù)接口中提供了查詢、創(chuàng)建訂單相關(guān)的接口,也提供了訂單評(píng)價(jià)、支付、保險(xiǎn)的接口。同時(shí)我們的表也是一個(gè)訂單大表,包含了非常多字段。在我們維護(hù)代碼時(shí),牽一發(fā)而動(dòng)全身,很可能只是想改下評(píng)價(jià)相關(guān)的功能,卻影響到了創(chuàng)單核心路徑。雖然我們可以通過測(cè)試保證功能完備性,但當(dāng)我們?cè)谟唵晤I(lǐng)域有大量需求同時(shí)并行開發(fā)時(shí),改動(dòng)重疊、惡性循環(huán)、疲于奔命修改各種問題。
上述問題,歸根到底在于系統(tǒng)架構(gòu)不清晰,劃分出來的模塊內(nèi)聚度低、高耦合。
問題:既然架構(gòu)不清晰重構(gòu)是否可以解決這些問題?
可以,但是并不能解決根本問題。一般重構(gòu)都是通過在單獨(dú)的類及方法級(jí)別上做一系列小步重構(gòu)來完成,封裝一些常用的操作,提煉出通用的代碼片段。所以我們可以很容易重構(gòu)出一個(gè)獨(dú)立的類來放某些通用的邏輯,但是你會(huì)發(fā)現(xiàn)你很難給它一個(gè)業(yè)務(wù)上的含義,只能給予一個(gè)技術(shù)維度描繪的含義。這會(huì)帶來什么問題呢?新來的同事并不總是知道對(duì)通用邏輯的改動(dòng)或獲取來自該類。顯然,制定項(xiàng)目規(guī)范并不是好的方法,隨著業(yè)務(wù)的變化在不久的將來重構(gòu)還會(huì)一直繼續(xù)下去。
3.貧血模式,基于數(shù)據(jù)表的設(shè)計(jì),數(shù)據(jù)驅(qū)動(dòng)(Data-Driven),所有的開發(fā)都是圍繞數(shù)據(jù)表來進(jìn)行的。
四.貧血模型
貧血領(lǐng)域?qū)ο螅ˋnemic Domain Object)是指僅用作數(shù)據(jù)的載體,而沒有行為和動(dòng)作的領(lǐng)域?qū)ο螅挥術(shù)et和set方法,或者包含少量的CRUD方法,所有的業(yè)務(wù)邏輯都不包含在內(nèi)。
貧血模型其實(shí)是違背了oop模式,對(duì)象有什么反應(yīng)的就是屬性,對(duì)象會(huì)做什么,反應(yīng)在類里面對(duì)應(yīng)的就是方法,傳統(tǒng)開發(fā)中很明顯看不到能做什么,不會(huì)有業(yè)務(wù)行為,get / set方式只是外部獲取屬性值的載體而已。
數(shù)據(jù)庫有什么,模型才會(huì)反應(yīng)什么,這樣迫使我們先去設(shè)計(jì)數(shù)據(jù)庫,這就是傳統(tǒng)的開發(fā)思路,這就是基于數(shù)據(jù)庫表的設(shè)計(jì),導(dǎo)致的問題就是表中會(huì)有一些重復(fù)的多余的字段,在設(shè)計(jì)的時(shí)候也會(huì)依賴于的特定的數(shù)據(jù)庫(因?yàn)槭窍热ピO(shè)計(jì)數(shù)據(jù)庫),所以好的系統(tǒng)是不應(yīng)該依賴于特定的數(shù)據(jù)庫,更不應(yīng)該依賴于特定數(shù)據(jù)庫的存儲(chǔ)過程,存儲(chǔ)函數(shù),觸發(fā)器等,做到數(shù)據(jù)庫無關(guān)性,當(dāng)做到數(shù)據(jù)遷移等時(shí)候很方便很平滑。所以應(yīng)該先去設(shè)計(jì)業(yè)務(wù)對(duì)象,就是對(duì)對(duì)象建模然后再去反向設(shè)計(jì)數(shù)據(jù)庫。
缺點(diǎn):
1.溝通困難,開發(fā)人員和業(yè)務(wù)人員交流語言不統(tǒng)一,開發(fā)用技術(shù)語言和業(yè)務(wù)溝通,而業(yè)務(wù)人員不了解技術(shù),就是交流障礙。
2.業(yè)務(wù)邏輯不能重用,因?yàn)闃I(yè)務(wù)散在各個(gè)層,業(yè)務(wù)各個(gè)方法互相調(diào)用,你不知道調(diào)用哪個(gè)方法,業(yè)務(wù)后期的查找維護(hù)也比較困難,業(yè)務(wù)邏輯也會(huì)和應(yīng)用邏輯的混合,業(yè)務(wù)邏輯反應(yīng)的是需求,應(yīng)用邏輯是和系統(tǒng)相關(guān),比如業(yè)務(wù)要查詢什么數(shù)據(jù),查詢的過程是業(yè)務(wù),而如何展現(xiàn)在ui上是應(yīng)用。
3.傳統(tǒng)的開發(fā)是和特點(diǎn)的技術(shù)耦合的,如果想把業(yè)務(wù)脫離出來,去更換某種技術(shù)就很困難。
4.適應(yīng)未來的變化就很有問題。
優(yōu)點(diǎn):
這種貧血模型的傳統(tǒng)開發(fā)也是當(dāng)前我們最常用的方法,開發(fā)速度快,開發(fā)人員容易掌握。
// 貧血模型下的實(shí)現(xiàn)
public class User{
private $id;
private $name;
...
// 省略get/set方法
}
public class UserManagerService{
public function save(User user){
// 持久化操作....
}
}
// 保存用戶的操作可能是這樣
$userManagerService::getInstance()->save(user);
我的業(yè)務(wù)邏輯都是寫在userManagerService中的,User只是個(gè)數(shù)據(jù)載體,沒有任何行為。簡單的業(yè)務(wù)系統(tǒng)采用這種貧血模型和過程化設(shè)計(jì)是沒有問題的,但在業(yè)務(wù)邏輯復(fù)雜了,業(yè)務(wù)邏輯、狀態(tài)會(huì)散落到在大量方法中,原本的代碼意圖會(huì)漸漸不明確,我們將這種情況稱為貧血模型或者是由貧血癥引起的失憶癥。
// 充血模型下的實(shí)現(xiàn)
public class User{
private $id;
private $name;
...
// 省略get/set方法
//用戶信息保存
public function save(User user){
// 持久化操作....
}
}
// 保存用戶的操作可能是這樣
$User::getInstance()->save(user);
更好的是采用領(lǐng)域模型的開發(fā)方式,將數(shù)據(jù)和行為封裝在一起,并與現(xiàn)實(shí)世界中的業(yè)務(wù)對(duì)象相映射。各類具備明確的職責(zé)劃分,將領(lǐng)域邏輯分散到領(lǐng)域?qū)ο笾小@^續(xù)舉我們上述的例子,用戶保存信息就應(yīng)當(dāng)放到User類中。
五.為什么選擇DDD
既然上述傳統(tǒng)開發(fā)和貧血模式有這些問題,那么有沒有什么方法來解決這些問題?
解決思路
1.思想理論:基于領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD能應(yīng)對(duì)復(fù)雜性與快速變化)。
2.技術(shù)實(shí)現(xiàn):
1).從技術(shù)維度實(shí)現(xiàn)分層:遵循分層架構(gòu)模式,能夠在每層關(guān)注自己的事情,比如領(lǐng)域?qū)雨P(guān)注業(yè)務(wù)邏輯的事情,倉儲(chǔ)關(guān)注持久化數(shù)據(jù)的事情,應(yīng)用服務(wù)層關(guān)注用例的事情,接口層關(guān)注暴露給前端的事情。通過‘開發(fā)主機(jī)服務(wù)’(REST服務(wù)是其中的一種)、消息模式、事件驅(qū)動(dòng) 等架構(gòu)風(fēng)格實(shí)現(xiàn).
2).業(yè)務(wù)維度:通過將大系統(tǒng)劃分層多個(gè)上下文,關(guān)注點(diǎn)放在domain上,將業(yè)務(wù)領(lǐng)域限定在同一上下文中,可以讓不同團(tuán)隊(duì)和不同人只關(guān)注當(dāng)前上下文的開發(fā)。降低上下文之間的依賴,業(yè)務(wù)核心與特定的技術(shù)隔離開來,不依賴任何一個(gè)技術(shù)框架。
六.理解DDD概念
DDD的全稱為Domain-driven Design,即領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。是一種思維方式和概念,可以應(yīng)用在處理復(fù)雜業(yè)務(wù)的軟件項(xiàng)目中,加快項(xiàng)目的交付速度。下面我從領(lǐng)域、問題域、領(lǐng)域模型、設(shè)計(jì)、驅(qū)動(dòng)這幾個(gè)詞語的含義和聯(lián)系的角度去闡述DDD是如何融入到我們平時(shí)的軟件開發(fā)初期階段的。要理解什么是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),首先要理解什么是領(lǐng)域,什么是設(shè)計(jì),還有驅(qū)動(dòng)是什么意思,什么驅(qū)動(dòng)什么。
1.什么是領(lǐng)域
領(lǐng)域代表的是某個(gè)范圍,假如現(xiàn)在要做一個(gè)系統(tǒng),這個(gè)系統(tǒng)有一些要實(shí)現(xiàn)的功能。那么這個(gè)系統(tǒng)肯定屬于某個(gè)特定的領(lǐng)域,比如論壇是一個(gè)領(lǐng)域,只要你想做一個(gè)論壇,那這個(gè)論壇的核心業(yè)務(wù)是確定的,比如都有用戶發(fā)帖、回帖等核心基本功能。比如電商系統(tǒng),這種都屬于網(wǎng)上電商領(lǐng)域,只要是這個(gè)領(lǐng)域的系統(tǒng),那都有商品瀏覽、購物車、下單、減庫存、付款交易,物流等核心環(huán)節(jié),或者一個(gè)支付平臺(tái)等。所以,同一個(gè)領(lǐng)域的系統(tǒng)都具有相同的核心業(yè)務(wù),因?yàn)樗麄円鉀Q的問題的本質(zhì)是類似的。
因此,我們可以推斷出,一個(gè)領(lǐng)域本質(zhì)上可以理解為就是一個(gè)問題域,只要是同一個(gè)領(lǐng)域,那問題域就相同。所以,只要我們確定了系統(tǒng)所屬的領(lǐng)域,那這個(gè)系統(tǒng)的核心業(yè)務(wù),即要解決的關(guān)鍵問題、問題的范圍邊界就基本確定了。通常我們說,要成為一個(gè)領(lǐng)域的專家,必須要在這個(gè)領(lǐng)域深入研究很多年才行。因?yàn)橹挥心阊芯苛撕芏嗄辏悴艜?huì)遇到非常多的該領(lǐng)域的問題,同時(shí)你解決這個(gè)領(lǐng)域中的問題的經(jīng)驗(yàn)也非常豐富。領(lǐng)域?qū)<业闹匾詫?duì)于設(shè)計(jì)良好的領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)是很重要的。在開發(fā)前理解領(lǐng)域知識(shí)是基礎(chǔ),也很重要,因?yàn)橐粋€(gè)系統(tǒng)要做成什么樣,里面包含哪些業(yè)務(wù)規(guī)則,核心業(yè)務(wù)關(guān)注點(diǎn)是什么,就要求對(duì)這個(gè)領(lǐng)域內(nèi)的一切業(yè)務(wù)相關(guān)的知識(shí)都非常了解,如果開發(fā)一個(gè)陌生的系統(tǒng),比如航空管理軟件,讓一個(gè)只會(huì)開發(fā)電商的程序員去寫,是完全不知道從哪開始下手。
在日常開發(fā)中,我們通常會(huì)將一個(gè)大型的軟件系統(tǒng)拆分成若干個(gè)子系統(tǒng)。這種劃分有可能是基于架構(gòu)方面的考慮,也有可能是基于基礎(chǔ)設(shè)施的。但是在DDD中,我們對(duì)系統(tǒng)的劃分是基于領(lǐng)域的,也即是基于業(yè)務(wù)的,領(lǐng)域的劃分,一個(gè)大的領(lǐng)域可以劃分成多個(gè)小的領(lǐng)域,也就是子域。
領(lǐng)域及子域的劃分是如何進(jìn)行的,如何去限定的,這個(gè)就得需要限界上下文和上下文映射圖,下面待會(huì)說。
2.什么是設(shè)計(jì)
DDD中的設(shè)計(jì)主要指領(lǐng)域模型的設(shè)計(jì)。為什么是領(lǐng)域模型的設(shè)計(jì)而不是架構(gòu)設(shè)計(jì)或其他的什么設(shè)計(jì)呢?因?yàn)镈DD是一種基于模型驅(qū)動(dòng)開發(fā)的軟件開發(fā)思想,強(qiáng)調(diào)領(lǐng)域模型是整個(gè)系統(tǒng)的核心,領(lǐng)域模型也是整個(gè)系統(tǒng)的核心價(jià)值所在。每一個(gè)領(lǐng)域,都有一個(gè)對(duì)應(yīng)的領(lǐng)域模型,領(lǐng)域模型能夠很好的幫我們解決復(fù)雜的業(yè)務(wù)問題。
從領(lǐng)域和代碼實(shí)現(xiàn)的角度來理解,領(lǐng)域模型綁定了領(lǐng)域和代碼實(shí)現(xiàn),確保了最終的代碼實(shí)現(xiàn)就一定是解決了領(lǐng)域中的核心問題的。因?yàn)椋?)領(lǐng)域驅(qū)動(dòng)領(lǐng)域模型設(shè)計(jì);2)領(lǐng)域模型驅(qū)動(dòng)代碼實(shí)現(xiàn)。我們只要保證領(lǐng)域模型的設(shè)計(jì)是正確的,就能確定領(lǐng)域模型可以解決領(lǐng)域中的核心問題;同理,我們只要保證代碼實(shí)現(xiàn)是嚴(yán)格按照領(lǐng)域模型的意圖來落地的,那就能保證最后出來的代碼能夠解決領(lǐng)域的核心問題的。這個(gè)思路,和傳統(tǒng)的分析、設(shè)計(jì)、編碼這幾個(gè)階段被割裂(并且每個(gè)階段的產(chǎn)物也不同)的軟件開發(fā)方法學(xué)形成鮮明的對(duì)比。
3.什么是驅(qū)動(dòng)
上面其實(shí)已經(jīng)提到了,就是:1)領(lǐng)域驅(qū)動(dòng)領(lǐng)域模型設(shè)計(jì);2)領(lǐng)域模型驅(qū)動(dòng)代碼實(shí)現(xiàn)。這個(gè)就和我們傳統(tǒng)的數(shù)據(jù)庫驅(qū)動(dòng)開發(fā)的思路形成對(duì)比了。DDD中,我們總是以領(lǐng)域?yàn)檫吔纾治鲱I(lǐng)域中的核心問題(核心關(guān)注點(diǎn)),然后設(shè)計(jì)對(duì)應(yīng)的領(lǐng)域模型,再通過領(lǐng)域模型驅(qū)動(dòng)代碼實(shí)現(xiàn)。而像數(shù)據(jù)庫設(shè)計(jì)、持久化技術(shù)等這些都不是DDD的核心,而是外圍的東西。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)第一步最關(guān)鍵就是應(yīng)該盡量先把領(lǐng)域模型想清楚,然后再開始動(dòng)手編碼,這樣的系統(tǒng)后期才會(huì)很好維護(hù)。但是,很多項(xiàng)目(尤其是互聯(lián)網(wǎng)項(xiàng)目,為了趕工)都是一開始模型沒想清楚,一上來就開始建表寫代碼,代碼寫的非常冗余,完全是過程是的思考方式,最后導(dǎo)致系統(tǒng)非常難以維護(hù)。而且更糟糕的是,前期的領(lǐng)域模型設(shè)計(jì)的不好,不夠抽象,如果你的系統(tǒng)會(huì)長期需要維護(hù)和適應(yīng)業(yè)務(wù)變化,那后面你一定會(huì)遇到各種問題維護(hù)上的困難,比如數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)不合理,代碼到處冗余,改BUG到處引入新的BUG,新人對(duì)這種代碼上手困難等。而那時(shí)如果你再想重構(gòu)模型,那要付出的代價(jià)會(huì)比一開始重新開發(fā)還要大,因?yàn)槟氵€要考慮兼容歷史的數(shù)據(jù),數(shù)據(jù)遷移,如何平滑發(fā)布等各種頭疼的問題。
所以通過建立領(lǐng)域模型來解決領(lǐng)域中的核心問題,這就是模型驅(qū)動(dòng)的思想。
七.DDD核心組件
1.通用語言
形成統(tǒng)一的領(lǐng)域術(shù)語,尤其是基于模型的語言概念,是溝通能夠達(dá)成一致的前提。尤其是開發(fā)人員與領(lǐng)域?qū)<抑g,他們掌握的知識(shí)存在巨大的差異,尤其是專業(yè)性很強(qiáng)的業(yè)務(wù),比如金融系統(tǒng),醫(yī)療系統(tǒng),它們的術(shù)語都很專業(yè)。而善于技術(shù)的開發(fā)人員關(guān)注于數(shù)據(jù)庫、通信機(jī)制、集成方式與架構(gòu)體系,而精通業(yè)務(wù)的領(lǐng)域?qū)<覍?duì)這些卻一竅不通,但他們?cè)谥v解業(yè)務(wù)知識(shí)時(shí),非常自然,這些對(duì)于開發(fā)人員來說,卻成了天書,這種交流就好似使用兩種不同語言的外國人在交談。
使用統(tǒng)一語言可以幫助我們將參與討論的客戶、領(lǐng)域?qū)<遗c開發(fā)團(tuán)隊(duì)拉到同一個(gè)維度空間進(jìn)行討論,若沒有達(dá)成這種一致性,那就是雞同鴨講,毫無溝通效率,相反還可能造成誤解。因此,在溝通需求時(shí),團(tuán)隊(duì)中的每個(gè)人都應(yīng)使用統(tǒng)一語言進(jìn)行交流。
一旦確定了統(tǒng)一語言,無論是與領(lǐng)域?qū)<业挠懻摚€是最終的實(shí)現(xiàn)代碼,都可以通過使用相同的術(shù)語,清晰準(zhǔn)確地定義領(lǐng)域知識(shí)。重要的是,當(dāng)我們建立了符合整個(gè)團(tuán)隊(duì)皆認(rèn)同的一套統(tǒng)一語言后,就可以在此基礎(chǔ)上尋找正確的領(lǐng)域概念,為建立領(lǐng)域模型提供重要參考。
2.界限上下文(Bounded Contexts):
界限上下文是DDD中的一個(gè)核心模式,這種模式是幫助我們剝離開復(fù)雜的應(yīng)用程序,將他們隔離開,形成不同的上下文邊界。不同的模塊有著不同的上下文,且能獨(dú)立調(diào)用,而各自的模塊可以有自己的持久化的,互不干擾。
在大型的應(yīng)用程序中,不同的人對(duì)不同的的東西可能取相同的名字,這跟我們程序的類一樣,為何我們要在外面放一個(gè)namespace在外面,其實(shí)也是形成一個(gè)邊界。程序內(nèi)部也是如此。例如,售樓部內(nèi)的員工把商品房認(rèn)為是產(chǎn)品(Product);但是在工程部,可能他們把刷灰和修理管道的服務(wù)叫做產(chǎn)品,為了消除這些歧義,可以定義一個(gè)邊界,分離開兩種情況,以免在系統(tǒng)內(nèi)產(chǎn)生混淆。每個(gè)界限上下文根據(jù)特點(diǎn),具體實(shí)現(xiàn)方式又不同,比如有些界限上下文基本沒有業(yè)務(wù)邏輯,就是增刪改查,則可以使用CRUD最簡單的模式;有些界限上線文有一定的業(yè)務(wù)邏輯,但對(duì)高并發(fā)、高性能沒要求,則可以使用經(jīng)典DDD模式。有些界限上下文有一定的業(yè)務(wù)邏輯,而且有高性能要求,則可以使CQRS模式(命令查詢職責(zé)分離(Command Query Responsibility Segregation,簡稱CQRS))。
上面這張圖展示了界限上下文
舉個(gè)例子,比如電商系統(tǒng)中訂單模塊的上下文有商品,物流模塊上下文有貨物,庫存模塊上下文有存貨等等,這時(shí)候你會(huì)發(fā)現(xiàn)其實(shí)他們都是指的同一個(gè)東西,只不過在不同的上下文中被人為的賦予了不同的概念。這樣是不是就更好理解界限上下文了。
3.實(shí)體:
有業(yè)務(wù)生命周期,采用業(yè)務(wù)標(biāo)識(shí)符進(jìn)行跟蹤。比如一個(gè)訂單就是實(shí)體,訂單有生命周期的,而且有一個(gè)訂單號(hào)唯一的標(biāo)識(shí)它自己,如果兩個(gè)訂單所有屬性值全部相同,但訂單號(hào)不同,也是不同的實(shí)體。
實(shí)體之間的關(guān)系:
1)關(guān)系越多,耦合越大。
2)找出整個(gè)業(yè)務(wù)期間的都依賴的關(guān)系,某些關(guān)系只是在對(duì)象創(chuàng)建的時(shí)候有意義,比如創(chuàng)建訂單的時(shí)候會(huì)查詢一下商品價(jià)格信息。
3)盡可能的簡化關(guān)系,避免雙向依賴關(guān)系。
4.值對(duì)象:
無業(yè)務(wù)生命周期,無業(yè)務(wù)標(biāo)識(shí)符,通常用于描述實(shí)體。比如訂單的收貨地址、訂單支付的金額等就是值對(duì)象。
根據(jù)上下文的不同,一個(gè)值對(duì)象在一個(gè)界限上下文中上值對(duì)象,到了另一個(gè)界限上下文環(huán)境中會(huì)是實(shí)體,就是在不同的領(lǐng)域里屬性是不一樣的,具體還得根據(jù)上下文來看。
值對(duì)象是不可變(只讀),這樣線程安全,可以到處傳遞。
值對(duì)象最大的好處在于增加了代碼復(fù)用。
5.領(lǐng)域服務(wù):
無狀態(tài),有行為,服務(wù)本身也是對(duì)象,但它卻沒有屬性(只有行為),因此說是無狀態(tài),通常負(fù)責(zé)協(xié)調(diào)多個(gè)領(lǐng)域?qū)ο蟮牟僮鱽硗瓿梢恍┕δ堋1热鐕L試如何將信息轉(zhuǎn)化為領(lǐng)域模型,但并非所有的點(diǎn)我們都能用Model來涵蓋。對(duì)象應(yīng)當(dāng)有屬性,狀態(tài)和行為,但有時(shí)領(lǐng)域中有一些行為是無法映射到具體的對(duì)象中的,我們也不能強(qiáng)行將其放入在某一個(gè)模型對(duì)象中,而將其單獨(dú)作為一個(gè)方法又沒有地方,此時(shí)就需要服務(wù)。協(xié)調(diào)聚合之間的業(yè)務(wù)邏輯,并且完成用例,表示某種能力。
6.聚合:
聚合是一組相關(guān)的對(duì)象,它通過定義對(duì)象之間清晰的所屬關(guān)系和邊界來實(shí)現(xiàn)領(lǐng)域模型的內(nèi)聚,并避免了錯(cuò)綜復(fù)雜的難以維護(hù)的對(duì)象關(guān)系網(wǎng)的形成,我們把聚合看作是一個(gè)修改數(shù)據(jù)的單元,目的將這些對(duì)象作為一個(gè)單元(是業(yè)務(wù)的一個(gè)最小單元,持久化最小單元),每個(gè)聚合都有一個(gè)邊界和一個(gè)根,邊界定義了聚合里應(yīng)該包含什么,根是聚合中唯一可以被外部飲用的元素,比如說不能直接繞過訂單實(shí)體去訪問訂單項(xiàng),但在聚合邊界內(nèi)部,可以互相引用。聚合根具有全局唯一標(biāo)識(shí),聚合根由倉儲(chǔ)負(fù)責(zé)持久化其生命周期,而實(shí)體只有在聚合內(nèi)部有唯一局部標(biāo)識(shí),由聚合根負(fù)責(zé)其生命周期持久化。
通常將多個(gè)實(shí)體和值對(duì)象組合到一個(gè)聚合中來表達(dá)一個(gè)完整的概念,比如訂單實(shí)體、訂單明細(xì)實(shí)體、訂單金額值對(duì)象就代表一個(gè)完整的訂單概念,而且生命周期是相同的,并且需要統(tǒng)一持久化到數(shù)據(jù)庫中。
7.聚合根:
將聚合中表達(dá)總概念的實(shí)體做成聚合根,比如訂單實(shí)體就是聚合根,對(duì)聚合中所有實(shí)體的狀態(tài)變更必須經(jīng)過聚合根,因?yàn)榫酆细鶇f(xié)調(diào)了整個(gè)聚合的邏輯,保證一致性。當(dāng)然其他實(shí)體可以被外部直接臨時(shí)查詢調(diào)用。
8.倉儲(chǔ):
用于對(duì)聚合進(jìn)行持久化,通常為每個(gè)聚合根配備一個(gè)倉儲(chǔ)即可。倉儲(chǔ)能夠很好的解耦領(lǐng)域邏輯與數(shù)據(jù)庫。
9.工廠:
用于創(chuàng)建復(fù)雜的領(lǐng)域?qū)ο螅軌驅(qū)㈩I(lǐng)域?qū)ο髲?fù)雜的創(chuàng)建過程保護(hù)起來,可以創(chuàng)建實(shí)體,值對(duì)象。在大型系統(tǒng)中,實(shí)體和聚合通常是很復(fù)雜的,這就導(dǎo)致了很難去通過構(gòu)造器來創(chuàng)建對(duì)象,工廠就決解了這個(gè)問題,其實(shí)就是一種封裝,隱藏了復(fù)雜的創(chuàng)建細(xì)節(jié)。
10.上下文映射圖
如果我們將限界上下文理解為是對(duì)工作邊界的控制,則上下文之間的協(xié)作實(shí)則就是團(tuán)隊(duì)之間的協(xié)作,高效的團(tuán)隊(duì)協(xié)作應(yīng)遵循“各司其職、權(quán)責(zé)分明”的原則。從組織層面看,需要預(yù)防一個(gè)團(tuán)隊(duì)的“權(quán)力膨脹”,導(dǎo)致團(tuán)隊(duì)的“勢(shì)力范圍”擴(kuò)大到整個(gè)組織。從團(tuán)隊(duì)層面,又需要避免自己的權(quán)力遭遇壓縮,導(dǎo)致自己的話語權(quán)越來越小,這中間就存在一個(gè)平衡問題。映射到領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的術(shù)語,就是要在滿足合理分配職責(zé)的前提下,謹(jǐn)慎地確保每個(gè)限界上下文的粒度。職責(zé)的合理分配,可以更好地滿足團(tuán)隊(duì)的自組織或者說自治,但不可能做到“萬事不求人”,全靠自己來做。如果什么事情都由這一個(gè)團(tuán)隊(duì)完成,這個(gè)團(tuán)隊(duì)也就成為無所不能的“上帝”團(tuán)隊(duì)了。上下文映射展現(xiàn)了一種組織動(dòng)態(tài)能力(Organizational Dynamic),它可以幫助我們識(shí)別出有礙項(xiàng)目進(jìn)展的一些管理問題。”這也是我為何要在識(shí)別上下文的過程中引入項(xiàng)目經(jīng)理這個(gè)角色的原因所在,因?yàn)樵趫F(tuán)隊(duì)協(xié)作層面,限界上下文與項(xiàng)目管理息息相關(guān)。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)根據(jù)團(tuán)隊(duì)協(xié)作的方式與緊密程度,定義了五種團(tuán)隊(duì)協(xié)作模式:
1)合作關(guān)系(Partnership):兩個(gè)上下文緊密合作的關(guān)系,互相聯(lián)系緊密。
2)共享內(nèi)核(Shared Kernel):兩個(gè)上下文依賴部分共享的模型。
3)客戶方-供應(yīng)方開發(fā)(Customer-Supplier Development):正常情況下,這是團(tuán)隊(duì)合作中最為常見的合作模式,體現(xiàn)的是上游(供應(yīng)方)與下游(客戶方)的合作關(guān)系。這種合作需要兩個(gè)團(tuán)隊(duì)共同協(xié)商。
4)遵奉者(Conformist):下游限界上下文對(duì)上游限界上下文模型的追隨,做出遵奉模型決策的前提是需要明確這兩個(gè)上下文的統(tǒng)一語言是否存在一致性,因?yàn)橄藿缟舷挛牡倪吔绫旧砭褪菫榱司S護(hù)這種一致性而存在的。
5)分離方式(Separate Ways):在典型的電商網(wǎng)站中,支付上下文與商品上下文之間就沒有任何關(guān)系,二者是“分離方式”的體現(xiàn)。
八.DDD系統(tǒng)的分層架構(gòu)
分層就是將具有不同職責(zé)的組件分離開來,組成一套層內(nèi)部高聚合,層與層之間低耦合的軟件系統(tǒng),領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的討論同樣也是建立在層模式的基礎(chǔ)上的,但與傳統(tǒng)的分層架構(gòu)相比,它更注重領(lǐng)域架構(gòu)和技術(shù)架構(gòu)的分離。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)將軟件系統(tǒng)分為四層:基礎(chǔ)結(jié)構(gòu)層、領(lǐng)域?qū)印?yīng)用層和表現(xiàn)層。與上述的三層相比,數(shù)據(jù)訪問層已經(jīng)不在了,它被移到基礎(chǔ)設(shè)施層了,這些是屬于外圍的,不是核心。
1.基礎(chǔ)結(jié)構(gòu)層(Infrastructure Layer):
該層專為其它各層提供技術(shù)框架支持。注意,這部分內(nèi)容不會(huì)涉及任何業(yè)務(wù)方面的知識(shí)。數(shù)據(jù)訪問的內(nèi)容,也被放在了該層當(dāng)中,因?yàn)閿?shù)據(jù)的讀寫是業(yè)務(wù)無關(guān)的,該層主要是幫助領(lǐng)域模型進(jìn)行落地。
2.領(lǐng)域?qū)樱―omain Layer):
DDD的核心,包含了業(yè)務(wù)所涉及的領(lǐng)域?qū)ο螅▽?shí)體、值對(duì)象),領(lǐng)域服務(wù),倉儲(chǔ)接口(倉儲(chǔ)接口就是和存儲(chǔ)DB打交道的)都位于此層,領(lǐng)域模型無關(guān)技術(shù),具有高度的業(yè)務(wù)抽象性,它能夠精確的描述領(lǐng)域中的知識(shí)體系,并維護(hù)了領(lǐng)域?qū)ο蟮臓顟B(tài)以及它們之間的關(guān)系。這部分內(nèi)容的具體表現(xiàn)形式就是領(lǐng)域模型(Domain Model)。領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)提倡富領(lǐng)域模型,即盡量將業(yè)務(wù)邏輯歸屬到領(lǐng)域?qū)ο笊希瑢?shí)在無法歸屬的部分則以領(lǐng)域服務(wù)的形式進(jìn)行定義。領(lǐng)域?qū)邮强梢砸蕾嚱涌诘摹?br />
領(lǐng)域?qū)幼裱脑瓌t是,除非業(yè)務(wù)發(fā)生變化,否則其他的變化均不會(huì)影響領(lǐng)域?qū)樱@些變化包括是否使用不同的框架,是否需要分頁,是否移動(dòng)端訪問等等。
3.應(yīng)用層(Application Layer):
該層不包含任何領(lǐng)域邏輯,但它會(huì)對(duì)任務(wù)進(jìn)行協(xié)調(diào),并可以維護(hù)應(yīng)用程序的狀態(tài),因此,它更注重流程性的東西,包括分頁,提供openapi,提供webservice,在某些領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的實(shí)踐中,也會(huì)將其稱為“工作流層”。可以處理與領(lǐng)域?qū)訜o關(guān)的攔截性工作,比如日志,事務(wù)等。
4.表現(xiàn)層(Presentation Layer):
面向用戶信息展現(xiàn)層,負(fù)責(zé)顯示和接受輸入,不包含業(yè)務(wù)中的邏輯。
從上圖還可以看到,表現(xiàn)層與應(yīng)用層之間是通過數(shù)據(jù)傳輸對(duì)象(DTO)進(jìn)行交互的,數(shù)據(jù)傳輸對(duì)象是沒有行為的POCO對(duì)象,它的目的只是為了對(duì)領(lǐng)域?qū)ο筮M(jìn)行數(shù)據(jù)封裝,實(shí)現(xiàn)層與層之間的數(shù)據(jù)傳遞。為何不能直接將領(lǐng)域?qū)ο笥糜跀?shù)據(jù)傳遞?因?yàn)轭I(lǐng)域?qū)ο蟾⒅仡I(lǐng)域,而DTO更注重?cái)?shù)據(jù)。不僅如此,由于“富領(lǐng)域模型”的特點(diǎn),這樣做會(huì)直接將領(lǐng)域?qū)ο蟮男袨楸┞督o表現(xiàn)層。
領(lǐng)域?qū)邮菢I(yè)務(wù)的核心,所有的業(yè)務(wù)都是在領(lǐng)域?qū)印?yīng)用層是在領(lǐng)域?qū)又希瑸閡i服務(wù),它是響應(yīng)ui的請(qǐng)求去領(lǐng)域?qū)诱{(diào)用相應(yīng)的服務(wù),把結(jié)果返回給ui,這里面包括一些事務(wù),分頁等和業(yè)務(wù)無關(guān)的,所以這些和業(yè)務(wù)無關(guān)都會(huì)放在應(yīng)用層。基礎(chǔ)設(shè)施層是服務(wù)領(lǐng)域?qū)拥摹?/p>
架構(gòu)風(fēng)格
針對(duì)DDD的架構(gòu)設(shè)計(jì),《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》書中提到了幾種架構(gòu)風(fēng)格:六邊形架構(gòu)、REST架構(gòu)、CQRS、事件驅(qū)動(dòng)等。在實(shí)際使用中,落地的架構(gòu)并非是純粹其中的一種,而很有可能戶將上述幾種架構(gòu)風(fēng)格結(jié)合起來實(shí)現(xiàn)。
所謂的六邊形架構(gòu),其實(shí)是分層架構(gòu)的擴(kuò)展,原來的分層架構(gòu)通常是上下分層的,比如常見的MVC模式,上層是對(duì)外的服務(wù)接口,下層是對(duì)接存儲(chǔ)層或者是集成第三方服務(wù),中層是業(yè)務(wù)邏輯層。我們跳出分層的概念,會(huì)發(fā)現(xiàn)上面層和下面層其實(shí)都是端口+適配器的實(shí)現(xiàn),上面層開放http/tcp端口,采用rest/soap/mq協(xié)議等對(duì)外提供服務(wù),同時(shí)提供對(duì)應(yīng)協(xié)議的適配器;下層也是端口+適配器,只不過應(yīng)用程序這時(shí)候變成了調(diào)用者,第三方服務(wù)或者存儲(chǔ)層提供端口和服務(wù),應(yīng)用程序本身實(shí)現(xiàn)適配功能。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain Driven Design)有一個(gè)官方的sample工程,名為DDDSample,官網(wǎng):http://dddsample.sourceforge.net/,該工程給出了一種實(shí)踐領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的參考架構(gòu)。下圖就是它的代碼結(jié)構(gòu)。
各個(gè)目錄含義:Infrastructure(基礎(chǔ)實(shí)施層),Domain(領(lǐng)域?qū)?,Application(應(yīng)用層),Interfaces(表示層,也叫用戶界面層或是接口層),config(各種配置)
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)過程中使用的模式
九.領(lǐng)域驅(qū)動(dòng)總結(jié)
DDD與數(shù)據(jù)庫設(shè)計(jì)不同:
1.領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)是一種面向?qū)ο蟮脑O(shè)計(jì),先建模再去設(shè)計(jì)數(shù)據(jù)庫。
2.領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)主要是基于現(xiàn)實(shí)業(yè)務(wù)中的模型,更加貼近真實(shí)業(yè)務(wù),不僅僅是一種技術(shù)的實(shí)現(xiàn)。
3.領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)出來的產(chǎn)品---領(lǐng)域?qū)ο螅―omain Object),是一個(gè)充血模型,不但包含業(yè)務(wù)對(duì)象的屬性,也包含業(yè)務(wù)對(duì)象的方法和行為,更加符合oo原則。
4.領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)并不包含數(shù)據(jù)庫具體設(shè)計(jì),而是和領(lǐng)域?qū)<乙黄穑捎媒y(tǒng)一的語言分析領(lǐng)域?qū)ο蟮膶傩裕瑯I(yè)務(wù)方法,以及領(lǐng)域之間的關(guān)系,并為之建模。
5.領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)能減少溝通的成本。
DDD的特點(diǎn):
1.統(tǒng)一語言,業(yè)務(wù) 產(chǎn)品 技術(shù)交流都不會(huì)設(shè)計(jì)到具體的技術(shù)方面,主要是對(duì)核心業(yè)務(wù)的建模,不會(huì)先考慮數(shù)據(jù)表的設(shè)計(jì),先考慮建模。
2.專有的領(lǐng)域?qū)樱I(lǐng)域?qū)映藰I(yè)務(wù)之外不設(shè)計(jì)軟件架構(gòu),等底層技術(shù)。
3.領(lǐng)域?qū)哟a就是業(yè)務(wù)文檔,看到領(lǐng)域?qū)哟a就能看到業(yè)務(wù) 的核心,就是從對(duì)象中不僅僅看到屬性還能可以看到業(yè)務(wù)。
DDD的一些問題
1.為什么DDD可以應(yīng)對(duì)復(fù)雜性?
答:就是分而自治思想,比如說一個(gè)系統(tǒng)幾百張表,不可能一下子弄清楚,但是可以按業(yè)務(wù),模塊去劃分,DDD里面叫做界限上下文,和模塊(但是提出了更多概念,比如聚合,一個(gè)模塊有可能還是很大。)類似,劃分成一個(gè)個(gè)領(lǐng)域,而領(lǐng)域模型有清晰的邊界,同時(shí)DDD重構(gòu)了設(shè)計(jì)模式 架構(gòu)模式 它里面也引入了ioc,工廠模式 策略模式 只是在更高層次上的應(yīng)用。
2.為什么可以快速應(yīng)對(duì)變化?
當(dāng)問題空間出現(xiàn)變化的時(shí)候,我們可以快速的找到領(lǐng)域模型。領(lǐng)域?qū)邮强梢院苋菀讓I(yè)務(wù)模塊拿出來重用的。
何時(shí)考慮使用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)?
1.如果系統(tǒng)只是簡單的curd,沒有很復(fù)雜的業(yè)務(wù)邏輯,不需要領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),反而回增加復(fù)雜性。
2.如果你的應(yīng)用多于用例場(chǎng)景,你的系統(tǒng)可能會(huì)逐漸成為一個(gè)大泥球(混雜在一起的上下文關(guān)系,邊界不清晰,代碼混亂)。如果你確定你的系統(tǒng)將會(huì)更復(fù)雜,你應(yīng)該使用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)來處理這個(gè)復(fù)雜性。
使用領(lǐng)域驅(qū)動(dòng)的難點(diǎn)
應(yīng)用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)并沒那么簡單容易,這需要花費(fèi)時(shí)間和精力去了解業(yè)務(wù)領(lǐng)域、術(shù)語、調(diào)查、和領(lǐng)域?qū)<乙黄鸷献魅澐秩绾稳澐诸I(lǐng)域,劃分好邊界并建模,去業(yè)務(wù)進(jìn)行抽象,這也是DDD的最重要的地方。
十.一些相關(guān)的擴(kuò)展閱讀
CQRS架構(gòu):
核心思想是將應(yīng)用程序的查詢部分和命令部分完全分離,這兩部分可以用完全不同的模型和技術(shù)去實(shí)現(xiàn)。比如命令部分可以通過領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)來實(shí)現(xiàn);查詢部分可以直接用最快的非面向?qū)ο蟮姆绞饺?shí)現(xiàn),比如用SQL。這樣的思想有很多好處:
1)實(shí)現(xiàn)命令部分的領(lǐng)域模型不用經(jīng)常為了領(lǐng)域?qū)ο罂赡軙?huì)被如何查詢而做一些折中處理;
2)由于命令和查詢是完全分離的,所以這兩部分可以用不同的技術(shù)架構(gòu)實(shí)現(xiàn),包括數(shù)據(jù)庫設(shè)計(jì)都可以分開設(shè)計(jì),每一部分可以充分發(fā)揮其長處;
3)高性能,命令端因?yàn)闆]有返回值,可以像消息隊(duì)列一樣接受命令,放在隊(duì)列中,慢慢處理;處理完后,可以通過異步的方式通知查詢端,這樣查詢端可以做數(shù)據(jù)同步的處理。
事件溯源(Event Sourcing):
基于DDD的設(shè)計(jì),對(duì)于聚合,不保存聚合的當(dāng)前狀態(tài),而是保存對(duì)象上所發(fā)生的每個(gè)事件。當(dāng)要重建一個(gè)聚合對(duì)象時(shí),可以通過回溯這些事件(即讓這些事件重新發(fā)生)來讓對(duì)象恢復(fù)到某個(gè)特定的狀態(tài);因?yàn)橛袝r(shí)一個(gè)聚合可能會(huì)發(fā)生很多事件,所以如果每次要在重建對(duì)象時(shí)都從頭回溯事件,會(huì)導(dǎo)致性能低下,所以我們會(huì)在一定時(shí)候?yàn)榫酆蟿?chuàng)建一個(gè)快照。這樣,我們就可以基于某個(gè)快照開始創(chuàng)建聚合對(duì)象了。
DCI架構(gòu):
DCI架構(gòu)強(qiáng)調(diào),軟件應(yīng)該真實(shí)的模擬現(xiàn)實(shí)生活中對(duì)象的交互方式,代碼應(yīng)該準(zhǔn)確樸實(shí)的反映用戶的心智模型。在DCI中有:數(shù)據(jù)模型、角色模型、以及上下文這三個(gè)概念。數(shù)據(jù)模型表示程序的結(jié)構(gòu),目前我們所理解的DDD中的領(lǐng)域模型可以很好的表示數(shù)據(jù)模型;角色模型表示數(shù)據(jù)如何交互,一個(gè)角色定義了某個(gè)“身份”所具有的交互行為;上下文對(duì)應(yīng)業(yè)務(wù)場(chǎng)景,用于實(shí)現(xiàn)業(yè)務(wù)用例,注意是業(yè)務(wù)用例而不是系統(tǒng)用例,業(yè)務(wù)用例只與業(yè)務(wù)相關(guān);軟件運(yùn)行時(shí),根據(jù)用戶的操作,系統(tǒng)創(chuàng)建相應(yīng)的場(chǎng)景,并把相關(guān)的數(shù)據(jù)對(duì)象作為場(chǎng)景參與者傳遞給場(chǎng)景,然后場(chǎng)景知道該為每個(gè)對(duì)象賦予什么角色,當(dāng)對(duì)象被賦予某個(gè)角色后就真正成為有交互能力的對(duì)象,然后與其他對(duì)象進(jìn)行交互;這個(gè)過程與現(xiàn)實(shí)生活中我們所理解的對(duì)象是一致的;
DCI的這種思想與DDD中的領(lǐng)域服務(wù)所做的事情是一樣的,但實(shí)現(xiàn)的角度有些不同。DDD中的領(lǐng)域服務(wù)被創(chuàng)建的出發(fā)點(diǎn)是當(dāng)一些職責(zé)不太適合放在任何一個(gè)領(lǐng)域?qū)ο笊蠒r(shí),這個(gè)職責(zé)往往對(duì)應(yīng)領(lǐng)域中的某個(gè)活動(dòng)或轉(zhuǎn)換過程,此時(shí)我們應(yīng)該考慮將其放在一個(gè)服務(wù)中。比如資金轉(zhuǎn)帳的例子,我們應(yīng)該提供一個(gè)資金轉(zhuǎn)帳的服務(wù),用來對(duì)應(yīng)領(lǐng)域中的資金轉(zhuǎn)帳這個(gè)領(lǐng)域概念。但是領(lǐng)域服務(wù)內(nèi)部做的事情是協(xié)調(diào)多個(gè)領(lǐng)域?qū)ο笸瓿梢患虑椤R虼耍贒DD中的領(lǐng)域服務(wù)在協(xié)調(diào)領(lǐng)域?qū)ο笞鍪虑闀r(shí),領(lǐng)域?qū)ο笸翘幱谝粋€(gè)被動(dòng)的地位,領(lǐng)域服務(wù)通知每個(gè)對(duì)象要求其做自己能做的事情,這樣就行了。這個(gè)過程中我們似乎看不到對(duì)象之間交互的意思,因?yàn)檎麄€(gè)過程都是由領(lǐng)域服務(wù)以面向過程的思維去實(shí)現(xiàn)了。而DCI則通用引入角色,賦予角色以交互能力,然后讓角色之間進(jìn)行交互,從而可以讓我們看到對(duì)象與對(duì)象之間交互的過程。但前提是,對(duì)象之間確實(shí)是在交互。因?yàn)楝F(xiàn)實(shí)生活中并不是所有的對(duì)象在做交互,比如有A、B、C三個(gè)對(duì)象,A通知B做事情,A通知C做事情,此時(shí)可以認(rèn)為A和B,A和C之間是在交互,但是B和C之間沒有交互。所以我們需要分清這種情況。資金轉(zhuǎn)帳的例子,A相當(dāng)于轉(zhuǎn)帳服務(wù),B相當(dāng)于帳號(hào)1,C相當(dāng)于帳號(hào)2。因此,資金轉(zhuǎn)帳這個(gè)業(yè)務(wù)場(chǎng)景,用領(lǐng)域服務(wù)比較自然。有人認(rèn)為DCI可以替換DDD中的領(lǐng)域服務(wù),我持懷疑態(tài)度。
四色原型分析模式:
1) 時(shí)刻-時(shí)間段原型(Moment-Interval Archetype)
表示在某個(gè)時(shí)刻或某一段時(shí)間內(nèi)發(fā)生的某個(gè)活動(dòng)。使用粉紅色表示,簡寫為MI。
2) 參與方-地點(diǎn)-物品原型(Part-Place-Thing Archetype)
表示參與某個(gè)活動(dòng)的人或物,地點(diǎn)則是活動(dòng)的發(fā)生地。使用綠色表示。簡寫為PPT。
3) 描述原型(Description Archetype)
表示對(duì)PPT的本質(zhì)描述。它不是PPT的分類!Description是從PPT抽象出來的不變的共性的屬性的集合。使用藍(lán)色表示,簡寫為DESC。
舉個(gè)例子,有一個(gè)人叫張三,如果某個(gè)外星人問你張三是什么?你會(huì)怎么說?可能會(huì)說,張三是個(gè)人,但是外星人不知道“人”是什么。然后你會(huì)怎么辦?你就會(huì)說:張三是個(gè)由一個(gè)頭、兩只手、兩只腳,以及一個(gè)身體組成的客觀存在。雖然這時(shí)外星人仍然不知道人是什么,但我已經(jīng)可以借用這個(gè)例子向大家說明什么是“Description”了。在這個(gè)例子中,張三就是一個(gè)PPT,而“由一個(gè)頭、兩只手、兩只腳,以及一個(gè)身體組成的客觀存在”就是對(duì)張三的Description,頭、手、腳、身體則是人的本質(zhì)的不變的共性的屬性的集合。但我們?nèi)祟惐容^聰明,很會(huì)抽象總結(jié)和命名,已經(jīng)把這個(gè)Description用一個(gè)字來代替了,那就是“人”。所以就有所謂的張三是人的說法。
4) 角色原型(Role Archetype)
角色就是我們平時(shí)所理解的“身份”。使用黃色表示,簡寫為Role。為什么會(huì)有角色這個(gè)概念?因?yàn)橛行┗顒?dòng),只允許具有特定角色(身份)的PPT(參與者)才能參與該活動(dòng)。比如一個(gè)人只有具有教師的角色才能上課(一種活動(dòng));一個(gè)人只有是一個(gè)合法公民才能參與選舉和被選舉;但是有些活動(dòng)也是不需要角色的,比如一個(gè)人不需要具備任何角色就可以睡覺(一種活動(dòng))。當(dāng)然,其實(shí)說人不需要角色就能睡覺也是錯(cuò)誤的,錯(cuò)在哪里?因?yàn)槲覀兛梢赃@樣理解:一個(gè)客觀存在只要具有“人”的角色就能睡覺,其實(shí)這時(shí)候,我們已經(jīng)把DESC當(dāng)作角色來看待了。所以,其實(shí)角色這個(gè)概念是非常廣的,不能用我們平時(shí)所理解的狹義的“身份”來理解,因?yàn)椤敖處煛薄ⅰ昂戏ü瘛薄ⅰ叭恕倍伎梢员蛔鳛榻巧珌砜创R虼耍瑧?yīng)該這樣說:任何一個(gè)活動(dòng),都需要具有一定角色的參與者才能參與。
用一句話來概括四色原型就是:一個(gè)什么什么樣的人或組織或物品以某種角色在某個(gè)時(shí)刻或某段時(shí)間內(nèi)參與某個(gè)活動(dòng)。 其中“什么什么樣的”就是DESC,“人或組織或物品”就是PPT,“角色”就是Role,而”某個(gè)時(shí)刻或某段時(shí)間內(nèi)的某個(gè)活動(dòng)"就是MI。
以上這些東西如果在學(xué)習(xí)了DDD之后再去學(xué)習(xí)會(huì)對(duì)DDD有更深入的了解,但我覺得DDD相對(duì)比較基礎(chǔ),如果我們?cè)谝呀?jīng)了解了DDD的基礎(chǔ)之上再去學(xué)習(xí)這些東西會(huì)更加有效和容易掌握。
十一 .其他的軟件開發(fā)模式
TDD:測(cè)試驅(qū)動(dòng)開發(fā)(Test-Driven Development)
BDD:行為驅(qū)動(dòng)開發(fā)(Behavior Driven Development)
ATDD:驗(yàn)收測(cè)試驅(qū)動(dòng)開發(fā)(Acceptance Test Driven Development)
總結(jié)
以上是生活随笔為你收集整理的【系统架构】领域驱动DDD(Domain-Driven Design)- 软件核心复杂性应对之道的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 北京的高档购物中心有哪些呢?中秋节想出去
- 下一篇: 江苏有哪些工艺品 江苏工艺品