領域驅動設計概覽
領域驅動設計(Domain Driven Design,DDD)是由Eric Evans最早提出的綜合軟體系統分析和設計的面向物件建模方法,如今已經發展為一種針對大型複雜系統的領域建模與分析方法。它完全改變了傳統軟體開發工程師針對資料庫進行的建模方法,從而 將要解決的業務概念和業務規則轉換為軟體系統中的型別以及型別的屬性與行為,通過合理運用面向物件的封裝、繼承、多型等設計要素 ,降低或隱藏整個系統的業務複雜性,並使得系統具有更好的擴充套件性,應對紛繁多變的現實業務問題。
領域驅動設計的開放性
領域驅動設計是一種方法論(Methodology)。根據維基百科的定義,方法論是一套運用到某個研究領域的系統與理論分析方法。領域驅動設計就是針對軟體開發領域提出的一套系統與理論分析方法。Eric Evans在創造性地提出領域驅動設計時,實則是針對當時專案中聚焦在以資料以及資料樣式為核心的系統建模方法的批判。面向資料的建模方法是關係資料庫理論的延續,關注的是資料表以及資料表之間關係的設計。這是典型的面向技術實現的建模方法,面對日漸複雜的業務邏輯,這種設計方法欠缺靈活性與可擴充套件性,也無法更好地利用面向物件設計思想以及設計模式,建立可重用的、可擴充套件的程式碼單元。領域驅動設計的提出,是 設計觀念的轉變,蘊含了全新的設計思想、設計原則與設計過程 。
由於領域驅動設計是一套方法論,它建立了 以領域為核心驅動力 的設計體系,因而具有一定的開放性。在這個體系中,你可以使用不限於領域驅動設計提出的任何一種方法來解決這些問題。例如,我們可以使用用例(Use Case)、測試驅動開發(TDD)、使用者故事(User Story)幫助我們對領域建立模型;我們可以引入整潔架構思想以及六邊形架構,以幫助我們建立一個層次分明、結構清晰的系統架構;我們可以引入函數語言程式設計思想,利用純函式與抽象代數結構的不變性以及函式的組合性來表達領域模型。這些實踐方法與模型已經超越了Eric Evans最初提出的領域驅動設計範疇,但在體系上卻是一脈相承的。這也是為什麼在領域驅動設計社群,能夠不斷誕生諸如CQRS模式、事件溯源(Event Sourcing)模式與事件風暴(Event Storming)等新概念的原因;領域驅動設計也以開放的心態擁抱微服務(Micro Service),甚至能夠將它的設計思想與原則運用到微服務架構設計中。
領域驅動設計過程
領域驅動設計當然不是架構方法,也並非設計模式。準確地說,它其實是“一種思維方式,也是一組優先任務,它旨在加速那些必須處理複雜領域的軟體專案的開發”。領域驅動設計貫穿了整個軟體開發的生命週期,包括對需求的分析,建模,架構,設計,甚至最終的編碼實現,乃至對編碼的測試與重構。
領域驅動設計強調領域模型的重要性,並通過模型驅動設計來保障領域模型與程式設計的一致。從業務需求中提煉出統一語言(Ubiquitous Language),再基於統一語言建立領域模型;這個領域模型會指導著程式設計以及編碼實現;最後,又通過重構來發現隱式概念,並運用設計模式改進設計與開發質量。這個過程如下圖所示:

這個過程是一個覆蓋軟體全生命週期的設計閉環,每個環節的輸出都可以作為下一個環節的輸入,而在其中扮演重要指導作用的則是“領域模型”。這個設計閉環是一個螺旋上升的迭代設計過程,領域模型會在這個迭代過程中逐漸演進,在保證模型完整性與正確性的同時,具有新鮮的活力,使得領域模型能夠始終如一的貫穿領域驅動設計過程,闡釋著領域邏輯,指導著程式設計,驗證著編碼質量。
如果仔細審視這個設計閉環,我們發現在針對問題域和業務期望提煉統一語言,並通過統一語言進行領域建模時,可能會面臨高複雜度的挑戰。這是因為對於一個複雜的軟體系統而言,我們要處理的問題域實在太龐大了。在為問題域尋求解決方案時,需要從巨集觀層次劃分不同業務關注點的子領域,然後再深入到子領域中從微觀層次對領域進行建模。巨集觀層次是戰略的層面,微觀層次是戰術的層面,只有將戰略設計與戰術設計結合起來,才是完整的領域驅動設計。
戰略設計階段
領域驅動設計的戰略設計階段是從兩個方面來考量的:
- 問題域方面:針對問題域,引入 限界上下文(Bounded Context) 和 上下文對映(Context Map) 對問題域進行合理的分解,識別出 核心領域(Core Domain) 與 子領域(SubDomain) ,並確定領域的邊界以及它們之間的關係,維持模型的完整性。
- 架構方面:通過 分層架構 來隔離關注點,尤其是將領域實現獨立出來,可以更利於領域模型的單一性與穩定性;引入 六邊形架構 清晰地表達領域與技術基礎設施的邊界;CQRS模式則分離了查詢場景和命令場景,針對不同場景選擇使用同步或非同步操作,提高架構的低延遲性與高併發能力。
Eric Evans提出戰略設計的初衷是要 保持模型的完整性 。限界上下文的邊界可以保護上下文內部和其他上下文之間的領域概念互不衝突。然而,如果我們將領域驅動設計的戰略設計模式引入到架構過程中,就會發現限界上下文不僅限於對領域模型的控制,而在於分離關注點之後,使得整個上下文可以成為獨立部署的設計單元,這就是“微服務”的概念,上下文對映的諸多模式則對應了微服務之間的協作。因此在戰略設計階段,微服務擴充套件了領域驅動設計的內容,反過來領域驅動設計又能夠保證良好的微服務設計。
一旦確立了限界上下文的邊界,尤其是作為物理邊界,則分層架構就不再針對整個軟體系統,而僅僅針對粒度更小的限界上下文。此時,限界上下文定義了技術實現的邊界,對當前上下文的領域與技術實現進行了封裝,我們只需要關心對外暴露的介面與整合方式,形成了在服務層次的設計單元重用。
邊界給了實現限界上下文內部的最大自由度。這也是戰略設計在 分治 上起到的效用。我們可以在不同的限界上下文選擇不同的架構模式,例如針對訂單的查詢與處理,選擇CQRS模式來分別處理同步與非同步場景;還可以針對核心領域與子領域重要性的不同,分別選擇領域模型(Domain Model)和事務指令碼(Transaction Script)模式,靈活地平衡開發成本與開發質量。在巨集觀層面,面對整個軟體系統,我們可以採用前後端分離與基於REST的微服務架構,保證系統具有一致的架構風格。
戰術設計階段
整個軟體系統被分解為多個限界上下文(或領域)後,我們就可以分而治之,對每個限界上下文進行戰術設計。領域驅動設計並不牽涉到技術層面的實現細節,在戰術層面,它主要應對的是領域的複雜性。領域驅動設計用以表示模型的主要要素包括:
- 值物件(Value Object)
- 實體(Entity)
- 領域服務(Domain Service)
- 領域事件(Domain Event)
- 資源庫(Repository)
- 工廠(Factory)
- 聚合(Aggregate)
- 應用服務(Application Service)
Eric Evans通過下圖勾勒了戰術設計諸要素之間的關係:

領域驅動設計圍繞著領域模型進行設計,通過 分層架構(Layered Architecture) 將領域獨立出來。表示領域模型的物件包括: 實體 、 值物件 和 領域服務 。 領域邏輯都應該封裝在這些物件中 。這一嚴格的設計原則可以避免業務邏輯滲透到領域層之外,導致技術實現與業務邏輯的混淆。在領域驅動設計的演進中,又引入了 領域事件 來豐富領域模型。
聚合是一種邊界,它可以封裝一到多個 實體 與 值物件 ,並維持該邊界範圍之內的業務完整性。在聚合中,至少包含一個實體,且只有實體才能作為 聚合根(Aggregate Root) 。注意,在領域驅動設計中,沒有任何一個類是單獨的聚合,因為聚合代表的是邊界概念,而非領域概念。極端情況下,一個聚合可能有且只有一個實體。
工廠和 資源庫 都是對領域物件生命週期的管理。前者負責領域物件的建立,往往用於封裝複雜或者可能變化的建立邏輯。後者則負責從存放資源的位置(資料庫、記憶體或者其他Web資源)獲取、新增、刪除或者修改領域物件。領域模型中的資源庫不應該暴露訪問領域物件的技術實現細節。
演進的領域驅動設計過程
戰略設計會控制和分解戰術設計的邊界與粒度,戰術設計則以實證角度驗證領域模型的有效性、完整性與一致性,進而以演進的方式對之前的戰略設計階段進行迭代,從而形成一種螺旋式上升的迭代設計過程,如下圖所示:

面對客戶的業務需求,由領域專家與開發團隊展開充分的交流,經過需求分析與知識提煉,獲得清晰的問題域。通過對問題域進行分析和建模,識別限界上下文,利用它劃分相對獨立的領域,再通過上下文對映建立它們之間的關係,輔以分層架構與六邊形架構劃分系統的邏輯邊界與物理邊界,界定領域與技術之間的界限。之後,進入戰術設計階段,深入到限界上下文內對領域進行建模,並以領域模型指導程式設計與編碼實現。若在實現過程中,發現領域模型存在重複、錯位或缺失時,再進而對已有模型進行重構,甚至重新劃分限界上下文。
兩個不同階段的設計目標是保持一致的,它們是一個連貫的過程,彼此之間又相互指導與規範,並最終保證 一個有效的領域模型和一個富有表達力的實現同時演進 。