服務類
控制器是我們應用程式的入口點。但是,它不是唯一可能的切入點。我想通過以下方式訪問我的邏輯:
- 耙任務
- 後臺工作
- 控制檯
- 測試
如果我將邏輯拋入控制器,則無法從所有這些位置訪問它。因此,讓我們嘗試“瘦控制器,胖模型”方法並將邏輯移動到模型中。但是哪一個?如果給定的邏輯涉及 User
,Cart
和 Product
模型 - 它應該在哪裡生活?
繼承自 ActiveRecord::Base
的類已經承擔了很多責任。它處理查詢介面,關聯和驗證。如果你向模型新增更多程式碼,它將很快成為數百種公共方法無法維護的混亂。
服務只是一個常規的 Ruby 物件。它的類不必從任何特定的類繼承。它的名字是動詞短語,例如 CreateUserAccount
而不是 UserCreation
或 UserCreationService
。它位於 app / services 目錄中。你必須自己建立這個目錄,但 Rails 會為你自動載入類。
服務物件做一件事
服務物件(也稱為方法物件)執行一個操作。它包含執行該操作的業務邏輯。這是一個例子:
# app/services/accept_invite.rb
class AcceptInvite
def self.call(invite, user)
invite.accept!(user)
UserMailer.invite_accepted(invite).deliver
end
end
我遵循的三個公約是:
服務屬於 app/services directory
。我鼓勵你將子目錄用於業務邏輯繁重的域。例如:
- 檔案
app/services/invite/accept.rb
將定義Invite::Accept
,而app/services/invite/create.rb
將定義Invite::Create
- 服務以動詞開頭(並且不以服務結束):
ApproveTransaction
,SendTestNewsletter
,ImportUsersFromCsv
- 服務響應
call
方法。我發現使用另一個動詞使它有點多餘:ApproveTransaction.approve()
讀得不好。此外,call
方法是lambda
,procs
和方法物件的事實上的方法。
優點
服務物件顯示我的應用程式的功能
我可以瀏覽服務目錄,看看我的應用程式做了什麼:ApproveTransaction
,CancelTransaction
,BlockAccount
,SendTransactionApprovalReminder
…
快速瀏覽一下服務物件,我知道涉及哪些業務邏輯。我不需要通過控制器,ActiveRecord
模型回撥和觀察者來理解批准交易涉及的內容。
清理模型和控制器
控制器將請求(引數,會話,cookie)轉換為引數,將它們傳遞給服務,並根據服務響應重定向或呈現。
class InviteController < ApplicationController
def accept
invite = Invite.find_by_token!(params[:token])
if AcceptInvite.call(invite, current_user)
redirect_to invite.item, notice: "Welcome!"
else
redirect_to '/', alert: "Oopsy!"
end
end
end
模型僅處理關聯,範圍,驗證和永續性。
class Invite < ActiveRecord::Base
def accept!(user, time=Time.now)
update_attributes!(
accepted_by_user_id: user.id,
accepted_at: time
)
end
end
這使得模型和控制器更容易測試和維護!
何時使用服務類
當某個操作符合以下一個或多個條件時,可以訪問服務物件:
- 行動很複雜(例如,在會計期間結束時關閉賬簿)
- 該操作涉及多個模型(例如,使用 Order,CreditCard 和 Customer 物件進行的電子商務購買)
- 該操作與外部服務互動(例如,釋出到社交網路)
- 該行動不是基礎模型的核心問題(例如,在一段時間後掃除過時的資料)。
- 有多種方法可以執行操作(例如,使用訪問令牌或密碼進行身份驗證)。