DDD 領域驅動設計學習(二)- 限界上下文

12/9-DDD中國峰會部分文章節選

DDD不是架構,而是一種方法論(Methodology)。根據維基百科:Methodology is the systematic, theoretical analysis of the methods applied to a field of study,DDD正是針對軟件領域提供的系統與理論分析方法。Eric在創造性地提出DDD時,實則是針對當時項目中聚焦在Data(主要是DB Schema)為核心的系統建模方法的批判。這種面向數據的建模方式無法應對日漸復雜的業務邏輯,也無法更好地應用當時正沸沸揚揚的OO設計思想。這是設計觀念的轉變,蘊含了全新的設計思想、設計原則與設計過程。
坦白說,Eric Evans的DDD奠基之作《Domain-Driven Design》并沒有非常清晰的系統脈絡,戰略設計與戰術設計也未成體系。Eric的DDD其實沒有解決三個問題:

  • 如何進行領域建模
  • 如何識別Bounded Context
  • 如何在戰術層面尋找對象

DDD不是架構(設計)方法,因此不能把每個設計細節具象化。DDD是一套體系,這就決定了它必須具有開放性,在這個體系中你可以用任何一種方法來解決這些問題。但如果這些關鍵問題如果沒有具體落地的方法,可能會讓團隊無可適從。這其實也是DDD在許多項目中難以推行的部分原因。

DDD的戰略建模與戰術建模

戰略建模-Strategic Modeling:

  1. 限界上下文(Bounded Context)
  2. 上下文映射圖(Context Mapping)

戰術建模-Tactical Modeling:

  1. 聚合-Aggregate
  2. 實體-Entity
  3. 值對象-Value Objects
  4. 資源庫-Repository
  5. 領域服務-Domain Services
  6. 領域事件-Domain Events
  7. 模塊-Modules

Bound Context(BC)

先引用一段文章內容DDD分層架構的三種模式

UL(Ubiquitous Language,通用語言)是團隊共享的語言,是DDD中最具威力的特性之一。不管你在團隊中的角色如何,只要你是團隊的一員,你都將使用UL。由于UL的重要性,所以需要讓每個概念在各自的上下文中是清晰無歧義的,于是DDD在戰略設計上提出了模式BC(Bounded Context,限界上下文)。UL和BC同時構成了DDD的兩大支柱,并且它們是相輔相成的,即UL都有其確定的上下文含義,而BC中的每個概念都有唯一的含義。
一個業務領域劃分成若干個BC,它們之間通過Context Map進行集成。BC是一個顯式的邊界,領域模型便存在于這個邊界之內。領域模型是關于某個特定業務領域的軟件模型。通常,領域模型通過對象模型來實現,這些對象同時包含了數據和行為,并且表達了準確的業務含義。
從廣義上來講,領域即是一個組織所做的事情以及其中所包含的一切,表示整個業務系統。由于“領域模型”包含了“領域”這個詞,我們可能會認為應該為整個業務系統創建一個單一的、內聚的和全功能式的模型。然而,這并不是我們使用DDD的目標。正好相反,領域模型存在于BC內。

關于BC,有一個很形象的類別,細胞和細胞膜的類比:

細胞之所以能存在,是因為細胞膜定義了什么在細胞內,什么在細胞外,而且確認了什么物質可以通過細胞膜

BC可以類比為細胞膜,大型系統由于其復雜性,對于一個對象如果采取統一建模方式,可能會產生不可預測的效果。例如書中提到的客戶發票中的收費對象故障,其實類似問題也在很多地方可以看到。
大型系統領域模型的完全統一是不可行的,也不是一種經濟有效的方法。任何一個大型項目都會存在多個模型。而當基于不同模型的代碼被組合到一起后,軟件就會出現 bug ,變得不可靠和難以理解。團隊成員之間的溝通變得混亂。人們往往弄不清楚一個模型不應該在哪個上下文中使用。當不同的團隊不得不共同工作于一個模型時,我們必須小心不要踩到別人的腳。每當要時刻意識到任何針對模型的變更都有可能破壞現有的功能。當使用多個模型時,每個人在自己的模型之上可以自由地工作。我們都知道自己模型的界限,都恪守在這些邊界里。我們需要確保模型的純潔、一致和統一。
因此明確地定義模型所應用的上下文。根據團隊的組織、軟件系統的各個部分的用法以及物理表現〈代碼和數據庫模式等〕來設置模型的邊界。在這些邊界中嚴格保持模型的一致性,而不要受到邊界之外問題的干擾和混淆。
BC明確地限定了模型的應用范圍,以便讓團隊成員對什么應該保持一致以及上下文之間如何關聯有一個明確和共同的理解。在 CONTEXT中,要保證模型在邏輯上統一,而不用考慮它是不是適用于邊界之外的情況。

CONTINUOUS INTEGRATION(持續集成-CI)

定義完一個 BC后,必須讓它持續保持合理化。當很多人在同一個BC中工作時.模型很容易發生分裂。團隊越大,問題就越大,但即使是3、4個人的團隊也有可能會遇到嚴重的問題。然而,如果將系統分解為更小的 CONTEXT,最終又難以保持集成度和一致性。
所以一個方法是需要經常保持BC的一致,以便當模型發生分裂時,可以迅速發現并糾正問題。像領域驅動設計中的其他方法一樣,CI也有兩個級別的操作:(1)模型溉念的集成;(2)實現的集成。團隊成員之間通過經常溝通來保證概念的集成。團隊必須對不斷變化的模型形成一個共同的理解,最基本的方法是對 UL多加錘煉,在討論模型和應用程序時要堅持使用UL。同時,實際工件是通過系統性的合并/構建/測試過程來集成的,這樣的過程能夠盡早暴露出模型的分裂問題。
建立一個經常把所有代碼和其他實現工件合并到一起的過程,并通過自動測試來快速查明模型的分裂問題。嚴格堅持使用 UL,以便在不同人的頭腦中演變出不同的概念時,使所有人對模型都能達成一個共識。
最后,不要在持續集成中做一些不必要的工作。CI只有在BC中才是重要的。相鄰 CONTEXT中的設計問題(包括轉換)不必以同一個步調來處理。

上下文圖(Context Map)

多個系統之間會發生關系,存在交互,這也必然會在各自的BC上有所表現。在項目中創建一個所有模型上下文的全局視圖,可以減少混亂。上下文圖(Context Map)便是表示各個系統之間關系的總體視圖。
建模的過程需要識別每個模型在項目中的作用,并定義其 BC,這包括非面向對象子系統的隱含模型。為每個 BC命名,并把名稱添加到 UL。描述模型之間的接觸點,明確每次交流所需的轉換,并突出任何共享的內容。畫出現有的范圍。為稍后的轉換做好準備。
在Context Map中可以有如下幾種形式來表征限界上下文之間的關系,簡介如下(具體可閱讀原書):

1. 共享內核(Shared Kernel)

當不同團隊開發一些緊密相關的應用程序時,團隊之間需要進行協調,通??梢詫蓚€團隊共享的子集剝離出來形成共享內核(Shared Kernel),雙方進行持續集成(Continuous Integration)。共享內核(Shared Kernel)是業務領域中公共的部分,同時也是團隊間容易達成且必須達成共識的領域部分。

2. 客戶/供應商(Customer/Supplier)

不同系統之間存在依賴關系時,下游系統依賴上游系統,下游系統是客戶,上游系統是供應商,雙方協定好需求,由上游系統完成模型的構建和開發,并交付給下游系統使用,之后進行聯調、測試。這種模式建立在團隊之間友好合作和支持的情況下。
當兩個具有上游/下游關系的團隊不歸同一個管理者指揮時,Customer/Supplier這樣的合作模式就不會奏效。勉強應用這種模式會給下游團隊帶來麻煩。

3. Conformist(追隨者)

當兩個開發團隊具有上/下游關系時,如果上游團隊沒有動機來滿足下游團隊的需求,那么下游團隊將無能為力。出于利他主義的考慮,上游開發人員可能會做出承諾,但他們可能不會履行承諾。下游團隊出于良好的意愿會相信這些承諾,從而根據一些永遠不會實現的特性來制定計劃。下游項目只能被擱置.直到團隊最終學會利用現有條件自力更生為止。下游團隊不會得到根據他們的需求而量身定做的接口。
這時候“客戶/供應商”模式就不湊效了,那么下游系統只能去追隨上游系統,下游系統嚴格遵從上游系統的模型,簡化集成。
通過嚴格遵從上游團隊的模型,可以消除在 BC之間進行轉換的復雜性。盡管這會限制下游設計人員的風格,而且可能不會得到理想的應用程序模型,但選擇 Conformist模式可以極大地簡化集成。此外,這樣還可以與供應商團隊共享一種 UL。供應商處于駕駛者的位置上,因此最好使他們能夠容易溝通。

4. 防腐層(Anticorruption Layer)

前面介紹了在兩個BC之間集成時可以進行的各種合作,從高度合作的 Shared Kernel模式或 Customer/Supplier Team到單方面的Conformist模式。如果是一種更悲觀的關系,假設一個團隊既不可能與另一個團隊合作也無法利用他們的設計時,該如何應對。
這時候我們需要使用防腐層(Anticorruption Layer)模式將上游系統的影響降低。

5. 公開主機服務(Open Host Service)

當一個子系統必須與大量其他系統進行集成時,為每個集成都定制一個轉換層可能會減慢團隊的工作速度。如果一個子系統有某種內聚性,那么或許可以把它描述為一組 Service,這組 Service滿足了其他子系統的公共需求。
公開主機服務(Open Host Service)能夠允許系統將一組Service公開出去公其他系統訪問。定義一個協議,把你的子系統作為一組 Service供其他系統訪問。開放這個協議,以便所有需要與你的子系統集成的人都可以使用它。當有新的集成需求時,就增強并擴展這個協議,但個別團隊的特殊需求除外。

6. 各行其道(Separate Way)

當兩個系統之間的關系并非必不可少時,兩者完全可以彼此獨立,各自獨立建模,獨立發展,互不影響。

Context Map例子

例子

U表示上游(Upstream)的被依賴方,D表示下游(Downstream)的依賴方。防腐層(ACL)放在下游,將上游的消息轉化為下游的領域模型。

模式圖譜

模式圖譜
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容