實際上比搭,本文的正確標題應該是《如何通過DDD構建解決快速出行問題的領域模型》。快速出行才是要解決的問題汽煮,汽車只是其中一種解決方案或者落地的實現而已冻记。嚴格區(qū)分問題域與解決方案域是實施DDD的老大難問題了睡毒。需要死磕這個問題的讀者可以參考TW洞見的這篇文章《當Subdomain遇見Bounded Context》,這里不展開討論冗栗。本文的目的是要展現一個在非IT領域運用DDD構建領域模型的例子演顾。
先出要解決的業(yè)務領域問題:我要實現城市內快速出行。
就只有一句話隅居,沒有細節(jié)信息钠至,感覺什么都做不了。但往往需求都是這樣的簡單粗暴的(此處淚奔)胎源。好棉钧,繼續(xù)深挖一下需求。要實現城市內快速出行涕蚤,最簡單的要實現3點:
1.? 當我趕時間時宪卿,我可以加快速度
2.? 當我遇到障礙物時,我可以減速
3.? 當我遇到障礙物時万栅,我可以繞過
嗯佑钾,有了3個細化的需求,有點感覺了烦粒。這里可以引入風靡全球的DDD落地大法-事件風暴(Event Storm休溶,簡稱ES)。在ES的過程中扰她,會識別出業(yè)務問題中的關鍵事件邮偎,觸發(fā)事件的命令與觸發(fā)者(角色)∫謇瑁回到我們的場景禾进,會是這樣:
作為例子,這里只是列舉了3個簡單的事件廉涕,其余就不在列出了泻云。但并不代表在實際建模過程中可以省略。例如這里有加快速度的需求狐蜕,就一定會有減緩速度的需求宠纯;有停下的需求,就一定會有啟動的需求层释。這些需求對應的事件在實際過程中都是需要識別出來的婆瓜。
好了,有了這些事件,我們可以把目光從戰(zhàn)術層面轉向戰(zhàn)略層識別子域廉白。從事件到子域个初,是一個歸納與提煉的過程。通過對相關事件的歸類猴蹂,劃分明確的業(yè)務領域與邊界院溺,從而得到清晰的業(yè)務架構。這個過程我認為類似于地圖zoom out磅轻,zoom out 前能夠看到國家內的省份珍逸,城市,zoom out后雖看不到這些細節(jié)聋溜,但能夠看到所有國家的全景谆膳。回到我們的場景撮躁,我們識別出2個子域:
真實的出行場景識別出來的子域肯定會更復雜漱病。例如安全等子域,并且會劃分核心域馒胆,通用域缨称,支撐域。作為例子這里就不展開了祝迂。在實戰(zhàn)過程中睦尽,需要精通業(yè)務領域的專家依據其豐富的業(yè)務經驗對事件進行歸類與定義清晰的業(yè)務邊界。這往往是困難的型雳,也是沒有什么章法可循的当凡。所以為什么在DDD的建模過程中必須強調一定要有領域專家在的原因。沒有領域專家纠俭,出來的模型往往質量不高沿量,所以如果沒有領域專家, 領域建模也就可以不用做了冤荆。
有了子域朴则,我們可以跳回戰(zhàn)術層。但這次是探索解決方案了钓简。這一步的關鍵概念是聚合乌妒。聚合是一組業(yè)務相關性比較強的對象,這些對象組合起來對外提供一致的服務外邓。聚合的特點是聚合內高內聚撤蚊,聚合間低耦合。由于要對外提供服務损话,必須有單一的訪問入口侦啸,這個入口叫做聚合根槽唾。從外部看一個聚合,只能看到它的聚合根光涂,而看不到聚合內部具體的對象庞萍。
從事件到聚合,是一個從發(fā)散到收斂的抽象過程顶捷。這個過程很考驗系統(tǒng)架構師的抽象能力挂绰∈豪椋回到我們的例子服赎,我抽象出來的聚合是:
1.? 動力控制 - 聚合根:加速控制器
2.? 制動控制 - 聚合根:制動控制器
3.? 轉向控制 - 聚合根:轉向控制器
在實戰(zhàn)的過程中,過程會更復雜交播,涉及的概念會更多重虑。例如實體,值對象秦士,領域服務缺厉,聚合根的識別等。這些展開的話都會是一篇文章隧土。
需要注意的一點是提针,我這里識別的聚合已經是解決方案的聚合,但實操過程中曹傀,有人會在這一步之前識別業(yè)務實體作為問題域的具體對象辐脖,然后再從業(yè)務實體識別出解決方案的聚合。很難說哪一種更好皆愉。先識別業(yè)務實體再識別聚合可能會更流暢嗜价,但也會因此引入更多的概念,讓對DDD不熟悉的人容易產生混淆的感覺幕庐。
到了這一步久锥,我們在問題域的戰(zhàn)略層面有邊界清晰的業(yè)務架構,在解決方案域的戰(zhàn)術層面有能組合起來對外提供服務的聚合异剥,是時候探索解決方案域的戰(zhàn)略設計了瑟由。這一步我的慣常做法是把戰(zhàn)術層面的聚合放回戰(zhàn)略層面的子域,看能不能解決子域的業(yè)務問題冤寿,如果能解決歹苦,就形成戰(zhàn)略層面的解決方案,即所謂的限界上下文疚沐。這個名字非常拗口暂氯,但這個名字卻很好的表明了它的特性:限界表明它是邊界劃分清晰的,職責是明確的亮蛔,上下文表明它是有一定的語境的痴施。這往往是最難理解的一點。一個簡單的例子就是對于“女兒”的解讀±背裕“女兒”在不同的家庭上下文里所指的人是不一樣的动遭。在我的家庭上下文里面,“女兒”是指我的女兒神得,但是在我岳母的家庭上下文里面厘惦,“女兒”指的就是我妻子。所以如果一個子域里面只有一個聚合哩簿,那往往就會以這個聚合形成一個限界上下文宵蕉;但如果一個子域里面存在多于一個聚合,并且不同的聚合里面存在一個名字一樣的對象节榜,為了區(qū)分二義性羡玛,就要考慮是否需要拆開不同的上下文∽诓裕回到我們的例子稼稿,限界上下文長這樣:
1.? 動力控制上下文(同屬于速度控制子域)
2.? 制動控制上下文(同屬于速度控制子域)
3.? 轉向控制上下文
這里速度控制子域還是維持動力與制動兩個上下文,更多的考慮點還是職責區(qū)分讳窟。
到這一步让歼,快速出行工具的的領域模型構建基本已經完成了。再往后走就是實現域的事情丽啡,也就沒有DDD什么事了谋右。到這里,才會涉及具體的技術實現碌上,技術對復雜的業(yè)務架構的影響被DDD構建的領域架構完美隔離倚评。從領域模型到具體實現的轉化,就輪到技術架構師出場了馏予。 技術架構師基于系統(tǒng)架構開展具體的技術架構設計時天梧,更多的是需要考慮現實的限制因素。例如霞丧,在古代呢岗,科學不像現在這么發(fā)達,出行工具以馬車的形式出現蛹尝,加速控制器被設計為馬鞭后豫,制動控制器被設計為馬韁繩,轉向控制就靠馬本身突那;在現代挫酿,出行工具變成了汽車,加速控制器是加速踏板愕难,制動控制器是制動踏板早龟,轉向控制是方向盤惫霸。但是,無論出行工具怎么進化葱弟,無論是地鐵壹店,汽車,還是電動車芝加,自行車硅卢,其通用系統(tǒng)架構與領域模型其實并沒有發(fā)生非常大的改變。究其原因是人們在出行時的具體需求沒有發(fā)生實質性變化藏杖,無非都是加速将塑,減速,轉向制市。變化的其實是具體的技術架構抬旺。
回到IT領域弊予,當系統(tǒng)架構確定后祥楣,系統(tǒng)的實現究竟是以現在火熱的微服務架構來實現還是沿用傳統(tǒng)的單體架構來實現,并不是DDD所要回答的問題汉柒。技術架構師必須基于現實因素來綜合考慮具體落地的實現方式误褪。例如微服務架構最大的好處就是松耦合帶來的高響應力,就像上面提到的馬車碾褂,馬車與馬之間是松耦合的兽间,馬累趴下了可以立刻換馬;單體架構的好處是不存在跨網絡調用正塌,性能往往比微服務好嘀略,就像現在的汽車(好吧,我承認我在黑微服務)乓诽。但是不論是微服務還是單體帜羊,基于DDD構建的領域模型都給最后的落地從架構上提供了很好的保障。
討論到這里鸠天,可能會有人問按照這種方法出來的模型跟我自己拍腦袋想的也差不多呀讼育。然而事實就是這樣,我們能拍出來一個八九不離十的模型是因為我們對各種出行工具已經非常熟悉了稠集,以至于可以認為我們其實都是一定程度上的領域專家奶段。類似的問題我們在幫客戶實施DDD的過程中也經常被問到。尤其是對于遺留系統(tǒng)改造或者是系統(tǒng)平遷剥纷,最后出來的模型往往與客戶腦子里面的模型差不多痹籍,然后他們就會問:“這和我們現在的差不多呀,也沒什么特別大的區(qū)別呢晦鞋《撞”刺洒,這個時候我一般會這樣回答:“這個是肯定的呢,如果你們現有系統(tǒng)架構與業(yè)務領域相去甚遠吼砂,你覺得你的系統(tǒng)能存活到現在嗎逆航?”
回到我們的問題:我要實現城市內快速出行。話說回來渔肩,其實這個問題的最快解決方法難道不是直接買一輛汽車嗎因俐?還花什么心思自己構建一輛呢?這個我承認周偎。對我們來說抹剩,市內快速出行只是一個通用域的問題,直接花錢買一輛汽車就行蓉坎。所以我們的建模過程就會快速收斂成以下這樣:
嗯澳眷,不錯。遇到非核心域快速收斂蛉艾。Fail fast钳踊,才是DDD的精髓所在。