はじめに
Serviceを今まで、なんとなくで利用してきていたので「Serviceとはなんだ?」というのを調べてみました。
事前知識
ファットモデル...ビジネスロジックが大量に書かれているモデル
ビジネスロジック...業務システムの中で、具体的な業務で扱う様々な実体(商品、顧客、在庫など)を表現し、また、それらの関係や処理の方法、業務の流れなどをデータモデルやプログラムコードなどとして実装した部分。
Serviceとは?
僕はServiceを「ファットモデルになることを回避し、コードを読みやすくするためのコード分割先」という意味だと理解しました。
具体的にServiceに置かれる処理は
・外部サービスを利用する処理
・複数のモデルに渡る複雑な処理
になります。
Serviceを活用することでコントローラーもモデルもすっきりしコードの可読性が上がるというわけです。
Serviceはどのように書く?
書き方はどうやら多岐にわたるようです。そのため逆にどこにでも共通しているのが書き方の規約をプロジェクトで決めてるということです。
例えば
・クラス名は、{動詞}{目的語}Serviceという命名規則
・publicメソッドは"call"だけ
・1serviceに1機能
といったような感じです。
実際にRailsで書かれているオープンソースを見てみるとわかり易いと思います。
こちらは分散SNS Mastodonのservicesディレクトリです。こちらから一部抜粋します。
https://github.com/mastodon/mastodon/tree/main/app/services
class BlockService < BaseService
include Payloadable
def call(account, target_account)
return if account.id == target_account.id
UnfollowService.new.call(account, target_account) if account.following?(target_account)
UnfollowService.new.call(target_account, account) if target_account.following?(account)
RejectFollowService.new.call(target_account, account) if target_account.requested?(account)
block = account.block!(target_account)
BlockWorker.perform_async(account.id, target_account.id)
create_notification(block) if !target_account.local? && target_account.activitypub?
block
end
private
def create_notification(block)
ActivityPub::DeliveryWorker.perform_async(build_json(block), block.account_id, block.target_account.inbox_url)
end
def build_json(block)
Oj.dump(serialize_payload(block, ActivityPub::BlockSerializer))
end
end
Mastodonはこのように基本、Serviceのpublicメソッドにはcallメソッド1つしか書いていないです。(他は全てprivate)
結局、Serviceに書くと何が嬉しい?
結局、Serviceに書くメリットは何かというと以下かなと思いました。
・コントローラー、モデルの可読性が上がる
・単一責任の原則に従って書くことで運用がしやすくなる。
・外部APIを利用するコードの置き場に迷わなくなる