天天看點

DDD領域驅動設計實戰-聚合(Aggregate)和聚合根(AggregateRoot)

實體(Entity)和值對象(ValueObject)組成聚合(Aggregate),再根據業務将多個聚合劃定到同一限界上下文(Bounded Context),并在限界上下文内完成領域模組化。

聚合隻是單純将一些共享父類、密切關聯的對象聚內建一個對象樹嗎?

如果是這樣,對于存在于這個樹中的對象有沒有一個實用的數目限制?

既然一個聚合可以引用另一個聚合,是否可以深度周遊下去,并且在此過程中修改對象?

聚合的不變條件和一緻性邊界究竟什麼意思?

1 聚合

實體一般對應業務對象,具有業務屬性和業務行為

值對象主要是屬性集合,描述實體的狀态和特征

但都隻是個體化對象,其行為表現出的是個體能力。

領域模型内的實體和值對象好比個體,而能讓實體和值對象協同工作的組織就是聚合,用來確定這些領域對象在實作共同的業務邏輯時,能保證資料的一緻性。

聚合就是由業務和邏輯緊密關聯的實體和值對象組合而成,聚合是資料修改和持久化的基本單元,每個聚合對應一個倉儲,實作資料的持久化。

聚合有一個聚合根和上下文邊界:

該邊界根據業務單一職責和高内聚原則,定義了聚合内部應該包含哪些實體和值對象

聚合之間的邊界是松耦合的

按這種方式設計出來的微服務自然就是高内聚、低耦合。

聚合屬領域層,領域層包含多個聚合,共同實作核心業務邏輯。

聚合内實體以充血模型實作個體業務能力,以及業務邏輯的高内聚。跨多個實體的業務邏輯通過領域服務來實作,跨多個聚合的業務邏輯通過應用服務來實作。比如

有的業務需同一聚合的A和B兩個實體共同完成,就可将這段業務邏輯用領域服務實作

有的業務需聚合C和聚合D中的兩個服務共同完成,使用應用服務來組合這倆服務

2 聚合根

為避免由于複雜資料模型缺少統一的業務規則控制,而導緻聚合、實體之間資料不一緻。

傳統資料模型中的每一個實體都是同級對等,若任由實體無管控地調用資料修改,可能導緻實體之間資料邏輯的不一緻。而若使用鎖則會增加代碼複雜度,降低系統性能。

若把聚合比作組織,則聚合根就是該組織負責人。

聚合根也稱為根實體,它不僅是實體,還是聚合的管理者。

作為實體,擁有實體的屬性和業務行為,實作自身的業務邏輯

作為聚合的管理者,在聚合内部負責協調實體和值對象按照固定業務規則協同完成共同的業務邏輯

在聚合間,它還是聚合對外的接口人,以聚合根ID關聯的方式接受外部任務和請求,在上下文内實作聚合之間的業務協同。即聚合間通過聚合根ID關聯引用,若需要通路其它聚合的實體,就要先通路聚合根,再導航到聚合内部實體,外部對象不能直接通路聚合内實體。

電商裡面比較典型的幾個聚合根,比如:庫存、商品、訂單等。

以訂單為例,訂單在聚合裡是聚合根,與訂單關聯的有訂單明細和收貨位址:

訂單明細包括商品ID、商品名稱、價格及數量等資訊。由于訂單明細是多個,它是一個集合,它被設計為實體,被訂單引用

訂單隻有一個收貨位址,收貨位址的值源于你的個人中心維護的收貨位址,收貨位址隻能被整體替換,是以設計為值對象

設計聚合

DDD領域模組化通常采用事件風暴,采用用例分析、場景分析和使用者旅程分析等方法,通過頭腦風暴列出所有可能的業務行為和事件,然後找出産生這些行為的領域對象,并梳理領域對象之間的關系,找出聚合根,找出與聚合根業務緊密關聯的實體和值對象,再将聚合根、實體和值對象組合,建構聚合。

保險的投保業務場景

DDD領域驅動設計實戰-聚合(Aggregate)和聚合根(AggregateRoot)

采用事件風暴,根據業務行為,梳理出在投保過程中發生這些行為的所有的實體和值對象,如投保單、标的、客戶、被保人

從衆多實體中選出适合作為對象管理者的根實體(聚合根)。判斷一個實體是否是聚合根,可分析:是否有獨立生命周期?是否有全局唯一ID?是否可建立或修改其它對象?是否有專門子產品管這個實體。即投保單和客戶聚合根

根據業務單一職責和高内聚原則,找出與聚合根關聯的所有緊密依賴的實體和值對象。建構出 1 個包含聚合根(唯一)、多個實體和值對象的對象集合,這個集合就是聚合。即客戶、投保聚合

在聚合内根據聚合根、實體和值對象的依賴關系,畫出對象的引用和依賴模型。

投保人和被保人的資料,是通過關聯客戶ID從客戶聚合中擷取的,在投保聚合裡它們是投保單的值對象,這些值對象的資料是客戶的備援資料,即使未來客戶聚合的資料發生了變更,也不會影響投保單的值對象資料。 從圖還可看出實體之間的引用關系,比如在投保聚合裡投保單聚合根引用了報價單實體,報價單實體則引用了報價規則子實體。

多個聚合根據業務語義和上下文一起劃分到同一個限界上下文内。

設計原則

要從限界上下文中發現聚合,我們需要了解模型中真正的不變條件。這樣才能決定什麼樣的對象可以放在一個聚合。

不變條件表示一個業務規則,該規則應該總是保持一緻。存在多種類型的一緻性:

事務一緻性

要求立即性和原子性

最終一緻性

在讨論不變條件時,我們讨論的是事務一緻性。我們可能有以下不變條件:

當a等于2, b等于3時,c必定等于5。根據這條規則,如果c不為5,那麼我們便違背了系統的不變條件。為了保持c的一緻性,我們應該在模型中為這些屬性設計了 一個邊界:

聚合邊界之内的所有内容組成了一套不變的業務規則,任何操作都不能違背這些規則。邊界之外的任何東西與該聚合都是不相關的。是以,聚合表達 了與事務一緻性邊界相同的意思(在該例中,AggregateTypel擁有3個int類型的屬 性,任何聚合都可擁有不同類型的屬性)。

聚合用來封裝真正的不變性,而非簡單地組合對象。聚合内有一套不變的業務規則,各實體和值對象按統一業務規則運作以實作對象資料的一緻性,邊界之外的任何東西都與該聚合無關,這就是聚合能實作高内聚的原因。

如果聚合設計過大,聚合會因為包含過多實體,導緻實體間管理複雜,高頻操作時會出現并發沖突或資料庫鎖,即便我們可以保證事務的成功執行,它依然有可能限制系統的性能和可伸縮性。

小聚合設計則可降低由于業務過大導緻聚合重構的可能性,讓領域模型更能适應業務變化。

那麼,這裡的“小”是什麼意思呢?最極端的情況是,一個聚合隻擁有全局辨別和單個屬性,當然,這并不是推薦做法(除非這正是需求所在)。好的做法是使用根實體(Root Entity)來表示聚合,其中隻包含最小數量的屬性或值類型屬性。這裡的“最小數量”表示所需的最小屬性集合,不多也不少。

哪些屬性是所需的呢?簡單的答案是:那些必須與其他屬性保持一緻。比如,一個Product擁有name和 description屬性,它們需要保持一緻,将它們放在兩個不同的聚合中顯然無意義。當我們修改name,很可能也會同時修改 description,如果你隻修改其一,很可能是在修改文法上的錯誤或使description能夠更比對name。

在聚合中,若認為有些被包含的部分應該模組化成實體,怎麼辦?首先思考該部分是否會随着時間而改變或該部分是否能被全部替換。若可被全部替換,請将其模組化成值對象,而非實體。很多情況下模組化成實體的概念都可重構成值對象。優先選用值對象并非意味着聚合就是不變的,因為當值對象屬性被替換成其他值時,根實體也就随之改變。

将聚合的内部模組化成值對象有很多好處。據所選用持久化機制,值 對象可随根實體而序列化,而實體則需單獨存儲區域予以跟蹤。

實體還會帶來某些不必要操作,比如,在使用Hibernate時,需對多表聯合查詢。對單表讀取快得多,而使用值對象也更友善安全。由于值對象不變,測試也相對簡單。

小聚合不僅有性能和可伸縮性上的好處,它還有助于事務成功執行,即可減少事務送出沖突。系統的可用性也得到了增強。在你的領域中,迫使你設計大聚合的不變條件限制并不多。當你遇到這樣的情況時,可以考慮添加實 體或者是集合,但無論如何,我們都應該将聚合設計得盡量小。

聚合之間是通過關聯外部聚合根ID的方式引用,而不是直接對象引用的方式。外部聚合的對象放在聚合邊界内管理,容易導緻聚合的邊界不清晰,也會增加聚合之間的耦合度。

聚合内資料強一緻性,而聚合間資料最終一緻性。

在一次事務中,最多隻能更改一個聚合的狀态。如果一次業務操作涉及多個聚合狀态的更改,應采用領域事件的方式異步修改相關的聚合,實作聚合間解耦。

在不持有對象引用的情況下,不能修改其他聚合,是以我們可以避免在同一個事務中修改多個聚合。但這種方式的缺點在于限制性太強,因為在領域模型中我們總需要對象之間的關聯關系來完成一些任務。

那麼,此時我們應該怎麼辦呢?

為實作微服務内聚合之間的解耦,以及未來以聚合為機關的微服務組合和拆分,應避免跨聚合的領域服務調用和跨聚合的資料庫表關聯。

總結

聚合的特點

高内聚、低耦合,它是領域模型中最底層的邊界,可作為拆分微服務的最小機關,但不推薦過度拆分。在對性能有極緻要求的場景中,聚合可獨立作為一個微服務,以滿足版本的高頻釋出和彈性伸縮要求。

一個微服務可包含多個聚合,聚合之間的邊界是微服務内天然的邏輯邊界。有了該邏輯邊界,在微服務架構演進時就可以聚合為機關進行拆分群組合。

聚合根的特點

聚合根是實體,有實體的特點,具有全局唯一辨別,有獨立的生命周期。一個聚合隻有一個聚合根,聚合根在聚合内對實體和值對象采用直接對象引用的方式進行組織和協調,聚合根與聚合根之間通過ID關聯的方式實作聚合之間的協同。

實體的特點

有ID辨別,通過ID判斷相等性,ID在聚合内唯一即可。狀态可變,它依附于聚合根,其生命周期由聚合根管理。實體一般會持久化,但與資料庫持久化對象不一定是一對一的關系。實體可引用聚合内的聚合根、實體和值對象。

值對象的特點

無ID,不可變,無生命周期,用完即扔。值對象之間通過屬性值判斷相等性。它的核心本質是值,是一組概念完整的屬性組成的集合,用于描述實體的狀态和特征。值對象盡量隻引用值對象。

參考 《實作領域驅動設計》 聚合和聚合根:怎樣設計聚合?

繼續閱讀