🏄♂️
Service Objectパターンとはなにか
Service Objectとは?
Service Objectは、コントローラーやモデルに書かれがちなビジネスロジックを切り出すための設計パターンです。
Railsでは、ビジネスロジックがモデルやコントローラーに集中しやすいため、Fat Controller や Fat Model といったアンチパターンが発生しやすくなります。
Service Objectを導入することで、
- コードの見通しが良くなる
- 再利用しやすくなる
- テストしやすくなる
といったメリットが得られます。
命名と配置
- クラス名の慣習:
○○Service
- ファイルの配置:
app/services/
ディレクトリ
app/services/
├── user/
│ └── register_user_service.rb
名前から「何をするクラスか」が明確に伝わるようにします。
例:商品購入処理をService Objectでリファクタ
Before:Fat Controllerの例
# app/controllers/purchases_controller.rb
class PurchasesController < ApplicationController
def create
@item = Item.find(params[:item_id])
if @item.stock <= 0
redirect_to @item, alert: "在庫がありません"
return
end
@purchase = Purchase.new(user: current_user, item: @item, amount: @item.price)
ActiveRecord::Base.transaction do
@purchase.save!
@item.decrement!(:stock)
NotificationMailer.purchase_complete(current_user, @item).deliver_later
end
redirect_to @item, notice: "購入が完了しました"
rescue => e
logger.error(e)
redirect_to @item, alert: "購入に失敗しました"
end
end
After:Service Objectを使ってリファクタ
app/services/purchase_item_service.rb
class PurchaseItemService
def initialize(user:, item:)
@user = user
@item = item
end
def call
raise "在庫がありません" if @item.stock <= 0
Purchase.transaction do
purchase = Purchase.create!(user: @user, item: @item, amount: @item.price)
@item.decrement!(:stock)
NotificationMailer.purchase_complete(@user, @item).deliver_later
purchase
end
end
end
app/controllers/purchases_controller.rb
class PurchasesController < ApplicationController
def create
service = PurchaseItemService.new(user: current_user, item: Item.find(params[:item_id]))
purchase = service.call
redirect_to purchase.item, notice: "購入が完了しました"
rescue => e
redirect_to item_path(params[:item_id]), alert: e.message
end
end
DRYの原則とService Object
このリファクタは、DRY(Don't Repeat Yourself)原則にも貢献しています。
- 重複していた在庫チェックや購入処理が1か所に集約
- 修正が必要な場合でも1箇所だけ直せばOK
- 他の箇所でもこの処理を再利用可能
まとめ
項目 | Before(Fat Controller) | After(Service Object) |
---|---|---|
責務の分離 | × | ◎ |
テストのしやすさ | × | ◎ |
コードの再利用 | △ | ◎ |
見通しの良さ | △ | ◎ |
Service Objectは「複雑な処理を1つの責務にまとめて、アプリ全体をスッキリ保つ」ための、強力なパターンです。
Discussion