【Laravel】サービスクラス設計パターン|ビジネスロジックの分離とテスト性の両立

【Laravel】サービスクラス設計パターン|ビジネスロジックの分離とテスト性の両立 Laravel

Laravelでは、コントローラーにビジネスロジックを直接書くと、保守性・再利用性・テスト性のいずれも損なわれやすくなります。その解決策として有効なのが「サービスクラス」の設計です。

この記事では、Laravelにおけるサービスクラスの設計パターンと、ビジネスロジックの分離によるメリット、テストコードとの連携方法を実例を交えて解説します。

サービスクラスとは?

サービスクラスとは、コントローラーとモデルの間に立って、ビジネスロジックをまとめる役割を担うクラスです。例えば、ユーザー登録処理、決済処理、メール通知など複数の処理を組み合わせるロジックを1つの責任としてまとめます。

従来のコントローラー:

public function store(Request $request)
{
    $user = User::create([...]);

    Notification::send($user, new WelcomeNotification());

    event(new UserRegistered($user));

    return redirect()->route('dashboard');
}

このように複数の処理が混在する場合、テストしづらく、ロジックの流用も困難になります。

サービスクラスの基本構造

Laravelでは、以下のようなディレクトリ構成でサービスクラスを配置するのが一般的です。

app/
├── Services/
│   └── UserService.php

サービスクラスの実装例:

namespace App\Services;

use App\Models\User;
use App\Notifications\WelcomeNotification;
use Illuminate\Support\Facades\Notification;

class UserService
{
    public function register(array $data): User
    {
        $user = User::create($data);

        Notification::send($user, new WelcomeNotification());

        event(new \App\Events\UserRegistered($user));

        return $user;
    }
}

コントローラーからの呼び出し

コントローラーでは、責務を限定し、サービスクラスに処理を委譲します。

use App\Services\UserService;

class RegisterController extends Controller
{
    public function __construct(protected UserService $userService) {}

    public function store(Request $request)
    {
        $user = $this->userService->register($request->validated());

        return redirect()->route('dashboard');
    }
}

__construct()で依存注入(DI)すれば、サービスクラスのテストやMock差し替えも容易になります。

サービスクラス設計のメリット

  • 関心の分離:コントローラーはルーティングとレスポンスのみに集中
  • 再利用性:同じロジックをAPI・バッチ・CLIなどでも流用可能
  • テスト性:サービス単位でユニットテストを書きやすくなる

サービスクラスに依存するテストの書き方

サービスクラスにメソッドを持たせることで、ユニットテストを明確に定義できます。

public function test_user_registration()
{
    $service = new UserService();

    $user = $service->register([
        'name' => 'テストユーザー',
        'email' => 'test@example.com',
        'password' => bcrypt('secret'),
    ]);

    $this->assertDatabaseHas('users', ['email' => 'test@example.com']);
}

ビジネスルールをサービス単位でテストできることで、LaravelのFeatureTestよりも高速かつ安定したテストを構築できます。

よくあるアンチパターンと改善ポイント

サービスクラスにすべてを詰め込みすぎると、巨大な「神クラス」になりやすくなります。以下のような設計に注意しましょう。

  • DB処理・通知処理・バリデーションが混在 → 処理単位でメソッド分割
  • 1クラスで10以上のpublicメソッド → サブサービスやアクションクラスへの委譲を検討

サービスクラスはあくまで「中間制御役」であり、実装は可能な限り小さく保つことが推奨されます。

まとめ

Laravelにおけるサービスクラスは、ビジネスロジックを整理し、保守性・テスト性・再利用性を高める設計の核です。

  • 複雑なロジックをコントローラーから分離
  • 依存注入でテスト可能な構造を実現
  • ルールに応じてクラス分割・責務整理を行う

規模が大きくなるほど、ロジックの所在が明確な設計が求められます。サービスクラスを上手に使い、Laravelプロジェクトを長期的に運用しやすい構造にしていきましょう。