Serviceオブジェクト

エンティティに最重要ビジネスデータと最重要ビジネスロジックを持たせて、ユースケースを考えて、詳細は方針に依存して、、

 

あれ、ユースケースの小さく分解したロジックってどこに書けば良いんだろう?と思った。

サービスオブジェクトに書くことが多いとあったので、下記にまとめる。

 

基本的な概念

💡 A good service object is easy to test and follows the single responsibility principle.

テストを書いたり、読み進めることが簡単であるべき。ServiceObjectには一つのビジネスロジックしか書かれていないから。

Amin Shah Gilaniは、ServiceObjectに切り出すべきか迷った時のフローチャートを示している。

Does your code handle routing, params or do other controller-y things?If so, don’t use a service object — your code belongs in the controller.

Are you trying to share your code in different controllers?In this case, don’t use a service object — use a concern.

Is your code like a model that doesn’t need persistence?If so, don’t use a service object. Use a non-ActiveRecord model instead.

Is your code a specific business action? (e.g., “Take out the trash,” “Generate a PDF using this text,” or “Calculate the customs duty using these complicated rules”)In this case, use a service object. That code probably doesn’t logically fit in either your controller or your model.

このコードってServiceObjectに切り出すべき、、?

  • routingやparams等のコントローラーっぽい処理をしている。

no, it should be in the Controller

  • 他のコントローラーと処理を共有している。

no, it should be in the Concern

  • 永続処理の不要なモデル処理をしている。

no, it’s non-ActiveRecord model (FormObject or something)

  • 詳しいビジネス処理(PDF作成、計算、ゴミ処理等)を行っている。

yes, ServiceObject should help it!!!


コントローラーって、責務を知らないと肥大になりがち。

with rendering and redirecting—normal controller concerns.

コントローラーは、renderかredirectという、コントローラの関心ごとにfocusさせたい。

class UserController < ApplicationController
  def create
    user = User.new(user_params)
    if user.save
      send_welcome_email
      notify_slack
      if @user.admin?
        log_new_admin
      else
        log_new_user
      end
      redirect_to new_user_welcome_path
    else
      render 'new'
    end
  end  # private methods
end

かといってモデルにロジックを移していくと、今度はモデルが肥大化しがち。

コントローラーとモデルの間に受け皿という形で層を設けることができたら、それぞれの処理が何をやっているのかが明確になって後で使用の把握に役立つ。

その「層」の役割を担うのがいくつかある。

その一つにService層がある。

Service objects in Rails

What are service objects?

Service objects are plain old Ruby objects (PORO’s) that do one thing.

単一責務のオブジェクト(PORO)

Serviceクラスにより詳細な機能を切り出すことで、

最終的にここまでコントローラーをスリムにすることができる。

class UserController < ApplicationController
  def create
    user = RegisterUser.new(User.new(user_params)).execute
    if user
      redirect_to new_user_welcome_path
    else
      render 'new'
    end
  end  # private methods
end

ServiceObject.new(arguments).executeの形が不恰好だと思ったら、下記のように独自にcallメソッドを定義しちゃってもいい。

class RegisterUser
  def self.call(*args, &block)
    new(*args, &block).execute
  end  def initialize(user)
@user = user
  end  def execute
    # old code
  end  # private methods
end

コントローラーはこうなる

class UserController < ApplicationController
  def create
    result = RegisterUser.call(User.new(user_params))
    if result.success?
      redirect_to new_user_welcome_path
    else
      render 'new', error: result.errors
    end
  end  # private methods
end

Serivice Classを理解する上で大事なことだから再掲

基本的な概念

💡 A good service object is easy to test and follows the single responsibility principle.

 

テストを書いたり、読み進めることが簡単であるべき。ServiceObjectには一つのビジネスロジックしか書かれていないから。

Amin Shah Gilaniは、ServiceObjectに切り出すべきか迷った時のフローチャートを示している。

Does your code handle routing, params or do other controller-y things?If so, don’t use a service object — your code belongs in the controller.

Are you trying to share your code in different controllers?In this case, don’t use a service object — use a concern.

Is your code like a model that doesn’t need persistence?If so, don’t use a service object. Use a non-ActiveRecord model instead.

Is your code a specific business action? (e.g., “Take out the trash,” “Generate a PDF using this text,” or “Calculate the customs duty using these complicated rules”)In this case, use a service object. That code probably doesn’t logically fit in either your controller or your model.

このコードってServiceObjectに切り出すべき、、?

  • routingやparams等のコントローラーっぽい処理をしている。

no, it should be in the Controller

  • 他のコントローラーと処理を共有している。

no, it should be in the Concern

  • 永続処理の不要なモデル処理をしている。

no, it’s non-ActiveRecord model (FormObject or something)

  • 詳しいビジネス処理(PDF作成、計算、ゴミ処理等)を行っている。

yes, ServiceObject should help it!!!

 

Service Objectがアンチパターンである理由とよりよい代替手段(翻訳)|TechRacho by BPS株式会社