天天看點

《DDD實戰課-歐創新》學習筆記領域、子域、核心域、通用域和支撐域限界上下文實體和值對象聚合和聚合根領域事件分層架構代碼目錄結構

領域、子域、核心域、通用域和支撐域

  1. DDD 的領域就是這個邊界内要解決的業務問題域。
  2. 領域可以進一步劃分為子領域。我們把劃分出來的多個子領域稱為子域,每個子域對應一個更小的問題域或更小的業務範圍。
  3. 領域模組化和微服務建設的過程和方法基本類似,其核心思想就是将問題域逐漸分解,降低業務了解和系統實作的複雜度。
  4. 在領域不斷劃分的過程中,領域會細分為不同的子域,子域可以根據自身重要性和功能屬性劃分為三類子域,它們分别是:核心域、通用域和支撐域。
    1. 決定産品和公司核心競争力的子域是核心域,它是業務成功的主要因素和公司的核心競争力。
    2. 沒有太多個性化的訴求,同時被多個子域使用的通用功能子域是通用域。
    3. 功能子域是必需的,但既不包含決定産品和公司核心競争力的功能,也不包含通用功能的子域,它就是支撐域。
  5. 領域的核心思想就是将問題域逐級細分,來降低業務了解和系統實作的複雜度。通過領域細分,逐漸縮小微服務需要解決的問題域,建構合适的領域模型,而領域模型映射成系統就是微服務了。
  6. 核心域、支撐域和通用域的主要目标是:通過領域劃分,區分不同子域在公司内的不同功能屬性和重要性,進而公司可對不同子域采取不同的資源投入和建設政策,其關注度也會不一樣。

限界上下文

  1. 通用語言定義上下文含義,限界上下文則定義領域邊界,以確定每個上下文含義在它特定的邊界内都具有唯一的含義,領域模型則存在于這個邊界之内。
  2. 在事件風暴過程中,通過團隊交流達成共識的,能夠簡單、清晰、準确描述業務涵義和規則的語言就是通用語言。
  3. DDD 分析和設計過程中的每一個環節都需要保證限界上下文内術語的統一,在代碼模型設計的時侯就要建立領域對象和代碼對象的一一映射,進而保證業務模型和代碼模型的一緻,實作業務語言與代碼語言的統一。
  4. 用來封裝通用語言和領域對象,提供上下文環境,保證在領域之内的一些術語、業務相關對象等(通用語言)有一個确切的含義,沒有二義性。這個邊界定義了模型的适用範圍,使團隊所有成員能夠明确地知道什麼應該在模型中實作,什麼不應該在模型中實作。
  5. 理論上限界上下文就是微服務的邊界。我們将限界上下文内的領域模型映射到微服務,就完成了從問題域到軟體的解決方案。

實體和值對象

  1. 在 DDD 中有這樣一類對象,它們擁有唯一辨別符,且辨別符在曆經各種狀态變更後仍能保持一緻。對這些對象而言,重要的不是其屬性,而是其延續性和辨別,對象的延續性和辨別會跨越甚至超出軟體的生命周期。我們把這樣的對象稱為實體。
    1. 實體的業務形态:領域模型中的實體是多個屬性、操作或行為的載體。實體和值對象是組成領域模型的基礎單元。
    2. 實體的代碼形态:在代碼模型中,實體的表現形式是實體類,這個類包含了實體的屬性和方法,通過這些方法實作實體自身的業務邏輯。在 DDD 裡,這些實體類通常采用充血模型,與這個實體相關的所有業務邏輯都在實體類的方法中實作,跨多個實體的領域邏輯則在領域服務中實作。
    3. 實體的運作形态:實體以 DO(領域對象)的形式存在,每個實體對象都有唯一的 ID。可以對一個實體對象進行多次修改,修改後的資料和原來的資料可能會大不相同。但是,由于它們擁有相同的 ID,它們依然是同一個實體。
    4. 實體的資料庫形态:,DDD 是先建構領域模型,針對實際業務場景建構實體對象和行為,再将實體對象映射到資料持久化對象。一個實體可能對應 0 個、1 個或者多個資料庫持久化對象。
  2. 在 DDD 中用來描述領域的特定方面,并且是一個沒有辨別符的對象,叫作值對象。值對象描述了領域中的一件東西,這個東西是不可變的,它将不同的相關屬性組合成了一個概念整體。
    1. 值對象的業務形态:實體是看得到、摸得着的實實在在的業務對象,實體具有業務屬性、業務行為和業務邏輯。而值對象隻是若幹個屬性的集合,隻有資料初始化操作和有限的不涉及修改資料的行為,基本不包含業務邏輯。
    2. 值對象的代碼形态:如果值對象是單一屬性,則直接定義為實體類的屬性;如果值對象是屬性集合,則把它設計為 Class 類,Class 将具有整體概念的多個屬性歸集到屬性集合,這樣的值對象沒有 ID,會被實體整體引用。
    3. 值對象的運作形态:值對象嵌入到實體的話,有這樣兩種不同的資料格式,也可以說是兩種方式,分别是屬性嵌入的方式和序列化大對象的方式。
    4. 值對象的資料庫形态:在領域模組化時,我們可以将部分對象設計為值對象,保留對象的業務涵義,同時又減少了實體的數量;在資料模組化時,我們可以将值對象嵌入實體,減少實體表的數量,簡化資料庫設計。
  3. 值對象采用序列化大對象的方法簡化了資料庫設計,減少了實體表的數量,可以簡單、清晰地表達業務概念。這種設計方式雖然降低了資料庫設計的複雜度,但卻無法滿足基于值對象的快速查詢,會導緻搜尋值對象屬性值變得異常困難。
  4. 值對象采用屬性嵌入的方法提升了資料庫的性能,但如果實體引用的值對象過多,則會導緻實體堆積一堆缺乏概念完整性的屬性,這樣值對象就會失去業務涵義,操作起來也不友善。
  5. 值對象和實體在某些場景下可以互換。值對象在某些場景下有很好的價值,但是并不是所有的場景都适合值對象。同樣的對象在不同的場景下,可能會設計出不同的結果。

聚合和聚合根

  1. 聚合的設計原則
    1. 在一緻性邊界内模組化真正的不變條件。聚合用來封裝真正的不變性,而不是簡單地将對象組合在一起。聚合内有一套不變的業務規則,各實體和值對象按照統一的業務規則運作,實作對象資料的一緻性,邊界之外的任何東西都與該聚合無關,這就是聚合能實作業務高内聚的原因。
    2. 設計小聚合。如果聚合設計得過大,聚合會因為包含過多的實體,導緻實體之間的管理過于複雜,高頻操作時會出現并發沖突或者資料庫鎖,最終導緻系統可用性變差。而小聚合設計則可以降低由于業務過大導緻聚合重構的可能性,讓領域模型更能适應業務的變化。
    3. 通過唯一辨別引用其它聚合。聚合之間是通過關聯外部聚合根 ID 的方式引用,而不是直接對象引用的方式。外部聚合的對象放在聚合邊界内管理,容易導緻聚合的邊界不清晰,也會增加聚合之間的耦合度。
    4. 在邊界之外使用最終一緻性。聚合内資料強一緻性,而聚合之間資料最終一緻性。
    5. 通過應用層實作跨聚合的服務調用。
  2. 聚合的特點:高内聚、低耦合,它是領域模型中最底層的邊界,可以作為拆分微服務的最小機關,但我不建議你對微服務過度拆分。但在對性能有極緻要求的場景中,聚合可以獨立作為一個微服務,以滿足版本的高頻釋出和極緻的彈性伸縮能力。
  3. 聚合根的特點:聚合根是實體,有實體的特點,具有全局唯一辨別,有獨立的生命周期。一個聚合隻有一個聚合根,聚合根在聚合内對實體和值對象采用直接對象引用的方式進行組織和協調,聚合根與聚合根之間通過 ID 關聯的方式實作聚合之間的協同。
  4. 實體的特點:有 ID 辨別,通過 ID 判斷相等性,ID 在聚合内唯一即可。狀态可變,它依附于聚合根,其生命周期由聚合根管理。實體一般會持久化,但與資料庫持久化對象不一定是一對一的關系。實體可以引用聚合内的聚合根、實體和值對象。
  5. 值對象的特點:無 ID,不可變,無生命周期,用完即扔。值對象之間通過屬性值判斷相等性。它的核心本質是值,是一組概念完整的屬性組成的集合,用于描述實體的狀态和特征。值對象盡量隻引用值對象。

領域事件

  1. 領域事件驅動設計可以切斷領域模型之間的強依賴關系,事件釋出完成後,釋出方不必關心後續訂閱方事件處理是否成功,這樣可以實作領域模型的解耦,維護領域模型的獨立性和資料的一緻性。在領域模型映射到微服務系統架構時,領域事件可以解耦微服務,微服務之間的資料不必要求強一緻性,而是基于事件的最終一緻性。
  2. 當領域事件發生在微服務内的聚合之間,領域事件發生後完成事件實體建構和事件資料持久化,釋出方聚合将事件釋出到事件總線,訂閱方接收事件資料完成後續業務操作。不一定需要引入消息中間件。
  3. 跨微服務的領域事件會在不同的限界上下文或領域模型之間實作業務協作,其主要目的是實作微服務解耦,減輕微服務之間實時服務通路的壓力。
  4. 領域事件處理包括:事件建構和釋出、事件資料持久化、事件總線、消息中間件、事件接收和處理等。
    1. 事件基本屬性至少包括:事件唯一辨別、發生時間、事件類型和事件源,其中事件唯一辨別應該是全局唯一的,以便事件能夠無歧義地在多個限界上下文中傳遞。事件基本屬性主要記錄事件自身以及事件發生背景的資料。

      另外事件中還有一項更重要,那就是業務屬性,用于記錄事件發生那一刻的業務資料,這些資料會随事件傳輸到訂閱方,以開展下一步的業務操作。

    2. 事件資料持久化可用于系統之間的資料對賬,或者實作釋出方和訂閱方事件資料的審計。當遇到消息中間件、訂閱方系統當機或者網絡中斷,在問題解決後仍可繼續後續業務流轉,保證資料的一緻性。

      事件資料持久化有兩種方案,

      • 持久化到本地業務資料庫的事件表中,利用本地事務保證業務和事件資料的一緻性。
      • 持久化到共享的事件資料庫中。這裡需要注意的是:業務資料庫和事件資料庫不在一個資料庫中,它們的資料持久化操作會跨資料庫,是以需要分布式事務機制來保證業務和事件資料的強一緻性,結果就是會對系統性能造成一定的影響。
    3. 事件總線是實作微服務内聚合之間領域事件的重要元件,它提供事件分發和接收等服務。事件總線是程序内模型,它會在微服務内聚合之間周遊訂閱者清單,采取同步或異步的模式傳遞資料。事件分發流程大緻如下:
      • 如果是微服務内的訂閱者(其它聚合),則直接分發到指定訂閱者;
      • 如果是微服務外的訂閱者,将事件資料儲存到事件庫(表)并異步發送到消息中間件;
      • 如果同時存在微服務内和外訂閱者,則先分發到内部訂閱者,将事件消息儲存到事件庫(表),再異步發送到消息中間件。
    4. 跨微服務的領域事件大多會用到消息中間件,實作跨微服務的事件釋出和訂閱。
    5. 微服務訂閱方在應用層采用監聽機制,接收消息隊列中的事件資料,完成事件資料的持久化後,就可以開始進一步的業務處理。領域事件處理可在領域服務中實作。

分層架構

《DDD實戰課-歐創新》學習筆記領域、子域、核心域、通用域和支撐域限界上下文實體和值對象聚合和聚合根領域事件分層架構代碼目錄結構

三層架構向DDD分層架構演進,主要發生在業務邏輯層和資料通路層。

DDD分層架構在使用者接口層引入了DTO,給前端提供了更多的可使用資料和更高的展示靈活性。

DDD分層架構對三層架構的業務邏輯層進行了更清晰的劃分,改善了三層架構核心業務邏輯混亂,代碼改動互相影響大的情況。DDD分層架構将業務邏輯層的服務拆分到了應用層和領域層。應用層快速響應前端的變化,領域層實作領域模型的能力。

另外一個重要的變化發生在資料通路層和基礎層之間。三層架構資料通路采用DAO方式;DDD分層架構的資料庫等基礎資源通路,采用了倉儲(Repository)設計模式,通過依賴倒置實作各層對基礎資源的解耦。

倉儲又分為兩部分:倉儲接口和倉儲實作。倉儲接口放在領域層中,倉儲實作放在基礎層。原來三層架構通用的第三方工具包、驅動、Common、Utility、Config等通用的公共的資源類統一放到了基礎層。

代碼目錄結構

《DDD實戰課-歐創新》學習筆記領域、子域、核心域、通用域和支撐域限界上下文實體和值對象聚合和聚合根領域事件分層架構代碼目錄結構
  1. interfaces:使用者接口層,主要存放使用者接口層與前端互動、展現資料相關的代碼。用來處理使用者發送的Restful請求,解析使用者輸入的配置檔案,并将資料傳遞給Application層。資料的組裝、資料傳輸格式以及Facade接口等代碼都會放在這一層目錄裡。
    1. assembler:實作DTO與領域對象之間的互相轉換和資料交換。(DTO接收前端傳遞的參數)
    2. dto:資料傳輸的載體,内部不存在任何業務邏輯,可以通過DTO把内部的領域對象與外界隔離。
    3. Facade:提供較粗粒度的調用接口,将使用者請求委派給一個或多個應用服務進行處理。
  2. Application:應用層,主要存在應用層服務組合和編排相關的代碼。基于微服務内的領域服務或外部微服務的應用服務完成服務的編排群組合,為使用者接口層提供各種資料展現支援服務。應用服務和事件代碼會放在這一層目錄裡。
    1. event:事件,主要存放事件相關代碼,包括兩個子目錄:publish和subscribe。前者主要存放事件釋出相關代碼,後者主要存放事件訂閱相關代碼(事件處理相關的核心業務邏輯在領域層實作)。
    2. service:應用服務,對多個領域服務或外部應用服務進行封裝、編排群組合,對外提供粗粒度的服務。
  3. Domain:領域層,主要存放領域層核心業務邏輯相關的代碼。可以包含多個聚合代碼包,它們共同實作領域模型的核心業務邏輯。聚合以及聚合内的實體、方法、領域服務和事件等代碼會放在這一層目錄裡。
    1. Aggregate:聚合,聚合軟體包的根目錄,可以根據實際項目的聚合名稱明明。在聚合内定義聚合根、實體和值對象以及領域服務之間的關系和邊界。聚合内實作高内聚的業務邏輯,代碼可以獨立拆分為微服務。
    2. Entity:實體,存放聚合根、實體、值對象以及工廠模式相關代碼。實體類采用充血模型,統一實體相關的業務邏輯都在實體類代碼中實作。跨實體的業務邏輯代碼在領域服務中實作。
    3. Event:事件,存放事件實體以及事件活動相關的業務邏輯代碼。
    4. Service:領域服務,存放領域服務代碼。一個領域服務是多個實體組合出來的一段業務邏輯。可以将聚合内所有領域服務都放在一個領域服務類中,也可以把每一個領域服務設計為一個類。
    5. Repository:倉儲,存放所在聚合的查詢或持久化領域對象的代碼,通常包括倉儲接口和倉儲實作方法。一個聚合對應一個倉儲。
  4. infrastructure:基礎層,主要存放基礎資源服務相關的代碼,為其他各層提供的通用技術能力、三方軟體包、資料庫服務、配置和基礎資源服務的代碼都會放在這一層目錄裡。
    1. Config:主要存放配置相關代碼。
    2. Util:主要存放平台、開發架構、消息、資料庫、緩存、檔案、總線、網關、第三方類庫、通用算法等基礎代碼。可以為不同的資源類别建立不同的子目錄。

繼續閱讀