l

2019年6月17日 星期一

領域驅動設計學習筆記(5):Aggregate (上)

June 17 15:36~16:42


問題

在物件導向的domain model裡面,物件和物件之間經常有著複雜的關聯(associations)。如下圖所示,一個看板系統的domain model,Board擁有若干的Stage,一個Stage擁有若干個MiniStage,一個MiniStage擁有若干個SwimLane,一個SwimLane擁有若干個WorkItem。


當修改這些物件狀態時,如果不小心很可能會違反系統的不變量(invariant)或固定規則而導致系統狀態不正確(產生bug)。例如,看板系統的一個不變量是:「SwimLane的WorkItem數量不可以超過WipLimit數值。」

試想在一個多人使用的狀況下,為了滿足上述不變量,當修改SwimLane的狀態時,你可能要鎖住SwimLane,甚至是鎖住整個系統,導致系統無法使用或效率很差。

***

Aggregate Pattern

Aggregate是領域驅動設計(Domain-Driven Design;DDD)裡面很重要的pattern。依據《Domain-Driven Design: Tackling Complexity in the Heart of Software》書中的定義,Aggregate想要解決的問題與解決方案分別為:

Problem

It is difficult to guarantee the consistency of changes to objects in a model with complex associations. Invariants need to be maintained that apply to closely related groups of objects, not just discrete objects. Yet cautious locking schemes cause multiple users to interfere pointlessly with each other and make a system unusable.

很難保證具有複雜關聯的模型中物件更改的一致性。 需要維護不變量,這些不變量適用於密切相關的物件組,而不僅僅是離散的物件。 然而,謹慎的鎖定方案會導致多個用戶無意義地相互干擾並使系統無法使用。(中文修改自google翻譯)


Solution

Cluster the ENTITIES and VALUE OBJECTS into AGGREGATES and define boundaries around each. Choose one ENTITY to be the root of each AGGREGATE, and control all access to the objects inside the boundary through the root. Allow external objects to hold references to the root only. Transient references to internal members can be passed out for use within a single operation only. Because the root controls access, it cannot be blindsided by changes to the internals. This arrangement makes it practical to enforce all invariants for objects in the AGGREGATE and for the AGGREGATE as a whole in any state change.

將ENTITIES和VALUE OBJECTS聚集在一起形成AGGREGATES並定義AGGREGATES的邊界。 選擇一個ENTITY作為每個AGGREGATE的根,並透過根控制對邊界內物件的所有存取。 允許外部物件僅保留對根的引用。可以傳遞對內部成員的瞬時引用,以便僅在單個操作中使用。 因為根控制存取權限,所以內部的更改不能繞過它。 這種安排可以確保任何針對AGGREGATE裡面的物件以及AGGREGATE本身的狀態修改都不會違反不變量。(中文修改自google翻譯)

***

實作細節

以上述看板系統為例子,Stage、MiniStage與SwimLane形成一個Aggregate,Stage是Aggregate Root。依據DDD的定義,為了確保不變量:

  1. 所有對於Aggregate內部物件的操作都要透過Aggregate Root。也就是存取MiniStage與SwimLane都要透過Stage。
  2. 外部物件只可以擁有Stage物件的reference(因為它是Aggregate Root),不可以擁有MiniStage與SwimLane的reference。
  3. 但是,Stage可以將MiniStage與SwimLane往外傳,只不過客戶端程式只能在單一method中操作這些entity,不可以把它們的reference保存起來(例如,不能存在data member裡面)。

前兩點很容易理解與實作,但第三點就需要進一步討論。因為如果把MiniStage與SwimLane往外傳,既使在單一method裡面使用,也可能繞過Stage直接呼叫MiniStage與SwimLane,因此改變Stage這個Aggregat的狀態而破壞不變量。

所以,在實作Aggregate的時候,就有幾種可能的選擇:

  • 完全禁止回傳Aggregate內部的Entity。
  • 可以回傳Entity,但只回傳immutable Entity或是Entity的deep copy。
  • 設計immutable interface,讓Entity實作immutable interface並透過immutable interface回傳給客戶端。

無論採取哪種方式,在實作時都需要特別留意,以免Aggregate的不變量一不小心就被違反了。

***

廣告

對於DDD的Aggregate與Event Storming(事件風暴)有興趣的鄉民,可參考泰迪軟體的【Clean Architecture這樣學就會了實作班】,2019年7月份課程已確定開課。

***


友藏內心獨白:還是要看定義。