# 第六章 領域物件的生命周期 主要的挑戰有以下兩類 1. 在整個生命周期中維護完整性 2. 防止模型陷入「管理生命周期複雜性」所造成的困境當中。 ![](https://i.imgur.com/trDB4RY.jpg) 可透過三種模式解決這些問題: Aggregate (聚合)、Factory (工廠)、Repository (儲存庫) 這些結構提供了容易掌握的模型物件處理方式,使 Model-Driven Desing 更完備。 ## Aggregate Pattern ### 確保物件更改一致性 > 「互不關聯的物件」須遵守固定規則(Invariant),而且「緊密關聯的各組件」也要遵守一些固定規則(Invariant)。 以一筆購物訂單為例: 在沒有聚合(Aggregate)規範下,假如消費者修改一個品項,可能影響折扣、金流、物流、發票。 模型各個物件必須隨時面對不知道從哪裡來的狀態變更,還需要維持整體模糊的完整性。 ``` OrderItem.update() → Order.check() →...→ Order.update() → Inventory.update() ``` | 訂單 | Request | 固定規則 | | ---- | ---------------- | --- | | 品項 | 修改品項 | OrderItem的內容與數量可以被新增、減少或異動,但最終總數量不能為 0、最終總額不能小於 0 | | 折扣 | 重新計算滿額折扣 | 有最低與最高訂單金額使用限制 | | 金流 | 刷退重新付款 | 有最低與最高訂單金額使用限制 | | 物流 | 金額限制 | 有最低與最高訂單金額使用限制,而且新增品項時也要考量某些商品搭配特定物流(如冷藏宅配) | | 發票 | 廢除後重新開立 | 若是信用卡付款,電子發票須加註卡號後四碼 | ![](https://i.imgur.com/7INGVhR.png) **⬇⬇⬇透過聚合(Aggregate)降低複雜度⬇⬇⬇** 1. 聚合是一群相關聯的物件的組合,讓我們可以把它作為一個狀態變更的單位。 2. 每個聚合都需要有一個 Entity 作為他的根(Aggregate Root)。 4. 任何的改變與事件傳遞,都要先通過根(Aggregate Root)。 ``` Order.updateItem() → Inventory.update() ``` ![](https://i.imgur.com/lJcg31T.png) ### Aggregate 設計原則 > Aggregate 由 Root & Boundary 組成 1. Aggregate Root 的 Entity 必須要在 Bounded Context 中有唯一標示性,它的 ID 不能與其他 Aggregate Root 重複。 2. Aggregate Root 負責檢查邊界內所有固定規則。 3. Aggregate 外的物件不能引用除了 Aggregate Root 之外的任合內部物件。換言之,只有 Aggregate Root 才能直接透過資料庫查詢來獲取。內部的其他物件都要透過 Aggregate Root 才能取得。 4. Aggregate 內部物件可以引用其他 Aggregate Root,但僅能引用該 Aggregate Root 的 ID。 5. 刪除操作必須一次刪除 Aggregate 邊界內的所有物件 6. 當提交對 Aggregate 內部的任何物件的修改時,整個 Aggregate 的固定規則都要被滿足,意指一次就要存整個 Aggregate 進去。 ### 萬能的Aggregate,通通交給你處理,越大越讚啦!??? > Aggregate 拆越大,複雜度越低、效能越差;Aggregate 拆越小,複雜度越高、效能越好。 當什麼工作都叫一個人完成就會出現效能問題,例如把Order、Prodcut、Discount 等等都放在 **Shop** 這個聚合裡,Shop 為了維持外部獲取料一致性,今天只是更改 Shop 說明,卻lock 住 Order,訂單無法建立~連店都不用開了呢??? 所以較為明智作法還是將 Order 獨立聚合(Aggregate),但是 Order 身上帶有 ShopId 來保持它對於 Shop 的引用。 ### Aggregate 小結 * 在一致性邊界內保護不變條件 * 設計小的聚合 * 通過 ID 引用其他聚合 * 在邊界外使用最終一致性 ## Factory Pattern * 一堆聚合互相關聯的時候,怎麼辦? DDD 的宗旨就是要去蕪存菁,把焦點放在Domain,所以把建立複雜的物件或是 Aggregate 的職責分配給一個單獨的物件(Factory) * Factory不會替代原本的業務邏輯,而是封裝複雜的建立過程,讓我們可以更容易取得聚合。 * 複雜的物件建立屬於領域層的職責 ### For Example (reference: https://refactoring.guru/design-patterns/factory-method) Problem: 今天我們的物流App V1只有一種物流方式:貨車,所以在程式實作只需要一個`Truck`去負責。但當業務擴大,V2要加入「海運」。 該怎麼做? ![](https://i.imgur.com/WLyQc6r.png) 在導入Factory 模式下,`Truck & Ship` 的建立被封裝在 `Logitsitcs`,且Factory負責確保建立的物件滿足所有固定規則。 ![](https://i.imgur.com/5YuyaXk.png) 對使用者而言,我們提供的產品就只有`Transport`,並且產品一定會提供`deliver()`服務,但對於實際物流如何運作對使用者而言就不是太重要。 ![](https://i.imgur.com/pltqyCe.png) ![](https://i.imgur.com/o2gEyaT.png) ### Factory 小結 通常需要有一種「更複雜或更抽象」的實例建立機制,即適合在設計中引入Factory模式。Factory 封裝了物件建立和重建時的生命週期。 ## Repository Pattern * 假設今天開發人員建構了一個SQL查詢,並將他傳遞給基礎設施層的某個查詢服務,然後再把得到的資料結果提取所需的資訊,傳遞給Factory。 * 這一系列的操作更像技術機制(technical mechanism),而非領域模型。 * Repository的機制負責將物件加進資料庫中,或從資料庫刪除物件。 ### Repository 優點 ![](https://i.imgur.com/qvmIqpm.png) * Repository 最大的優點就在於分離領域模型與資料模型,你可以更輕鬆的擴展領域模型或是優化資料庫操作的效能,而不用怕互相影響。 * 提供簡單的資料模型來查詢 * 容易將他替換為「虛擬實作」(dummy implementation) 以便測試中使用 ### Repository & Factory 的關係 * 從領域驅動設計角度來看,Factory 負責製造新物件,Repository 負責尋找已有的物件 * 在職責上明確區分Factory 的工作是用資料來實例化一個很複雜的物件。 * 如果一個新物件被建立,建立完成後應該把它加進Repository中,由Repository 來封裝物件在資料庫中儲存 ![](https://i.imgur.com/CQd3kIR.png) * Repository 也可以委派Factory 來建立一個物件(理論可行,但少見) ![](https://i.imgur.com/kxFxLo5.png) ## 總結回憶一下 1. Aggregate (聚合):透過定議清晰的所屬關系和邊界,避免「混亂、錯綜複雜的物件關系網」 3. Factory (工廠):「建立」和「重建」複雜物件和 Aggregate,進而**封裝**他們的內部結構 4. Repository (儲存庫):提供尋找和取出「持久化物件」並「封裝龐大基礎設施」 > Factory 負責處理物件生命週期的開始,而 Repository 幫助管理生命週期的中間與結束。