Zenn
🏄‍♂️

Service Objectパターンとはなにか

2025/03/26に公開

Service Objectとは?

Service Objectは、コントローラーやモデルに書かれがちなビジネスロジックを切り出すための設計パターンです。

Railsでは、ビジネスロジックがモデルやコントローラーに集中しやすいため、Fat ControllerFat 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

ログインするとコメントできます