第2部の導入: Fat Controller/Fat Modelとは何か
第 1 部では、フレームワークに依存しない PHP のクラス設計原則を扱いました。 この章から始まる第 2 部では、その原則を Laravel の実務コードに応用していきます。
第 2 部は、Laravel の基本的な MVC 構造・ルーティング・Eloquent の基本的な CRUD 操作を経験済みであることを前提とします。 不安がある場合は、先にLaravel × DDD × クリーンアーキテクチャ実践ガイドのはじめにとLaravel API開発の基礎を読むことをおすすめします。
Fat Controllerとは
次の OrderController を見てください。
class OrderController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'items' => 'required|array',
'customer_email' => 'required|email',
]);
$total = 0;
foreach ($validated['items'] as $item) {
$total += $item['price'] * $item['quantity'];
}
foreach ($validated['items'] as $item) {
$stock = DB::table('stocks')->where('name', $item['name'])->value('quantity');
if ($stock < $item['quantity']) {
return response()->json(['error' => '在庫が不足しています'], 422);
}
}
$order = Order::create([
'customer_email' => $validated['customer_email'],
'total' => $total,
]);
Mail::raw("ご注文ありがとうございます。合計{$total}円です。", function ($message) use ($validated) {
$message->to($validated['customer_email'])->subject('注文確認');
});
return response()->json($order, 201);
}
}
バリデーション・合計金額の計算・在庫確認・注文の作成・確認メールの送信が、storeメソッド 1 つに詰め込まれています。
このように、業務ロジックが Controller に溜め込まれ肥大化した状態を、Fat Controllerと呼びます1。
MVC で最もよく見られるアンチパターンの 1 つです。
Fat Modelとは
「Controller を痩せさせよう」と考えて、処理をそのままモデルに移すとどうなるでしょうか。
class Order extends Model
{
public static function placeOrder(array $items, string $customerEmail): self
{
$total = 0;
foreach ($items as $item) {
$total += $item['price'] * $item['quantity'];
}
foreach ($items as $item) {
$stock = DB::table('stocks')->where('name', $item['name'])->value('quantity');
if ($stock < $item['quantity']) {
throw new RuntimeException('在庫が不足しています');
}
}
$order = self::create([
'customer_email' => $customerEmail,
'total' => $total,
]);
Mail::raw("ご注文ありがとうございます。合計{$total}円です。", function ($message) use ($customerEmail) {
$message->to($customerEmail)->subject('注文確認');
});
return $order;
}
}
処理の置き場所が Controller から Model に移っただけで、1 つのメソッドに複数の責務が詰め込まれている状態は変わっていません。 これは、第 1 章で見た god class と同じ構造の問題です。
Laravel の作者である Taylor Otwell は、Controller を薄く保ち Model を厚くする方針を好んで語っています2。 ただし、ここでの「厚い」は、行数が多いという意味ではなく、他の Service やコマンドを呼び出す公開 API が豊富だという意味です。 バリデーション・在庫確認・通知という異なる関心事を 1 つのメソッドに詰め込んだ、上の Order クラスとは区別してください。
第2部で扱う範囲
第 2 部では、第 9 章で Eloquent モデルの責務責務 (Responsibility)そのクラスや関数が引き受けている役割のこと。誰の要求で変更されるかを問うと境界が見えやすい。、第 10 章でサービスクラスサービスクラスコントローラやモデルに置き場のない業務手続きを引き受けるクラス。呼び出し順序を持つ処理をまとめる場所として使う。への責務分割を扱い、Fat Controller・Fat Model のどちらも避ける設計を見ていきます。
一方で、次の内容には踏み込みません。
- DDD戦術パターン(値オブジェクト・エンティティ・集約・リポジトリパターンなど)
- クリーンアーキテクチャの層実装(ユースケース層・プレゼンテーション層・トランザクション境界の管理など)
これらは、Laravel × DDD × クリーンアーキテクチャ実践ガイドが扱う領域です。 第 10 章のサービスクラス抽出も、大きくなったクラスを分割するというクラス設計の視点にとどまり、トランザクション境界の管理やドメインイベントの発行といった、ユースケース層としての設計には立ち入りません。
まとめ
- Controller に業務ロジックが詰め込まれ肥大化した状態を Fat Controller と呼ぶ
- 処理を Model に移しただけでは、詰め込みの構造自体は変わらず Fat Model になりうる
- 第 2 部は、ここまでの原則を Laravel に応用してこの 2 つを避ける設計を扱う
- DDD 戦術パターンやクリーンアーキテクチャの層実装は、既存連載が担う範囲として立ち入らない
次に読む
次章では、Eloquentモデルの責務を扱います。 モデルに書いていい責務と、書くべきでない責務の判断基準を見ていきます。
練習問題
次の処理を書く場所として、Controller・Model・Serviceクラスのどれが適切か、理由とともに答えてください
- リクエストのバリデーション
- 在庫確認と注文作成をまとめた一連の処理
- データベースへの保存(Eloquent のクエリ)
解答例
- Controller(またはForm Request): リクエストの形式チェックは、Controller の手前で行う入り口の処理です
- Serviceクラス: 複数の関心事にまたがる一連の処理は、第 10 章で扱うサービスクラスの責務です
- Model: 個々のデータの永続化は、Eloquent モデル自身の責務です
この判断基準は、第 9 章・第 10 章で詳しく扱います。