領域驅動設計概覽

領域驅動設計(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資源)獲取、添加、刪除或者修改領域對象。領域模型中的資源庫不應該暴露訪問領域對象的技術實現細節。

演進的領域驅動設計過程

戰略設計會控制和分解戰術設計的邊界與粒度,戰術設計則以實證角度驗證領域模型的有效性、完整性與一致性,進而以演進的方式對之前的戰略設計階段進行迭代,從而形成一種螺旋式上升的迭代設計過程,如下圖所示:

領域驅動設計概覽

面對客戶的業務需求,由領域專家與開發團隊展開充分的交流,經過需求分析與知識提煉,獲得清晰的問題域。通過對問題域進行分析和建模,識別限界上下文,利用它劃分相對獨立的領域,再通過上下文映射建立它們之間的關係,輔以分層架構與六邊形架構劃分系統的邏輯邊界與物理邊界,界定領域與技術之間的界限。之後,進入戰術設計階段,深入到限界上下文內對領域進行建模,並以領域模型指導程序設計與編碼實現。若在實現過程中,發現領域模型存在重複、錯位或缺失時,再進而對已有模型進行重構,甚至重新劃分限界上下文。

兩個不同階段的設計目標是保持一致的,它們是一個連貫的過程,彼此之間又相互指導與規範,並最終保證一個有效的領域模型和一個富有表達力的實現同時演進。


分享到:


相關文章: