DDD 中聚合、聚合根的含義以及作用

聚合與聚合根的含義

聚合: 聚合往往是一些實體爲了某項業務而聚類在一起形成的集合 , 舉個例子, 社會是由一個個的個體組成的,象徵着我們每一個人。隨着社會的發展,慢慢出現了社團、機構、部門等組織,我們開始從個人變成了組織的一員,大家可以協同一致的工作,朝着一個最大的目標前進,發揮出更大的力量。領域模型內的實體和值對象就好比個體,而能讓實體和值對象協同工作的組織就是聚合,它用來確保這些領域對象在實現共同的業務邏輯時,能保證數據的一致性。可以這麼理解,聚合就是由業務和邏輯緊密關聯的實體和值對象組合而成的,聚合是數據修改和持久化的基本單元,每一個聚合對應一個倉儲,實現數據的持久化。

聚合根 : 前面有提到, 聚合有一個聚合根和上下文邊界,這個邊界根據業務單一職責和高內聚原則,定義了聚合內部應該包含哪些實體和值對象,而聚合之間的邊界是松耦合的。按照這種方式設計出來的微
服務很自然就是 “高內聚、低耦合” 的。聚合根的主要目的是爲了避免由於複雜數據模型缺少統一的業務規則控制,而導致聚合、實體之間數據不一致性的問題。傳統數據模型中的每一個實體都是對等的,如果任由實體進行無控制地調用和數據修改,很可能會導致實體之間數據邏輯的不一致。而如果採用鎖的方式則會增加軟件的複雜度,也會降低系統的性能。

如果把聚合比作組織,那聚合根就是這個組織的負責人。聚合根也稱爲根實體,它不僅是實體,還是聚合的管理者。首先它作爲實體本身,擁有實體的屬性和業務行爲,實現自身的業務邏輯。其次它作爲聚合的管理者,在聚合內部負責協調實體和值對象按照固定的業務規則協同完成共同的業務邏輯。

最後在聚合之間,它還是聚合對外的接口人,以聚合根 ID 關聯的方式接受外部任務和請求,在上下文內實現聚合之間的業務協同。也就是說,聚合之間通過聚合根 ID 關聯引用,如果需要訪問其它聚合的實體,就要先訪問聚合根,再導航到聚合內部實體,外部對象不能直接訪問聚合內實體。

怎樣設計聚合?

DDD 領域建模通常採用事件風暴,它通常採用用例分析、場景分析和用戶旅程分析等方法,通過頭腦風暴列出所有可能的業務行爲和事件,然後找出產生這些行爲的領域對象,並梳理領域對象之間的關係,找出聚合根,找出與聚合根業務緊密關聯的實體和值對象,再將聚合根、實體和值對象組合,構建聚合。

下面我們以保險的投保業務場景爲例,看一下聚合的構建過程主要都包括哪些步驟。

第 1 步:採用事件風暴,根據業務行爲,梳理出在投保過程中發生這些行爲的所有的實體和值對象,比如投保單、標的、客戶、被保人等等。

第 2 步:從衆多實體中選出適合作爲對象管理者的根實體,也就是聚合根。判斷一個實體是否是聚合根,你可以結合以下場景分析:是否有獨立的生命週期?是否有全局唯一 ID?是否可以創建或修改其它對象?是否有專門的模塊來管這個實體。圖中的聚合根分別是投保單和客戶實體。

第 3 步:根據業務單一職責和高內聚原則,找出與聚合根關聯的所有緊密依賴的實體和值對象。構建出 1 個包含聚合根(唯一)、多個實體和值對象的對象集合,這個集合就是聚合。在圖中我們構建了客戶和投保這兩個聚合。

第 4 步:在聚合內根據聚合根、實體和值對象的依賴關係,畫出對象的引用和依賴模型。這裏我需要說明一下:投保人和被保人的數據,是通過關聯客戶 ID 從客戶聚合中獲取的,在投保聚合裏它們是投保單的值對象,這些值對象的數據是客戶的冗餘數據,即使未來客戶聚合的數據發生了變更,也不會影響投保單的值對象數據。從圖中我們還可以看出實體之間的引用關係,比如在投保聚合裏投保單聚合根引用了報價單實體,報價單實體則引用了報價規則子實體。

第 5 步:多個聚合根據業務語義和上下文一起劃分到同一個限界上下文內。這就是一個聚合誕生的完整過程了。

聚合的一些設計原則我們不妨先看一下**《實現領域驅動設計》**一書中對聚合設計原則的描述,原文是有點不太好理解的,我來給你解釋一下。

  1. 在一致性邊界內建模真正的不變條件。聚合用來封裝真正的不變性,而不是簡單地將對象組合在一起。聚合內有一套不變的業務規則,各實體和值對象按照統一的業務規則運行,實現對象數據的一致性,邊界之外的任何東西都與該聚合無關,這就是聚合能實現業務高內聚的原因。

  2. 設計小聚合。如果聚合設計得過大,聚合會因爲包含過多的實體,導致實體之間的管理過於複雜,高頻操作時會出現併發衝突或者數據庫鎖,最終導致系統可用性變差。而小聚合設計則可以降低由於業務過大導致聚合重構的可能性,讓領域模型更能適應業務的變化。

  3. 通過唯一標識引用其它聚合。聚合之間是通過關聯外部聚合根 ID 的方式引用,而不是直接對象引用的方式。外部聚合的對象放在聚合邊界內管理,容易導致聚合的邊界不清晰,也會增加聚合之間的耦合度。

  4. 在邊界之外使用最終一致性。聚合內數據強一致性,而聚合之間數據最終一致性。在一次事務中,最多隻能更改一個聚合的狀態。如果一次業務操作涉及多個聚合狀態的更改,應採用領域事件的方式異步修改相關的聚合,實現聚合之間的解耦(相關內容我會在領域事件部分詳解)。

  5. 通過應用層實現跨聚合的服務調用。爲實現微服務內聚合之間的解耦,以及未來以聚合爲單位的微服務組合和拆分,應避免跨聚合的領域服務調用和跨聚合的數據庫表關聯。上面的這些原則是 DDD 的一些通用的設計原則,還是那句話:“適合自己的纔是最好的。” 在系統設計過程時,你一定要考慮項目的具體情況,如果面臨使用的便利性、高性能要求、技術能力缺失和全局事務管理等影響因素,這些原則也並不是不能突破的,總之一切以解決實際問題爲出發點。

聚合、聚合根、實體和值對象的聯繫和區別

聚合的特點:高內聚、低耦合,它是領域模型中最底層的邊界,可以作爲拆分微服務的最小單位,但我不建議你對微服務過度拆分。但在對性能有極致要求的場景中,聚合可以獨立作爲一個微服務,以滿足版本的高頻發佈和極致的彈性伸縮能力。

一個微服務可以包含多個聚合,聚合之間的邊界是微服務內天然的邏輯邊界。有了這個邏輯邊界,在微服務架構演進時就可以以聚合爲單位進行拆分和組合了,微服務的架構演進也就不再是一件難事了。

聚合根的特點:聚合根是實體,有實體的特點,具有全局唯一標識,有獨立的生命週期。一個聚合只有一個聚合根,聚合根在聚合內對實體和值對象採用直接對象引用的方式進行組織和協調,聚合根與聚合根之間通過 ID 關聯的方式實現聚合之間的協同。

**實體的特點:**有 ID 標識,通過 ID 判斷相等性,ID 在聚合內唯一即可。狀態可變,它依附於聚合根,其生命週期由聚合根管理。實體一般會持久化,但與數據庫持久化對象不一定是一對一的關係。實體可以引用聚合內的聚合根、實體和值對象。

**值對象的特點:**無 ID,不可變,無生命週期,用完即扔。值對象之間通過屬性值判斷相等
性。它的核心本質是值,是一組概念完整的屬性組成的集合,用於描述實體的狀態和特徵。
值對象儘量只引用值對象。

站在巨人的肩膀上

  1. 極客時間歐創新 DDD 實踐課

  2. 田園裏的蟋蟀我的領域驅動設計之路

  3. dax.net 領域驅動設計實踐

出處:https://www.cnblogs.com/Courage129/p/14861100.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/12HbpQ7tpi5n4Dwgmmqgcw