あなたは自分のアプリの中身を見て、
どんな処理をしているか説明できますか?
あなたはコントローラーを見るだけで
どんな処理をしているか大体わかったりしますか?
少なくとも私が初めに作ったアプリは
そんなことなかったです。
そもそもFatControllerの知識もなく
作ったのでこの概念を知った時は
衝撃的でした。
と言うことでまとめてみました。
そもそもファットコントローラーってなにゃ
一言で言うと
責務を持ちすぎた Controller。
本来の Controller の役割
- リクエストを受け取る
- パラメータを渡す
- レスポンスを返す
交通整理をするだけ。
なぜ Fat Controller はダメなのか?
結論:役割が混ざって、変更とテストがつらくなるから。
本来 Controller は
「受け取って渡すだけ」。
Fat Controller になると:
- ビジネスロジックが混ざる
- 再利用できない
- テストが重くなる
- 修正が怖くなる
ならどんな設計がいいのか。結論から言うと
❌ Controller → Model に全部押し付ける
⭕ Controller → “役割別のクラス” に分散させる
です。
🌱 なぜ「モデルに渡す」だけだとダメなのか?
Fat Controller を避けようとして…
def create
Article.create_with_tags_and_notify_and_log(params)
end
みたいにすると、
👉 今度は Model が
- 保存ロジック
- バリデーション
- 外部API
- 通知
- 集計
全部抱えて
💀 Fat Model 爆誕
になります。
つまり:
太る場所が Controller → Model に移動しただけ
✅ 正解の考え方:責務を“分解”する
Railsではこれを
Single Responsibility Principle(単一責務)
と言います。
1クラス1仕事。
🧠 実戦パターン(これ覚えればOK)
① Controller:交通整理だけ
def create
Articles::CreateService.call(params)
end
Controllerは
✅ params受け取る
✅ Service呼ぶ
だけ。
② Service Object:業務ロジック担当
class Articles::CreateService
def self.call(params)
new(params).call
end
def initialize(params)
@params = params
end
def call
article = Article.new(@params)
article.save!
SlackNotifier.notify(article)
end
end
ここで
- 保存
- 通知
- 分岐
を書く。
③ Model:純粋なドメイン
class Article < ApplicationRecord
validates :title, presence: true
end
Modelは
✅ 関連
✅ validation
✅ scope
だけ。
④ 複雑なフォームは FormObject
class ArticleForm
include ActiveModel::Model
end
🔥 まとめ(超重要)
Fat Controller 問題の本当の解決策は:
| レイヤ | 役割 |
|---|---|
| Controller | 受付係 |
| Service | 処理係 |
| Model | データ定義 |
| FormObject | 入力専門 |
つまり:
🌟 太らせない
Controller もModel も
〜Service Object は Controller の一部なのか?〜
- Controller が太る
- Model に処理を移す
- 今度は Model が太る
え、じゃあ
結局どこにロジックを書けばいいんだ?
結論
MVC の外側⭐️
「ビジネスロジック専用レイヤ」です。
よくある間違い
Fat Controller を避けようとして、こんなコードを書いた経験ありませんか?
def create
Article.create_with_tags_and_notify(params)
end
そして Model 側:
class Article < ApplicationRecord
def self.create_with_tags_and_notify(params)
article = create!(params)
SlackNotifier.notify(article)
end
end
一見スッキリ。
でも Model が:
- 保存
- 通知
- 条件分岐
- 外部API
を抱え始めます。
結果:
Fat Model 爆誕
これは
Controller の脂肪を Model に移植しただけ
です。
問題は解決していません。
本質的な解決策
答え:「第三の場所」を作る
つまり Service Object
レイヤ構造の考え方
Rails 標準:
- Controller → 受付
- Model → データと制約
- View → 表示
ここに Service を足すと:
Controller
↓
Service Object
↓
Model
になります。
各レイヤの責務
Controller
def create
Articles::Create.call(params)
end
役割は:
- params を受け取る
- Service を呼ぶ
- redirect / render
だけ。
Service Object(業務ロジックの本体)
class Articles::Create
def self.call(params)
new(params).call
end
def initialize(params)
@params = params
end
def call
article = Article.new(@params)
article.save!
SlackNotifier.notify(article)
end
end
ここに書くもの:
- 複数モデルをまたぐ処理
- 条件分岐
- トランザクション
- 外部API
- 通知
つまり
アプリの「動き」そのもの
Model
class Article < ApplicationRecord
validates :title, presence: true
end
Model は:
- association
- validation
- scope
だけ。
純粋なドメイン。
Service Object は Controller の一部?
違います。
Controller から呼ばれるだけで、
- rake task
- background job
- rails console
からも使えます。
超短い定義
覚えるならこれ:
Controller
HTTPアダプター
Service Object
ビジネスロジックの本体
Model
データ構造と制約
なぜこれが大事かまとめ
この分離をすると:
- Controller が太らない
- Model も太らない
- テストが楽
- 再利用できる
- 設計が見える
逆にやらないと:
- 修正が怖くなる
- 影響範囲が読めなくなる
- 属人化する