LaravelのService層を導入して可読性と保守性を高める方法

Laravelで開発を続けていると、「Controllerが肥大化してきた」「ロジックの再利用がしづらい」と感じることはありませんか?
この記事では、Service層(サービスクラス)を導入することで、アプリケーションの保守性・テスト容易性を高める方法を解説します。


Controllerが肥大化する理由

LaravelのControllerは便利ですが、ビジネスロジックやデータ加工処理を直接書き込むと、次のような問題が生じます。

  • コントローラが長くなり可読性が落ちる
  • 同じロジックを別のコントローラで再利用できない
  • 単体テストがしづらい(外部依存が多くなる)

例:肥大化したController

class OrderController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'user_id' => 'required',
            'items' => 'required|array',
        ]);

        $order = new Order();
        $order->user_id = $validated['user_id'];
        $order->total_price = collect($validated['items'])->sum('price');
        $order->save();

        foreach ($validated['items'] as $item) {
            OrderItem::create([
                'order_id' => $order->id,
                'product_id' => $item['id'],
                'price' => $item['price'],
            ]);
        }

        return response()->json($order);
    }
}

このように、注文作成ロジックとHTTPリクエスト処理が混在しています。


Service層を導入する

Service層を導入して、ビジネスロジックを切り出します。

ディレクトリ構成

app/
 ├─ Http/
 │   └─ Controllers/
 └─ Services/
     └─ OrderService.php

Serviceクラスの例

namespace App\Services;

use App\Models\Order;
use App\Models\OrderItem;

class OrderService
{
    public function createOrder(int $userId, array $items): Order
    {
        $order = Order::create([
            'user_id' => $userId,
            'total_price' => collect($items)->sum('price'),
        ]);

        foreach ($items as $item) {
            OrderItem::create([
                'order_id' => $order->id,
                'product_id' => $item['id'],
                'price' => $item['price'],
            ]);
        }

        return $order;
    }
}

Controller側のリファクタ後

class OrderController extends Controller
{
    public function __construct(private OrderService $orderService) {}

    public function store(Request $request)
    {
        $validated = $request->validate([
            'user_id' => 'required',
            'items' => 'required|array',
        ]);

        $order = $this->orderService->createOrder(
            $validated['user_id'],
            $validated['items']
        );

        return response()->json($order);
    }
}

Service層を導入するメリット

  1. Controllerが薄くなり、役割が明確になる
    → HTTPリクエストとレスポンス処理に専念できる。
  2. ビジネスロジックの再利用が可能
    → 同じ処理をジョブやスケジューラからも呼び出せる。
  3. テストが容易になる
    → Service層を単体でテスト可能。

テスト例

public function testCreateOrder()
{
    $service = new OrderService();

    $order = $service->createOrder(1, [
        ['id' => 10, 'price' => 1200],
        ['id' => 11, 'price' => 800],
    ]);

    $this->assertEquals(2000, $order->total_price);
    $this->assertCount(2, $order->items);
}

まとめ

Laravelはシンプルな構造でも十分動作しますが、長期的な保守を考えるとService層の導入は非常に有効です。
チーム開発や大規模化を見据えた設計を行うことで、後からの改修コストを大幅に抑えることができます。


関連リンク