天天看點

DDD領域驅動設計實戰-聚合(Aggregate)和聚合根(AggregateRoot)(下)設計原則總結

設計原則

在一緻性邊界内模組化真正的不變條件

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

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

  • 事務一緻性

    要求立即性和原子性

  • 最終一緻性    

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

c = a + b      

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

AggregateTypel (
    int a;
    int b;
    int c;
    operations ...      

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

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

設計小聚合    

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

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

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

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

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

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

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

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

通過唯一辨別引用其它聚合

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

在邊界之外使用最終一緻性

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

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

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

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

通過應用層實作跨聚合的服務調用

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

總結

聚合的特點

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

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

聚合根的特點

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

實體的特點

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

值對象的特點

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