實際上,本文的正確標題應該是《如何通過DDD構建解決快速出行問題的領域模型》。快速出行才是要解決的問題,汽車只是其中一種解決方案或者落地的實現而已。嚴格區分問題域與解決方案域是實施DDD的老大難問題了。需要死磕這個問題的讀者可以參考ThoughtWorks洞見的這篇文章《當Subdomain遇見Bounded Context》,這裡不展開討論。本文的目的是要展現一個在非IT領域運用DDD構建領域模型的例子。

先出要解決的業務領域問題:我要實現城市內快速出行。

就只有一句話,沒有細節信息,感覺什麼都做不了。但往往需求都是這樣的簡單粗暴的(此處淚奔)。好,繼續深挖一下需求。要實現城市內快速出行,最簡單的要實現3點:

1. 當我趕時間時,我可以加快速度

2. 當我遇到障礙物時,我可以減速

3. 當我遇到障礙物時,我可以繞過

嗯,有了3個細化的需求,有點感覺了。這裡可以引入風靡全球的DDD落地大法-事件風暴(Event Storm,簡稱ES)。在ES的過程中,會識別出業務問題中的關鍵事件,觸發事件的命令與觸發者(角色)。回到我們的場景,會是這樣:

事件風暴

作為例子,這裡只是列舉了3個簡單的事件,其餘就不在列出了。但並不代表在實際建模過程中可以省略。例如這裡有加快速度的需求,就一定會有減緩速度的需求;有停下的需求,就一定會有啟動的需求。這些需求對應的事件在實際過程中都是需要識別出來的。

好了,有了這些事件,我們可以把目光從戰術層面轉向戰略層識別子域。從事件到子域,是一個歸納與提煉的過程。通過對相關事件的歸類,劃分明確的業務領域與邊界,從而得到清晰的業務架構。這個過程我認為類似於地圖zoom out,zoom out 前能夠看到國家內的省份,城市,zoom out後雖看不到這些細節,但能夠看到所有國家的全景。回到我們的場景,我們識別出2個子域:

1. 速度控制

2. 方向控制

子域

真實的出行場景識別出來的子域肯定會更複雜。例如安全等子域,並且會劃分核心域,通用域,支撐域。作為例子這裡就不展開了。在實戰過程中,需要精通業務領域的專家依據其豐富的業務經驗對事件進行歸類與定義清晰的業務邊界。這往往是困難的,也是沒有什麼章法可循的。所以為什麼在DDD的建模過程中必須強調一定要有領域專家在的原因。沒有領域專家,出來的模型往往質量不高,所以如果沒有領域專家, 領域建模也就可以不用做了。

有了子域,我們可以跳回戰術層。但這次是探索解決方案了。這一步的關鍵概念是聚合。聚合是一組業務相關性比較強的對象,這些對象組合起來對外提供一致的服務。聚合的特點是聚合內高內聚,聚合間低耦合。由於要對外提供服務,必須有單一的訪問入口,這個入口叫做聚合根。從外部看一個聚合,只能看到它的聚合根,而看不到聚合內部具體的對象。

從事件到聚合,是一個從發散到收斂的抽象過程。這個過程很考驗系統架構師的抽象能力。回到我們的例子,我抽象出來的聚合是:

1. 動力控制 - 聚合根:加速控制器

2. 制動控制 - 聚合根:制動控制器

3. 轉向控制 - 聚合根:轉向控制器

聚合

在實戰的過程中,過程會更複雜,涉及的概念會更多。例如實體,值對象,領域服務,聚合根的識別等。這些展開的話都會是一篇文章。

需要注意的一點是,我這裡識別的聚合已經是解決方案的聚合,但實操過程中,有人會在這一步之前識別業務實體作為問題域的具體對象,然後再從業務實體識別出解決方案的聚合。很難說哪一種更好。先識別業務實體再識別聚合可能會更流暢,但也會因此引入更多的概念,讓對DDD不熟悉的人容易產生混淆的感覺。

到了這一步,我們在問題域的戰略層面有邊界清晰的業務架構,在解決方案域的戰術層面有能組合起來對外提供服務的聚合,是時候探索解決方案域的戰略設計了。這一步我的慣常做法是把戰術層面的聚合放回戰略層面的子域,看能不能解決子域的業務問題,如果能解決,就形成戰略層面的解決方案,即所謂的限界上下文。這個名字非常拗口,但這個名字卻很好的表明了它的特性:限界表明它是邊界劃分清晰的,職責是明確的,上下文表明它是有一定的語境的。這往往是最難理解的一點。一個簡單的例子就是對於「女兒」的解讀。「女兒」在不同的家庭上下文里所指的人是不一樣的。在我的家庭上下文裡面,「女兒」是指我的女兒,但是在我岳母的家庭上下文裡面,「女兒」指的就是我妻子。所以如果一個子域裡面只有一個聚合,那往往就會以這個聚合形成一個限界上下文;但如果一個子域裡面存在多於一個聚合,並且不同的聚合裡面存在一個名字一樣的對象,為了區分二義性,就要考慮是否需要拆開不同的上下文。回到我們的例子,限界上下文長這樣:

1. 動力控制上下文(同屬於速度控制子域)

2. 制動控制上下文(同屬於速度控制子域)

3. 轉向控制上下文

限界上下文

這裡速度控制子域還是維持動力與制動兩個上下文,更多的考慮點還是職責區分。

到這一步,快速出行工具的的領域模型構建基本已經完成了。再往後走就是實現域的事情,也就沒有DDD什麼事了。到這裡,才會涉及具體的技術實現,技術對複雜的業務架構的影響被DDD構建的領域架構完美隔離。從領域模型到具體實現的轉化,就輪到技術架構師出場了。 技術架構師基於系統架構開展具體的技術架構設計時,更多的是需要考慮現實的限制因素。例如,在古代,科學不像現在這麼發達,出行工具以馬車的形式出現,加速控制器被設計為馬鞭,制動控制器被設計為馬韁繩,轉向控制就靠馬本身;在現代,出行工具變成了汽車,加速控制器是加速踏板,制動控制器是制動踏板,轉向控制是方向盤。但是,無論出行工具怎麼進化,無論是地鐵,汽車,還是電動車,自行車,其通用系統架構與領域模型其實並沒有發生非常大的改變。究其原因是人們在出行時的具體需求沒有發生實質性變化,都是加速,減速,轉向。變化的其實是具體的技術架構。

馬車的實現
汽車的實現

回到IT領域,當系統架構確定後,系統的實現究竟是以現在火熱的微服務架構來實現還是沿用傳統的單體架構來實現,並不是DDD所要回答的問題。技術架構師必須基於現實因素來綜合考慮具體落地的實現方式。例如微服務架構最大的好處就是松耦合帶來的高響應力,就像上面提到的馬車,馬車與馬之間是松耦合的,馬累趴下了可以立刻換馬;單體架構的好處是不存在跨網路調用,性能往往比微服務好,就像現在的汽車(好吧,我承認我在黑微服務)。但是不論是微服務還是單體,基於DDD構建的領域模型都給最後的落地從架構上提供了很好的保障。

構建IT系統的微服務架構

討論到這裡,可能會有人問按照這種方法出來的模型跟我自己拍腦袋想的也差不多呀。然而事實就是這樣,我們能拍出來一個八九不離十的模型是因為我們對各種出行工具已經非常熟悉了,以至於可以認為我們其實都是一定程度上的領域專家。類似的問題我們在幫客戶實施DDD的過程中也經常被問到。尤其是對於遺留系統改造或者是系統平遷,最後出來的模型往往與客戶腦子裡面的模型差不多,然後他們就會問:「這和我們現在的差不多呀,也沒什麼特別大的區別呢。」,這個時候我一般會這樣回答:「這個是肯定的呢,如果你們現有系統架構與業務領域相去甚遠,你覺得你的系統能存活到現在嗎?」

回到我們的問題:我要實現城市內快速出行。話說回來,其實這個問題的最快解決方法難道不是直接買一輛汽車嗎?還花什麼心思自己構建一輛呢?這個我承認。對我們來說,市內快速出行只是一個通用域的問題,直接花錢買一輛汽車就行。所以我們的建模過程就會快速收斂成以下這樣:

非核心域快速收斂

嗯,不錯。遇到非核心域快速收斂,不用建模。Fail fast,才是DDD的精髓所在。

文/ThoughtWorks 馮文輝


推薦閱讀:
相关文章