實體與倉庫 - Rails 開發實踐
在我們實作訂閱功能的過程中,提到了像是 Entity(實體)還有 Repository(倉庫)等關鍵字,現在我們要來回顧一些這些使用的物件有怎樣的特性,在 Rails 中我們應該如何使用,才能避免預期外的問題。
實體
在 Rails 中我們所使用的 Model 就是 Entity,然而 ActiveRecord 同時也把這些 Model 物件賦予了 Repository 的角色,然而這並不代表他是混合的,這是因為 Ruby 語言的特性所造成的影響。
首先,對 Ruby 來說一切都被視為「物件」也因此一個類別(Class)也是一種物件,以 User
為例子,我們會有 User
類別物件和 #<User id=1>
的實例(Instance)兩種物件,前者是 Repository 的角色,因此會有 User.find(1)
的使用,而後者則是 Entity 的角色 user.admin?
。
因為 Entity 是用於維護「狀態」的物件,我們要避免寫入資料庫這類操作,這是由 Repository 負責的任務。基於這樣的原則,我們會發現 ActiveModel 會紀錄「狀態變化」這件事情,進而提供像是 user.changed?
和 user.changes
的方法。
在大多數的狀況下,這類型的使用我們應該避免:
1class User < ApplicationRecord
2 # ...
3 def upgrade(role:)
4 update(role: role)
5 end
6end
這種類型的操作會讓「副作用(Side Effect)」產生,像是常見的 N + 1 查詢,或者在沒有注意到的狀況下不小心改動到使用者。
1class User < ApplicationRecord
2 # ...
3 def upgrade(role:)
4 self.role = role
5 end
6end
7
8# ...
9users.each { |user| user.upgrade(role: :admin) }
10# 明確聲明「儲存」並且確保一起成功
11User.transaction { users.each(&:save!) }
12end
倉庫
Repository 是用來獲取資料的方式,實際上只要能夠將「資料」轉換成「資訊」就可以,簡單說就是取的資料後產生一個或多個實體,因此將他認知為一個「介面(Interface)」會更加適合,舉例來說除了 ActiveRecord 可以從資料庫獲取資料外,也可以用 ActiveResource 來抓到資料。
1class Person < ActiveResource::Base
2 self.site = "http://api.people.com:3000"
3end
因為 ActiveResource 和 ActiveRecord 提供了相同的介面,像是 .find
、.create
等方法,因此我們可以在拆分服務的狀況下,做出從 class Person < ActiveRecord
切換成 class Person < ActiveResource::Base
這樣子的切換,只要提供的方法還是相同,那麼就可以無痛轉移。
因此在使用上,比起單純使用 .find
這類基礎方法,我們可以更善用 scope
的機制來更清楚的描述我們的使用情境,舉例來說:
1class Subscription < ApplicationRecord
2 # ...
3 scope :unexpired, -> { where(expired_at: nil) }
4end
這樣就可以用 Subscription.unexpired
來表示「未過期的訂閱」在 ActiveResource 的版本下,可能就會像這樣實現。
1class Subscription < ActiveResource::Base
2 # ...
3
4 def self.unexipred
5 where(expired_at: nil)
6 end
7end
如此一來我們就可以在修改成外部服務時限縮重構的部分。
因為 Rails 比較少以這種方式拆分,因此大多數套件的介面跟 ActiveRecord 其實不太相容,在使用上還是要多評估,或者自己拆分出 Repository 而不要去依賴 ActiveModel 處理。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- 前言 - Rails 開發實踐
- 將需求實現的準備 - Rails 開發實踐
- 獲取規格的技巧 - Rails 開發實踐
- 可以測試的規格 - Rails 開發實踐
- 快速通過測試的方法 - Rails 開發實踐
- 用測試完善規格 - Rails 開發實踐
- 資料跟資訊的差異 - Rails 開發實踐
- 用測試資料驗證邏輯 - Rails 開發實踐
- 預期外狀況的檢查 - Rails 開發實踐
- 預期外狀況的測試 - Rails 開發實踐
- 聚合多筆資料 - Rails 開發實踐
- 重構與修正邏輯 - Rails 開發實踐
- 加入聚合實體 - Rails 開發實踐
- 持久化資料 - Rails 開發實踐
- 實體與倉庫 - Rails 開發實踐
- 聚合與邊界 - Rails 開發實踐
- 使用案例與服務 - Rails 開發實踐
- 結語 - Rails 開發實踐