聚合與邊界 - Rails 開發實踐
倉庫跟實體是相當基本的概念,然而還不足以涵蓋更多的情境。我們還需要討論 Aggregate(聚合)的情況,以我們這次的例子來說,就是一種聚合的表現。有了 Aggregate 的概念後,就可以逐步看出一個系統的邊界。
聚合
Aggregate 和 Entity(實體)基本上是非常類似的,他們通常都會有一個唯一的編號(如:ID)來識別成獨立的個體,然而差異在於 Aggregate 會有從屬的 Entity 而且這些 Entity 的改變會由 Aggregate 來管理。
以訂閱機制的情境作為例子,Subscription
聚合了 SubscriptionItem
也因此當我們要製作新的 SubscriptionItem
不會使用 SubscriptionItem.new(subscription_id: ...)
的方式,而是 subscription.items.build(...)
來處理。
這也是我們會以 #extend_with(...)
方法來建立新的訂閱的原因,基本上我們會希望這些被聚合的 Entity 是由他的 Aggregate Root(聚合根)來進行管理。
Aggregate 的特性也跟我們在資料庫經常使用的 Aggregate Function(聚合函式)類似,像是 SELECT SUM(amount) FROM ...
在 Aggregate 會這樣實現。
1class Subscription < ApplicationRecord
2 # ...
3 def total_amount
4 items.sum(&:amount)
5 end
6end
因為是 ActiveRecord 的關係,我們可以用 items.sum(:amount)
來直接對資料庫查詢,而不是載入所有 SubscriptionItem
後由 Ruby 來加總。
邊界
你可能會發現,如果是 Aggregate 的話在 Rails 中就是使用 has_many
來關聯其他 Model 的物件,然而依照 Aggregate 的定義,如果 User
關聯了相關的 Model 後,不就變成所有操作都需要以 User
來控制了?
這就要提到上下文邊界(Bounded Context)的概念,在一個系統中可能會有訂閱、會員、商品等等不同的模組,這些模組完全可以自己構成一個小的系統,並且在大多數時候互不關聯。
正因如此,我們在 User
中不會用 has_one
或者 has_many
來關聯 Subscription
而是利用 Repository(倉庫)的方式,用 Subscription.by_owner(user)
來篩選出我們想要關聯的資料。
如此一來,原本 User
和 Subscription
兩個物件需要互相知道對方資訊的情況,就變為約定「具有 #id
屬性的物件」這樣一層相對薄弱的關係,兩者就劃分出了邊界。透過這樣的模組劃分,我們就可以把相關的邏輯有效的聚集在一處,降低維護上的困難。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- 前言 - Rails 開發實踐
- 將需求實現的準備 - Rails 開發實踐
- 獲取規格的技巧 - Rails 開發實踐
- 可以測試的規格 - Rails 開發實踐
- 快速通過測試的方法 - Rails 開發實踐
- 用測試完善規格 - Rails 開發實踐
- 資料跟資訊的差異 - Rails 開發實踐
- 用測試資料驗證邏輯 - Rails 開發實踐
- 預期外狀況的檢查 - Rails 開發實踐
- 預期外狀況的測試 - Rails 開發實踐
- 聚合多筆資料 - Rails 開發實踐
- 重構與修正邏輯 - Rails 開發實踐
- 加入聚合實體 - Rails 開發實踐
- 持久化資料 - Rails 開發實踐
- 實體與倉庫 - Rails 開發實踐
- 聚合與邊界 - Rails 開發實踐
- 使用案例與服務 - Rails 開發實踐
- 結語 - Rails 開發實踐