結論先行

在 DDD 中,通用語言是以限界上下文為邊界的。如果一個產品或者項目有多個限界上下文,我們就需要為每個限界上下文定義通用語言。

限界上下文提供了一個語義邊界,來保持通用語言和領域概念的一一對應關係。

這個約束解決了現實世界中同樣的名詞在不同場景、時機下對應不同的業務概念所帶來的歧義問題,幫助團隊在使用通用語言交流的時候可以無歧義溝通。

初嘗「通用語言」

最初我對於如何構建通用語言的認識,來自於《領域驅動設計》第一章中的案例。這個案例生動的展示了開發人員如何在和領域專家的溝通過程中,建立了雙方理解一致的通用語言,並且使用這個語言來進行雙方的溝通。基於那個案例,我當時對構建通用語言的理解就是要:

  • 技術人員使用業務人員的用語作為開發辭彙;
  • 劃分好聚合,將這些辭彙關聯到聚合上;
  • 技術人員要將這些辭彙映射到代碼實現中;
  • 這些辭彙會隨著項目的發展一點點擴展;

帶著這份理解,我在曾經負責過的小型項目上做了一些實踐,效果都很不錯。在很長一段時間,團隊的開發人員體會到了在和業務人員交流時候心有靈犀、會心一笑的快感;也很少聽到「這個東西不是我要的」這類批評了。

「通用語言」遇到同名辭彙時就變得不清不楚了

然而,當我來到ThoughtWorks參與到一些幾十號人的項目時,我發現根據這個原則構建起來的通用語言,在遇到同名多義的辭彙時,就無法保證團隊內部的溝通是無歧義的。而這種歧義又會導致團隊成員說著同樣的話想著不同的事情的情況出現,例如:

  • 同名的業務辭彙與實際業務關係不清:「為什麼不能給銷售訂單增加一個是否投訴的欄位,界面上都是顯示在銷售訂單上的」——銷售訂單到底是個什麼東西,能幹什麼不能幹什麼是怎麼確定的?
  • 同名的業務辭彙與不同的業務辭彙關聯:「我在銷售訂單付款後改變了買家信息,為什麼我看銷售訂單的預定里的買家也發生了改變」——這裡說的買家信息有幾個?
  • 同名的業務辭彙之間的關係不清楚:「為什麼我變更了profile 上的買家地址,銷售訂單上的買家地址就跟著改變了」 ——這裡說訂單上的買家地址和profile 上的買家地址是一個什麼關係?

通過添加約束消除歧義

下圖是 DDD 概念的一個元模型圖。從圖的左下角,我們可以看到在構建通用語言時,還有兩個額外的約束條件:子域和限界上下文。

在 DDD 中,軟體的核心是其為客戶解決領域相關的問題的能力

這裡的領域,就是指軟體系統要解決的實際問題相關的東西的集合。

例如:為一個電子商務公司開發一個電商系統,我們就需要圍繞這個盈利模式的運營方式、業務規則,比如如何進貨,如何促銷,如何物流等等了解這個電子商務公司的盈利模式,所有和業務相關的東西都屬於領域。

領域分為問題域和解決方案域兩部分。

為了分解問題域的複雜度,問題域又會被拆解為多個子域,每個子域都要明確待解決的業務問題和業務流程,以及通過解決業務問題為企業帶來了什麼樣的業務價值(這個是因,業務流程和要解決的業務問題是果)。

在清晰的定義子域後,我們就可以建立通用語言來提取該子域的領域知識,並基於通用語言為解決問題建立領域模型。

一個領域模型會存在於一個限界上下文中。限界上下文在 DDD 中用來定義模型的適用範圍、模型的用途、以及在何處保持一致,限界上下文會讓團隊明確模型的職責邊界是什麼。同時,通用語言被限定在限界上下文中;限界上下文提供了一個語義邊界,在每個限界上下文內通用語言的每個辭彙必須和領域概念一一對應。

理想條件下,子域和限界上下文是一一對應。但是子域劃分的粒度,遺留系統的現狀,語言的歧義,團隊結構等子域和限界上下文對應可能是1:N 或者 N:N 的。

通過限界上下文間的映射,上下文中的多個模型會協作以滿足系統需求。我們也可以了解在不同上下文中的同名辭彙是否存在關係,存在什麼樣的關係。

對通用語言而言,子域解釋了通用語言和現實世界業務活動的關係;限界上下文提供了一個語義邊界,來保持通用語言和領域概念的一一對應關係;上下文映射則提供了不同限界上下中的通用語言的轉換關係。

來解決下前文的問題

前文所述的訂單及訂單的相關概念存在著歧義,我們來看下通過子域、限界上下文和上下文映射是怎麼消除這些歧義的:

因為同名的業務辭彙與實際業務關係不清導致的疑惑

「為什麼不能在銷售訂單中增加一個是否投訴的欄位,界面上都是顯示在銷售訂單上的」

假設,這裡所說的銷售訂單存在於銷售子域下,那麼這個訂單應該解決的是銷售過程中的問題。訂單的生命周期以銷售開始到銷售終止。一般而言投訴屬於售後環節,在銷售訂單上聲明是否投訴欄位,意味著銷售訂單的職能突破了銷售子域。UI 上的銷售訂單展示了聚合的信息,和同名的領域模型不一定保持一致。

因為同名的業務辭彙與不同的業務辭彙關聯導致的疑惑

「我在訂單付款後改變了買家信息,為什麼我看訂單的預定里的買家也發生了改變」

在訂單上有兩種買家信息,可以通過在不同的上下文中隔離來區別這兩個擁有相同含義但卻是不同辭彙的辭彙。在銷售子域中建立兩個上下文,分別為預定有界上下文和購買上下文,把訂單領域模型拆分到這兩個上下文中。在不同的上下文中,訂單都有自己的買家信息,就解決了「在訂單付款後改變了買家信息,為什麼我看訂單的預定里的買家也發生了改變」這個問題。

因為同名的業務辭彙之間的關係不清楚導致的疑惑

「為什麼我變更了profile 上的買家地址,訂單上的買家地址就跟著改變了」

訂單存在於購買上下文,profile 存在於身份信息上下文中,購買上下文和身份信息上下文存在映射關係,在訂單創建時候從身份信息上下文複製買家地址,在訂單中單獨保存。這樣就解決了「為什麼我變更了profile 上的買家地址,訂單上的買家地址就跟著改變了」 的問題。

引用:

  1. 《領域驅動設計》
  2. 《實現領域驅動設計》
  3. 當Subdomain遇見Bounded Context
  4. DDD的終極大招——By Experience
  5. 《領域驅動設計學習:領域、子域、限界上下文》

文/ThoughtWorks王岩

更多精彩洞見,請關注微信公眾號:ThoughtWorks洞見


推薦閱讀:
相关文章