1. DDD是什么?解決什么問題?
1.1 軟件開發(fā)的困境
- “隨著業(yè)務(wù)的擴(kuò)展,軟件開發(fā)投資越來越大” 團(tuán)隊(duì)的規(guī)模也開始變得越來越大,軟件系統(tǒng)的投資和維護(hù)的成本變得越來越高。
- “業(yè)務(wù)人員不懂架構(gòu),架構(gòu)師不懂代碼,開發(fā)人員不不懂業(yè)務(wù)模型” 當(dāng)團(tuán)隊(duì)中的關(guān)鍵角色誰也不懂誰的時候,問題來了。。。
- “重構(gòu)是好的,但什么時候要重構(gòu)?重構(gòu)到什么樣的架構(gòu)就是夠?的了?” 每個有追求的團(tuán)隊(duì)都在做重構(gòu),但管理者更關(guān)心,什么時間必須要重構(gòu)?重構(gòu)的目標(biāo)在哪?
1.2 DDD的來源及簡介
?? 2004年Eric Evans 發(fā)表Domain-Driven Design –Tackling Complexity in the Heart of Software (領(lǐng)域驅(qū)動設(shè)計(jì)),簡稱DDD,DDD是一套綜合軟件系統(tǒng)分析和設(shè)計(jì)的面向?qū)ο蠼7椒ā?br>
?? Eric的著名書籍《領(lǐng)域驅(qū)動設(shè)計(jì)》的副標(biāo)題是“軟件核心復(fù)雜性的應(yīng)對之道”,這個其實(shí)點(diǎn)出了DDD的來源和目標(biāo),很多因素會使軟件的開發(fā)復(fù)雜化。軟件是從現(xiàn)實(shí)世界到數(shù)字世界的一種建模和映射,軟件復(fù)雜性的根本原因還是業(yè)務(wù)本身復(fù)雜性,軟件開發(fā)者無法回避這種復(fù)雜性,所能做的是去控制這種復(fù)雜性。
怎樣才能讓軟件和領(lǐng)域和諧相處呢?最佳方式是讓軟件成為領(lǐng)域的一個映射。軟件需要包含領(lǐng)域里重要的核心概念和元素,并精確實(shí)現(xiàn)它們之間的關(guān)系。也就是說,軟件需要對領(lǐng)域進(jìn)行建模。
?? 領(lǐng)域驅(qū)動設(shè)計(jì)分為兩個階段:以一種領(lǐng)域?qū)<摇⒃O(shè)計(jì)人員、開發(fā)人員都能理解的通用語言作為相互交流的工具,在交流的過程中發(fā)現(xiàn)領(lǐng)域概念,然后將這些概念設(shè)計(jì)成一個領(lǐng)域模型;第二個階段是由領(lǐng)域模型驅(qū)動軟件設(shè)計(jì),用代碼來實(shí)現(xiàn)該領(lǐng)域模型。
?? 從DDD提出到開始流行,感覺經(jīng)過了10年左右的時間,巧的是XP和敏捷從提出到流行也差不多10年左右的時間。可見一套方法論從出現(xiàn)到成熟確實(shí)是有一定規(guī)律并需要成長時間的。
?? 按Martin Fowler在PoEAA一書中給了一個圖。說明當(dāng)軟件在開發(fā)初期,以數(shù)據(jù)驅(qū)動的架構(gòu)方式非常容易上手,但是隨著業(yè)務(wù)的增長和項(xiàng)目的推進(jìn),軟件開發(fā)和維護(hù)難度急劇升高。領(lǐng)域驅(qū)動設(shè)計(jì)則在項(xiàng)目初期就處在一個比較難以上手的位置,但是隨著業(yè)務(wù)的增長和項(xiàng)目的推進(jìn),軟件開發(fā)和維護(hù)難度平滑上升。

2. 模型和建模
2.1 什么是模型
- 模型是對客觀世界事物的一種抽象和簡化。
- 它是從某個角度反映人對客觀世界事物的一種認(rèn)識。
- 它用于對事物的本質(zhì)進(jìn)行深入細(xì)致的研究。
2.2 模型的例子
2.2.1 地圖的例子
?? 地圖就是一種典型的模型?,F(xiàn)實(shí)世界的地理信息往往很復(fù)雜,一個地理位置上會包括非常多的信息,例如有位置信息,地形,氣候等信息,也有城市,道路,綠化,管網(wǎng),建筑等這些城市建設(shè)信息,還可能會有擁堵情況,商場,文化等各種附加于地理信息的各種信息在里面。如何表達(dá)一個地圖信息其實(shí)并不容易,如何表達(dá)一個地圖信息是一件非常復(fù)雜的事情。
?? 我們常見的地圖,一般都會縮小尺寸,忽略很多細(xì)節(jié),分不同類型的地圖,現(xiàn)在還有圖層的方式來表達(dá)不同地理信息。地圖其實(shí)就是一種模型,而且地圖很多建模方式在軟件設(shè)計(jì)中也能找到映射。
?? 看兩個地圖例子,建模從古至今就存在,而且隨著人們認(rèn)知水平和建模能力還會不斷的演進(jìn)。


?? 回到軟件研發(fā)的話題,軟件的研發(fā)也是一種建模的過程,傳統(tǒng)的方法中,從問題空間到解決方案的空間,會經(jīng)過需求采集和分析,概要設(shè)計(jì),詳細(xì)設(shè)計(jì)再到編碼的過程。正如我們國家“系統(tǒng)分析師” 和“系統(tǒng)設(shè)計(jì)師” 兩種職稱考試一樣,系統(tǒng)分析和系統(tǒng)設(shè)計(jì)是分離的,“系統(tǒng)分析師” -BA屬于業(yè)務(wù)專家,“系統(tǒng)設(shè)計(jì)師”-SA是系統(tǒng)專家。隨著信息不斷傳遞的過程,信息也在不斷的失真,導(dǎo)致可能到了最后交付階段才發(fā)現(xiàn)很多功能不是客戶想要的,或者客戶需求變化太快,軟件的變更也不能快速跟隨需求變化。
?? 再舉一個常見的例子:一個函數(shù)寫了幾千行,里面的if-else寫了一大堆,計(jì)算各種業(yè)務(wù)規(guī)則。另一個人接手之后,分析了好幾個月,才把業(yè)務(wù)邏輯徹底理清楚。從表面來看,這是代碼寫的不規(guī)范,要重構(gòu),把一個幾千行的函數(shù)拆成一個個小的函數(shù)。從根本上來講,就是“重要邏輯”隱藏在代碼里面,沒有“顯性”的表達(dá)出來。這里可以引用一個觀點(diǎn):建模的本質(zhì)就是把“重要的東西進(jìn)行顯性化,并進(jìn)而把這些顯性化的構(gòu)造塊,互相串聯(lián)起來,組成一個體系“。
?? DDD通過建立一個業(yè)務(wù)域到軟件域的通用模型,把問題空間同解決方案空間聯(lián)系在一起,真正把領(lǐng)域的知識挖掘出來,讓領(lǐng)域?qū)<铱梢匀ヲ?qū)動軟件的實(shí)現(xiàn)。
3. 統(tǒng)一語言(UBIQUITOUS LANGUAGE)
?? 解決問題首先要從理解問題入手,很多事情的難點(diǎn)不在于解決問題,而在于認(rèn)知問題。關(guān)于統(tǒng)一語言必要性,有一個經(jīng)典的通天塔故事,人類想建一座通天塔,進(jìn)度很快,上帝害怕了,于是上帝讓建造者說不通的語言,這樣通天塔就再也沒有能建起來了。統(tǒng)一語言是一件事情能順利開展的基礎(chǔ)。
?? 由于語言上存在鴻溝,領(lǐng)域?qū)<覀冎荒苣:孛枋鏊麄兿胍臇|西,開發(fā)人員雖然努力去理解一個自己不熟悉的領(lǐng)域但也只能形成模糊的認(rèn)識,結(jié)果就是各說各的話,或者都是一知半解,最后到上線前才會發(fā)現(xiàn)漏了這個漏了那個。
?? 通用語言也并不是像UML,XML Schema或Java這樣的語言,它是一種自然的但經(jīng)過濃縮的領(lǐng)域語言,它是一種開發(fā)與用戶共享的語言,用來描述問題和領(lǐng)域模型。通用語言不是把從用戶那里聽到的內(nèi)容翻譯為開發(fā)的語言,而是為了減少誤解,讓用戶更容易理解的草圖,從而可以真正的幫助糾正錯誤,幫助開發(fā)獲取有關(guān)的領(lǐng)域新知識。
?? 那么統(tǒng)一語言到底長什么樣子?對DDD的UL沒有標(biāo)準(zhǔn)的定義,可以是圖,也可以是文字,UML的各種圖依然是常見的表達(dá)方式,以DDD書中例子來說明下UL的一個應(yīng)用。
?? 示例:制定貨運(yùn)路線,比較兩個圖和兩段溝通交流的方式
-
場景1:最小化的領(lǐng)域抽象
image.png -
場景2:用領(lǐng)域模型進(jìn)行討論
image.png - 兩種溝通方式的比較
| 最小化的領(lǐng)域抽象 | 用領(lǐng)域模型進(jìn)行討論 | |
|---|---|---|
| 用戶 | 那么,當(dāng)更改清關(guān)(customs clearance)地點(diǎn)時①,需要重新制定整個路線計(jì)劃啰。 | 那么,當(dāng)更改清關(guān)地點(diǎn)時,需要重新制定整個路線計(jì)劃啰。 |
| 開發(fā)人員 | 是的。我們將從貨運(yùn)表(shipment table)中刪除所有與該貨物id相關(guān)聯(lián)的行,然后將出發(fā)地、目的地和新的清關(guān)地點(diǎn)傳遞給Routing Service,它會重新填充貨運(yùn)表。Cargo中必須設(shè)立一個布爾值,用于指示貨運(yùn)表中是否有數(shù)據(jù)。 | 是的。當(dāng)更改Route Specification(路線說明)的任意屬性時,都將刪除原有的Itinerary(航線),并要求Routing Service(路線服務(wù))基于新的Route Specification生成一個新的Itinerary。 |
| 用戶 | 刪除行?好,就按你說的做。但是,如果先前根本沒有指定清關(guān)地點(diǎn),也需要這么做嗎? | 如果先前根本沒有指定清關(guān)地點(diǎn),也需要這么做嗎? |
| 開發(fā)人員 | 是的,無論何時更改了出發(fā)地、目的地或清關(guān)地點(diǎn)(或是第一次輸入),都將檢查是否已經(jīng)有貨運(yùn)數(shù)據(jù),如果有,則刪除它們,然后由Routing Service重新生成數(shù)據(jù)。 | 是的,無論何時更改了Route Spec的任何屬性,都將重新生成Itinerary。這也包括第一次輸入某些屬性。 |
| 用戶 | 當(dāng)然,如果原有的清關(guān)數(shù)據(jù)碰巧是正確的,我們就不需要這樣做了。 | 當(dāng)然,如果原有的清關(guān)數(shù)據(jù)碰巧是正確的,我們就不需要這樣做了。 |
| 開發(fā)人員 | 哦,沒問題。但讓Routing Service每次重新加載或卸載數(shù)據(jù)會更容易些。 | 哦,沒問題。但讓Routing Service每次重新生成一個Itinerary會更容易些。 |
| 用戶 | 是的,但為新航線制定所有支持計(jì)劃的工作量很大,因此,除非非改不可,我們一般不想更改航線。 | 是的,但為新航線制定所有支持計(jì)劃的工作量很大,因此,除非非改不可,我們一般不想更改路線。 |
| 開發(fā)人員 | 哦,好的,當(dāng)?shù)谝淮屋斎肭尻P(guān)地點(diǎn)時,我們需要查詢表格,找到以前的清關(guān)地點(diǎn),然后與新的清關(guān)地點(diǎn)進(jìn)行比較,從而判斷是否需要重做。 | 哦。那么需要在Route Specification添加一些功能。這樣,當(dāng)更改Route Specification中的屬性時,查看Itinerary是否仍滿足Specification。如果不滿足,則需要由Routing Service重新生成Itinerary。 |
| 用戶 | 這個處理不必考慮出發(fā)地和目的地,因?yàn)楹骄€在此總要變更。 | 這一點(diǎn)不必考慮出發(fā)地和目的地,因?yàn)镮tinerary在此總是要變更的。 |
| 開發(fā)人員 | 好的,我明白了。 | 好的,但每次只做比較就簡單多了。只有當(dāng)不滿足Route Specification時,才重新生成Itinerary。 |
?? 很明顯,這兩段對話有意使用了相似的結(jié)構(gòu),第一段對話顯得更啰嗦,對話雙方需要不斷對應(yīng)用程序的特性和表達(dá)不清的地方進(jìn)行解釋。第二段對話使用了基于領(lǐng)域模型的術(shù)語,因此討論更簡潔,表達(dá)了領(lǐng)域?qū)<业母嘁鈭D。在這兩段對話中,用戶都使用了“itinerary”這個詞,但在第二段中它是一個對象,這使得雙方可以更準(zhǔn)確、具體地進(jìn)行討論。他們明確討論了“route specification”,而不是每次都通過屬性和過程來描述它。

