這篇文章是 軟體架構編年史的一部分,這是一系列關于軟體架構的文章。在其中,我寫下了我在軟體架構方面學到的知識、我對它的看法以及我如何使用這些知識。如果您閱讀了本系列的前幾篇文章,這篇文章的内容可能會更有意義。
領域驅動設計由埃裡克·埃文斯 (Eric Evans) 在他 2003 年出版的精彩著作《領域驅動設計:解決軟體核心的複雜性》(Domain-Driven Design: Tackling Complexity in the Heart of Software)中提出。埃裡克· 埃文斯 (Eric Evans) 的書是将我們今天認為理所當然的許多軟體開發概念形式化的關鍵。
我無法在部落格文章中對 DDD 進行詳盡的回顧。與 DDD 相關的重要概念太多了。幸運的是,這也不是這裡的目标。然而,我要做的是列出 DDD 概念,我發現這些概念與我喜歡的代碼組織方式以及我對架構的看法更相關:構成功能開發基礎的系統範圍的概念。
在這篇文章中,我将寫下:
- 無處不在的語言
- 圖層
- 限界上下文
- 反腐敗層
- 共享核心
- 通用子域
無處不在的語言
軟體開發中經常出現的問題,圍繞着了解代碼,它是什麼,它做了什麼,它是如何做的,為什麼要這樣做......如果它使用的術語與術語不同,那麼了解代碼會更加複雜領域專家使用,例如,如果領域專家談論老年使用者而代碼談論監管者,這可能會在讨論應用程式時帶來很多混亂。然而,大多數這種歧義都可以通過正确命名類和方法來解決,使它們表達對象是什麼以及方法在域上下文中的作用。
使用通用語言的主要思想是使應用程式與業務保持一緻。這是通過在代碼中采用業務和技術之間的通用語言來實作的。語言的來源是公司的業務方,他們有需要實施的概念,但是術語是與公司的技術方協商的(這意味着業務方并不總是選擇最好的命名either) 的目标是建立一個通用的術語,可以被業務、技術和代碼本身使用而沒有任何歧義,一種無處不在的語言。代碼、類、方法、屬性和子產品命名必須與通用語言保持一緻。如果需要,值得重構代碼!
圖層
我已經在之前的文章中讨論過分層,但我發現此時重要的是要記住 DDD 辨別的層:
- 使用者界面
- 負責繪制使用者用來與應用程式互動的螢幕,并将使用者的輸入轉換為應用程式指令。需要注意的是,“使用者”可以是人類,也可以是連接配接到我們 API 的其他應用程式,這完全對應于EBI 架構中的 Boundary 對象 ;
- 應用層
- 編排域對象以執行使用者所需的任務:用例。它不包含業務邏輯。這與EBI 架構中的互動器有關,除了互動器是與 UI 或實體無關的任何對象,在這種情況下,應用程式層僅包含與用例相關的對象。這一層是應用服務所屬的層,因為它們是用例編排發生的容器,使用存儲庫、域服務、實體、值對象或任何其他域對象;
- 領域層
- 這是包含所有業務邏輯、領域服務、實體、事件和任何其他包含業務邏輯的對象類型的層。顯然和EBI的Entity對象類型有關。這是系統的核心。領域服務将包含不太适合實體的領域邏輯,通常會協調多個實體以完成某些領域操作;
- 基礎設施
- 支援以上層的技術能力,即。持久性或消息傳遞。
埃裡克·埃文斯,2003 年
限界上下文
在企業應用程式中,模型可以增長很多,并且在代碼庫上工作的團隊規模也會增長。這給我們帶來了兩個問題:
- 開發人員必須處理的代碼庫越大,認知負荷越大,了解代碼的難度就越大,是以引入錯誤和判斷錯誤的可能性就越大;
- 在同一個代碼庫上工作的開發人員越多,協調工作和對應用程式有共同的技術和領域願景就越困難。
換句話說,手頭的問題變得太大了。
大問題的通常解決方案是将其分解成更小的部分,而這正是“限界上下文”發揮作用的地方。
兩個子系統通常服務于截然不同的使用者群體
Eric Evans 2014,領域驅動設計參考
有界上下文定義模型的隔離部分應用的上下文。隔離可以通過解耦技術邏輯、代碼庫隔離、資料庫模式隔離以及團隊組織來實作。像往常一樣,我們隔離限界上下文的程度取決于實際情況:我們擁有的需求和可能性。
有趣的是,這并不是一個全新的概念。早在 1992 年,Ivar Jacobson 在他的書中就寫了關于子系統的文章,比 Eric Evans 早 11 年!
伊瓦爾·雅各布森,1992 年
那時他已經對這個主題有了很多非常具體的想法:
- 是以,該系統由許多子系統組成,這些子系統可以包含它們自己的子系統。在這種層次結構的底部是分析對象。是以,子系統是為進一步開發和維護建構系統的一種方式
- 子系統的任務是打包對象以降低複雜性。
- 與功能的特定部分有關的所有對象都将放在同一個子系統中
- 目的是在子系統内部實作強功能耦合,子系統之間實作弱耦合(現在稱為低耦合高内聚)
- [一個子系統]是以最好隻耦合到一個參與者,因為變化通常是由參與者引起的
- […] 首先将控制對象放在一個子系統中,然後将強耦合的實體對象和接口對象放在同一個子系統中
- 所有具有強互相功能耦合的對象将被放置在同一個子系統中 [...]一個對象的改變會導緻另一個對象的改變嗎?(這現在被稱為共同閉包原則——一起改變的類被打包在一起——由 Robert C. Martin 于 1996 年在他的論文“粒度”中發表,比 Ivar Jacobson 的書晚了 4 年)他們與同一個演員交流嗎?它們是否都依賴于第三方對象,例如接口對象或實體對象?一個對象是否對另一個對象執行多個操作?(這現在被稱為共同重用原則——一起使用的類被打包在一起——由 Robert C. Martin 在 1996 年的論文“粒度”中提出,比 Ivar Jacobson 的書晚了 4 年)
- 劃分的另一個标準是不同子系統之間的通信越少越好 (低耦合)
- 對于大型項目,子系統劃分可能有其他标準,例如:不同的開發組具有不同的能力或資源,是以可能需要相應地配置設定開發工作(這些組也可以在地理上分開)在分布式環境中,每個邏輯節點(SOA、Web 服務和微服務)可能需要一個子系統如果一個現有的産品可以在這個系統中使用,這可能被認為是一個子系統(我們系統依賴的庫,即ORM)
反腐敗層
反腐敗層基本上是兩個子系統之間的中間件。它用于隔離兩個子系統,使它們依賴于反腐敗層,而不是直接互相依賴。這樣,如果我們重構或完全替換其中一個子系統,我們隻需更新反腐敗層,而另一個子系統保持不變。
當我們有一個需要與遺留系統內建的新系統時,這尤其有用。為了不讓遺留結構決定我們如何設計新系統,我們建立了一個反腐敗層,使遺留子系統的 API 适應新子系統的需求。
它有3個主要關注點:
- 使子系統 API 适應用戶端子系統的需求;
- 在子系統之間轉換資料和指令;
- 根據需要在一個或多個方向上建立通信
埃裡克·埃文斯,2003 年
當我們不控制一個或所有子系統時使用這種技術更合乎邏輯,但當我們控制所有相關子系統時使用它也可能有意義,即使它們設計良好但簡單有非常不同的模型,我們希望防止從一個模型洩漏到另一個模型(更改一個子系統以比對另一個子系統的需求)。
共享核心
在某些情況下,盡管我們希望擁有完全隔離和解耦的元件,但某些域代碼由多個元件共享是有意義的。
這将允許元件保持彼此分離,盡管耦合到相同的共享代碼,共享核心。
例如,由一個元件觸發并由另一個或多個元件偵聽的事件就是這種情況。但服務接口甚至實體也可能是這種情況。
盡管如此,我們應該保持共享核心較小,并且在更改它時要非常小心,以免無意中破壞使用它的其他代碼。重要的是,在未與使用它的其他開發團隊協商的情況下,不得更改共享核心中的代碼。
通用子域
子域是域中隔離得很好的部分。通用子域是不特定于我們的應用程式的子域,它可以用于任何類似的應用程式。
是以,如果我們有一個應用程式,其中有一部分是關于金融的,也許我們可以在我們的應用程式中使用現有的金融庫。但無論哪種方式,即使我們不能使用現有的庫而需要建構我們自己的庫,如果它是一個通用的子域,它也不是我們的核心業務,它應該被認為是必不可少的,但不是至關重要的。它不是我們應用程式中最重要的部分,是以它不是我們最好的專家應該關注的地方,它甚至應該明顯在主要源代碼之外,可能與依賴管理工具一起安裝。
結論
我選擇在這裡采用的 DDD 概念主要是關于單一職責、低耦合、高内聚、隔離邏輯,以便我們的應用程式變得更加一緻、更容易和更快地改變和适應業務需求。
參考資料:https://herbertograca.com/2017/09/07/domain-driven-design/