領域驅動設計 領域驅動設計是關於軟體開發時架構設計與建模的方法論,隨著微服務架構的普及,領域驅動設計也隨之被廣泛使用。在本文中,將對領域驅動設計中的重要概念進行介紹。 界限上下文 在領域驅動設計中,首先需要根據客觀對象的實際內容以及對業務的理解,劃分出不同的領域。因此,引出了一個重要的概念:界限上下 ...
領域驅動設計
領域驅動設計是關於軟體開發時架構設計與建模的方法論,隨著微服務架構的普及,領域驅動設計也隨之被廣泛使用。在本文中,將對領域驅動設計中的重要概念進行介紹。
界限上下文
在領域驅動設計中,首先需要根據客觀對象的實際內容以及對業務的理解,劃分出不同的領域。因此,引出了一個重要的概念:界限上下文。界限上下文(bounded context)指的是根據業務知識自然劃分出的業務邊界。比如,以書店為例,書店有兩種基礎的業務上下文,分別是倉儲與銷售。在不同的上下文中,書店這一客觀實體將被以不同方式理解,所有與特定上下文相關的行為與邏輯都應該在這個上下文中被解決。當涉及到多個上下文交互的時候,通常會使用各個上下文中的門戶對象進行交流(這一點有點類似於聚合中使用聚合根進行統一的管理與訪問)
實體、值對象與聚合
做一個淺顯的類比,界限上下文好比一個城市的邊界。劃分完邊界後,可能還會根據業務的具體需要劃分出更細粒度的組織————聚合,聚合就好比在城市之中的各個街道與社區。當然,城市系統可以正常運作實際上離不開其中的人與物,類似於這一層級的概念,在領域驅動設計中我們將之稱作實體、值對象。
實體
- 實體,擁有唯一標識符,經歷狀態變更後仍保持一致性。其重要特征是延續性和可標識性。比如在學生管理系統中,學生可以被設計為一個實體,每一個學生實體擁有唯一標識符。在不同的界限上下文中,名字相同的實體將會有不同的含義與側重點, 比如在倉儲的上下文中,我們關心書的尺寸、重量與類型,而在銷售的上下文中,我們關心書的內容、外觀、價格與評價。
- 相比於貧血模型的優勢。所謂貧血模型指的是,模型僅對資料庫中的表結構進行了簡單映射,其餘的邏輯大量充斥在其他的類中,需要依賴大量的Utils類去計算與之相關的業務邏輯。
貧血模型的缺點[1]
- 無法保護模型對象的完整性和一致性:對象的所有屬性都是公開的,只能由調用方來維護模型的一致性,缺乏保障。
- 對象操作的可發現性極差:從對象的屬性上難以直觀的看出業務邏輯。什麼時候可以被調用?賦值的邊界是什麼?舉個例子,Long類型的值是否可以是0或者負數?
- 代碼邏輯重覆:如校驗邏輯、計算邏輯等,很容易出現在多個服務的代碼塊里,這會提升後期維護的成本和bug出現的概率。
- 代碼的健壯性差:比如一個數據模型的變化可能導致從上到下的所有代碼的變更。
值對象
- 值對象,擁有一系列屬性,沒有唯一標識符。比如在學生管理系統中,家庭住址這一概念,可以由省、市、街道唯一編碼,是一組靜態的屬性值。
聚合與聚合根
- 聚合,可以認為聚合是擁有一系列相關的,在特定界限上下文中的實體與值對象的集合。在聚合中,我們通常使用聚合根來管理和訪問聚合以及其中的其他成員。聚合根本質上也是一種實體,一般被稱作為根實體。對於一個特定的界限上下文,可以由單個或多個聚合構成。
Domain Primitive
Domain Primitive 是一個在特定領域里,擁有精准定義的、可自我驗證的、擁有行為的 Value Object。(註:Domain Primitive的概念和命名來自於Dan Bergh Johnsson & Daniel Deogun的書 Secure by Design)
通常使用DP時,會遇到以下幾種需求:
- 讓隱性的概念顯性化
- 讓隱性的上下文顯性化
- 封裝多對象行為
常見的需要用到DP的場景:
- 有格式限制的String或Integer欄位,如電話號碼、地址、姓名、成績等,因為一般含有校驗邏輯,此時可以在DP中封裝校驗邏輯
- 複雜的數據結構的操作,如
Map<String, List<Integer>>
,可以使用DP來隱藏具體的細節。
具體細節可以參考這一篇博文
領域之間的交互
不同領域之間通過相互交互,完成特定的系統功能。一個上游領域在一個特定業務流程中,可能會與多個下游領域交互。比如購物車服務在後續需要與訂單服務、倉儲服務、郵件服務進行交互。但由於各個服務是以微服務形式存在的,如果使用上游服務去聲明式(同步)地與下游服務進行交互,那麼一旦當中某一個下游服務因故障無法正確執行,則會造成整個系統狀態的不一致性。因此,使用非同步的交互方式(發佈-訂閱)能避免這一問題,上游服務可以在消息中間件中下發特定的消息,讓下游的服務非同步進行消費。同時,這種非同步的方式使上下游的服務相互解耦,當我們添加新的服務時,不需要改動無關的代碼。
事件風暴
開發人員與業務人員一起分析領域,並提出相關的建模。在每次討論中,只需要對特定的一個部分進行建模,而無需考慮全局的詳盡細節。可以以類似於敏捷開發的形式,迭代迴圈。通常需要確定的是業務相關的事件,以及事件相關的活動。通常來說,我們可以先定義完整個事件流,然後每個事件流後補充對應的活動與規則。
參考
[1]Repository模式
[2]DDD領域驅動設計-概述