結論先行:
在 DDD 中,通用語言是以限界上下文為邊界的。如果一個產品或者項目有多個限界上下文,我們就需要為每個限界上下文定義通用語言。
限界上下文提供了一個語義邊界,來保持通用語言和領域概念的一一對應關系。
這個約束解決了現實世界中同樣的名詞在不同場景、時機下對應不同的業(yè)務概念所帶來的歧義問題,幫助團隊在使用通用語言交流的時候可以無歧義溝通。
初嘗“通用語言”
最初我對于如何構建通用語言的認識,來自于《領域驅動設計》第一章中的案例。這個案例生動的展示了開發(fā)人員如何在和領域專家的溝通過程中,建立了雙方理解一致的通用語言,并且使用這個語言來進行雙方的溝通?;谀莻€案例,我當時對構建通用語言的理解就是要:
- 技術人員使用業(yè)務人員的用語作為開發(fā)詞匯;
- 劃分好聚合,將這些詞匯關聯(lián)到聚合上;
- 技術人員要將這些詞匯映射到代碼實現中;
- 這些詞匯會隨著項目的發(fā)展一點點擴展;
帶著這份理解,我在曾經負責過的小型項目上做了一些實踐,效果都很不錯。在很長一段時間,團隊的開發(fā)人員體會到了在和業(yè)務人員交流時候心有靈犀、會心一笑的快感;也很少聽到“這個東西不是我要的”這類批評了。
“通用語言”遇到同名詞匯時就變得不清不楚了
然而,當我來到ThoughtWorks參與到一些幾十號人的項目時,我發(fā)現根據這個原則構建起來的通用語言,在遇到同名多義的詞匯時,就無法保證團隊內部的溝通是無歧義的。而這種歧義又會導致團隊成員說著同樣的話想著不同的事情的情況出現,例如:
- 同名的業(yè)務詞匯與實際業(yè)務關系不清:“為什么不能給銷售訂單增加一個是否投訴的字段,界面上都是顯示在銷售訂單上的”——銷售訂單到底是個什么東西,能干什么不能干什么是怎么確定的?
- 同名的業(yè)務詞匯與不同的業(yè)務詞匯關聯(lián):“我在銷售訂單付款后改變了買家信息,為什么我看銷售訂單的預定里的買家也發(fā)生了改變”——這里說的買家信息有幾個?
- 同名的業(yè)務詞匯之間的關系不清楚:“為什么我變更了profile 上的買家地址,銷售訂單上的買家地址就跟著改變了” ——這里說訂單上的買家地址和profile 上的買家地址是一個什么關系?
通過添加約束消除歧義
下圖是 DDD 概念的一個元模型圖。從圖的左下角,我們可以看到在構建通用語言時,還有兩個額外的約束條件:子域和限界上下文。
在 DDD 中,軟件的核心是其為客戶解決領域相關的問題的能力
這里的領域,就是指軟件系統(tǒng)要解決的實際問題相關的東西的集合。
例如:為一個電子商務公司開發(fā)一個電商系統(tǒng),我們就需要圍繞這個盈利模式的運營方式、業(yè)務規(guī)則,比如如何進貨,如何促銷,如何物流等等了解這個電子商務公司的盈利模式,所有和業(yè)務相關的東西都屬于領域。
領域分為問題域和解決方案域兩部分。
為了分解問題域的復雜度,問題域又會被拆解為多個子域,每個子域都要明確待解決的業(yè)務問題和業(yè)務流程,以及通過解決業(yè)務問題為企業(yè)帶來了什么樣的業(yè)務價值(這個是因,業(yè)務流程和要解決的業(yè)務問題是果)。
在清晰的定義子域后,我們就可以建立通用語言來提取該子域的領域知識,并基于通用語言為解決問題建立領域模型。
一個領域模型會存在于一個限界上下文中。限界上下文在 DDD 中用來定義模型的適用范圍、模型的用途、以及在何處保持一致,限界上下文會讓團隊明確模型的職責邊界是什么。同時,通用語言被限定在限界上下文中;限界上下文提供了一個語義邊界,在每個限界上下文內通用語言的每個詞匯必須和領域概念一一對應。
理想條件下,子域和限界上下文是一一對應。但是子域劃分的粒度,遺留系統(tǒng)的現狀,語言的歧義,團隊結構等子域和限界上下文對應可能是1:N 或者 N:N 的。
通過限界上下文間的映射,上下文中的多個模型會協(xié)作以滿足系統(tǒng)需求。我們也可以了解在不同上下文中的同名詞匯是否存在關系,存在什么樣的關系。
對通用語言而言,子域解釋了通用語言和現實世界業(yè)務活動的關系;限界上下文提供了一個語義邊界,來保持通用語言和領域概念的一一對應關系;上下文映射則提供了不同限界上下中的通用語言的轉換關系。
來解決下前文的問題
前文所述的訂單及訂單的相關概念存在著歧義,我們來看下通過子域、限界上下文和上下文映射是怎么消除這些歧義的:
因為同名的業(yè)務詞匯與實際業(yè)務關系不清導致的疑惑
“為什么不能在銷售訂單中增加一個是否投訴的字段,界面上都是顯示在銷售訂單上的”
假設,這里所說的銷售訂單存在于銷售子域下,那么這個訂單應該解決的是銷售過程中的問題。訂單的生命周期以銷售開始到銷售終止。一般而言投訴屬于售后環(huán)節(jié),在銷售訂單上聲明是否投訴字段,意味著銷售訂單的職能突破了銷售子域。UI 上的銷售訂單展示了聚合的信息,和同名的領域模型不一定保持一致。
因為同名的業(yè)務詞匯與不同的業(yè)務詞匯關聯(lián)導致的疑惑
“我在訂單付款后改變了買家信息,為什么我看訂單的預定里的買家也發(fā)生了改變”
在訂單上有兩種買家信息,可以通過在不同的上下文中隔離來區(qū)別這兩個擁有相同含義但卻是不同詞匯的詞匯。在銷售子域中建立兩個上下文,分別為預定有界上下文和購買上下文,把訂單領域模型拆分到這兩個上下文中。在不同的上下文中,訂單都有自己的買家信息,就解決了“在訂單付款后改變了買家信息,為什么我看訂單的預定里的買家也發(fā)生了改變”這個問題。
因為同名的業(yè)務詞匯之間的關系不清楚導致的疑惑
“為什么我變更了profile 上的買家地址,訂單上的買家地址就跟著改變了”
訂單存在于購買上下文,profile 存在于身份信息上下文中,購買上下文和身份信息上下文存在映射關系,在訂單創(chuàng)建時候從身份信息上下文復制買家地址,在訂單中單獨保存。這樣就解決了“為什么我變更了profile 上的買家地址,訂單上的買家地址就跟著改變了” 的問題。
引用:
- 《領域驅動設計》
- 《實現領域驅動設計》
- 當Subdomain遇見Bounded Context
- DDD的終極大招——By Experience
- 《領域驅動設計學習:領域、子域、限界上下文》
文/ThoughtWorks王巖
更多精彩洞見,請關注微信公眾號:ThoughtWorks洞見

