領域驅動設計(DDD):三層架構到DDD架構演化

2023-08-24 06:00:36

三層架構的問題

在前文中,我從基礎程式碼的角度探討了如何運用領域驅動設計(DDD)來實現高內聚低耦合的程式碼。本篇文章將從專案架構的角度,繼續探討三層架構與DDD之間的演化過程,以及DDD如何優化架構的問題。

三層架構作為一種常見的軟體架構模式,將應用程式分為展示層、業務邏輯層和資料存取層,具有以下優點:

  1. 分離關注點: 三層架構將不同功能模組分隔開,使每個模組專注於特定任務,降低了程式碼複雜性。
  2. 可維護性和可延伸性: 不同層之間的鬆耦合使得對某一層的修改不會影響其他層,有助於系統的維護和擴充套件。
  3. 可測試性: 不同層的獨立性使得單元測試和整合測試更容易實現,有助於確保程式碼質量。

然而,儘管三層架構有其優點,在處理複雜業務時,三層架構也可能面臨一些問題。具體有:

  1. 業務邏輯分散: 在三層架構中,業務邏輯往往分散在不同的層中,導致業務流程難以理清,影響了程式碼的可讀性和可維護性。
  2. 領域模型貧血: 三層架構中,領域邏輯和資料儲存混合在一起,導致領域模型的業務方法受限,難以表達複雜的業務規則。
  3. 過度依賴資料儲存: 不同層之間對資料儲存的依賴緊密,當切換資料儲存媒介時,需要大量修改程式碼。

具體具體示意如下圖:

隨著業務的不斷複雜化,service層變得越來越龐大,服務之間的參照也變得越來越混亂,這為專案帶來了風險和不確定性。

三層架構演化到DDD

在三層架構的演化過程中,有時會嘗試引入額外的"Manager"層來管理服務層的功能,但這並不是DDD所倡導的概念。在DDD中,更加關注領域的劃分和內聚,以及如何將領域模型與業務需求對應起來。

一般情況下,三層架構的問題可以通過引入領域驅動設計來解決。在以下內容中,我們將重點放在如何將DDD思想融入現有的三層架構中,以實現更高內聚、更低耦合的程式碼架構。

  1. 領域的劃分: DDD將service層按業務場景劃分成不同的領域,每個領域內包含實體、值物件、聚合根等元素。
  2. 內聚的領域: 在領域內,業務儘量內聚,避免領域之間的耦合。每個領域內部可以根據需要建立更細粒度的子域,進一步提高內聚性。
  3. 應用層的組合: 引入一個Application層,將領域內的service組合呼叫,形成業務服務,避免服務之間直接參照,降低耦合度。

經過我們的修改,三層架構可以(組合和聚合)演進到右側架構模式,通過這種方式,我們能夠更好地組織和管理程式碼,實現領域內高內聚低耦合的目標。

程式碼組織

在進行了基礎程式碼的優化後,接下來我們將探討如何根據領域驅動設計(DDD)思想來優化整體程式碼架構。經過前面的分析,我們大致瞭解了DDD的專案結構,並且明確了每個層次的職責。現在,讓我們更詳細地探討每個層次的程式碼組織。

  1. Domain層: 該層是DDD的核心,包含了領域物件、值物件、聚合根等,以及領域內的業務邏輯和規則。在領域內,業務邏輯應該儘量內聚,領域間應該儘量鬆耦合。
  2. 基礎架構層: 包括倉儲實現,快取實現,佇列實現等等系列系統需要的基礎能力,這一層的目的是為整個專案提供基礎支援。
  3. Application層: 這一層用於組合領域內的服務,形成具體的應用用例。它不包含具體的業務邏輯,只是通過呼叫領域內的服務來實現具體的功能。
  4. UI層: UI層負責展示資料和接收使用者輸入,它不包含業務邏輯,只是通過呼叫Application層來觸發業務流程。

具體架構類似如下圖:

當將領域驅動設計(DDD)引入到專案架構中,程式碼的組織方式會有所不同,以更好地體現領域的業務邏輯和關係。讓我們詳細解釋每個層次的程式碼組織,為了保證閱讀的連貫性,我們從參照的最低層(domain層)開始說起

Domain層:

Domain層是DDD的核心,它包含了領域物件、值物件、聚合根等,以及領域內的業務邏輯和規則。在這一層,你應該更關注領域的核心業務,讓程式碼更貼近業務現實。以下是一些程式碼組織的思路:

  • 實體和值物件: 領域物件可以分為實體和值物件。實體是有唯一標識的物件,通常代表業務概念;值物件是沒有唯一標識的物件,它們通常用來描述實體的屬性。在這一層,你應該為每個實體和值物件定義其屬性和行為。

  • 聚合和聚合根: 將相關聯的實體和值物件組合成聚合,聚合根是聚合的入口。聚合根負責保持聚合內的一致性,它是領域模型的核心部分。

  • 領域服務: 領域服務用於處理一些領域範圍內的業務邏輯,它們不屬於任何具體的實體或值物件。將這些邏輯封裝在領域服務中可以使領域模型更加清晰。

  • 通用工具類: 通用工具類是一些與領域相關的輔助方法,可以被領域內的多個實體或值物件使用。將通用工具類放在領域層可以更方便地供領域內的實體使用,避免在其他層重複實現。

在domain域內提供,entity(實體),valueobj(值物件),AggregateRoot(聚合根),倉儲介面(IRepository),事件驅動相關(event)

基礎架構層:

在基礎架構層,我們主要關注與系統的基礎設施和通用功能。這一層包含倉儲模式和介面介面卡,用於封裝資料儲存操作併為領域層提供統一的資料存取介面。通用工具類也可以在這裡定義和實現,為領域層和應用層提供通用的輔助功能。基礎架構層的程式碼組織通常如下:

  • 第三方庫封裝: 如果專案使用了第三方庫或框架,你可以在基礎架構層進行封裝,以便在其他層中更方便地使用。封裝可以包括對第三方庫的初始化、設定以及封裝特定的操作介面。

  • 倉儲介面和介面卡: 在基礎架構層中定義倉儲介面,以及不同資料儲存媒介的介面卡實現。這樣可以將資料存取操作與領域層解耦,同時實現資料儲存的切換。

  • 中介軟體實現: 如果系統使用了中介軟體,如快取、訊息佇列等,你可以在基礎架構層實現中介軟體的具體操作。這有助於將與中介軟體相關的邏輯隔離在基礎架構層中。

  • 事件驅動實現: 如果系統採用了事件驅動的架構,你可以在基礎架構層實現事件的釋出與訂閱機制,以及事件的處理邏輯。

如上圖,使用redis提供快取,使用kafka提供訊息佇列,使用guava提供事件驅動,倉儲層負責實現倉儲功能

Application層:

Application層用於組合領域內的服務,形成具體的應用用例。它不包含具體的業務邏輯,而是通過呼叫領域內的服務來實現功能。在這一層,你可以有以下的組織方式:

  • 應用服務: 應用服務負責處理使用者請求,協調領域內的服務,形成具體的用例。每個應用服務通常對應一個使用者操作,它們應該是輕量級的,不涉及具體的業務邏輯。
  • DTO(資料傳輸物件): DTO負責承接前端傳入的資料,為領域層轉換為對應的業務引數。它們將使用者輸入的資料進行封裝,以便傳遞給領域層進行處理。
  • 資料轉換: 在應用層,你可能需要將領域物件轉換為DTO,用於與UI層進行資料互動。資料轉換負責將領域物件的資料對映到DTO中,只暴露需要的資料欄位。

UI層:

UI層負責展示資料和接收使用者輸入,它不包含業務邏輯,只是通過呼叫Application層來觸發業務流程。在這一層,主要形式有 api,job和檢視頁面等等

總結

當我們將三層架構向DDD演進時,我們逐步重塑我們的程式碼組織,讓領域層成為核心,包含實體、值物件、聚合根和領域服務,以最佳方式捕捉業務邏輯和規則。基礎架構層負責提供通用能力,如倉儲實現、中介軟體封裝等,為領域層提供支援。應用層負責將領域內的服務組合成具體的應用用例,通過呼叫領域服務實現功能。最後,UI層負責與使用者互動,通過呼叫應用層觸發業務流程。這種結構使得不同層次之間的耦合度降低,程式碼變得更清晰、可維護和可延伸。

在我們演進的過程中,重要的是不僅僅是技術層面的變化,更是對於業務的深入理解和把握。DDD不僅僅是一種架構模式,更是一種用於探索和應對複雜業務的方法論。通過將DDD思想融入我們的架構設計中,我們能夠更好地應對日益複雜的業務需求,使得我們的系統更具彈性和適應性,從而為日後面臨複雜的業務奠定基礎。這雖然充滿挑戰,但也必將為我們帶來更大的成長和收穫。
在下一講主要講下DDD在實際落地中碰到的問題和解決方案,歡迎關注。