1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravelのサービスクラス設計について

1
Posted at

💡 はじめに

Laravelでサービスクラスを設計する時、
MainService(代表サービス) を挟むべきかどうか、迷ったことはありませんか?

たとえば以下のようなドメインがあるケース:

  • 発送(send)
  • 入金(payment)
  • 契約(contract)

それぞれに一覧・登録・編集などの機能があり、 それをどう整理するかがテーマです。

この記事では、代表サービスを作るべきかどうかに結論を出しつつ、
実際に運用しやすい設計パターンを整理します。

✅ 結論(結論ファースト)

Laravelで MainService 的な代表サービスは原則不要。
サービスは以下のいずれかで十分:

  • ドメイン単位で1サービス
  • ユースケース(処理)単位で複数サービス

代表サービスが必要になるのは、
複数の処理をまとめて1つの“フロー”として使いたい時のみ。

❓ なぜ代表サービスは不要なのか

1. レイヤーが増えて分かりにくい

代表サービスで List / Store / Update などをラップすると、
依存関係が深くなってわかりにくくなります。

Controller
→ MainService
→ ListService
→ StoreService
→ UpdateService

この構造は「責務を隠すだけ」で、
かえって読み手に不要な階層を強いることになります。


2. 責務が逆流して God クラス化する

MainService に一覧・登録・編集すべてを集約すると、
結局 MainService が責務の塊になりやすいです。

責務の分離という OOP のメリットを
逆に殺してしまうパターンです。


3. 再抽象化になっている

代表サービスは、
ユースケースごとに分割した責務を
再び一箇所でまとめているだけ。

OOP設計の理想である「一意の責務」は失われがちです。

🧱 オススメ設計パターン

🟢 パターン1: ドメイン単位のサービス

各ドメインに1つサービスを持つ構成。

App\Services\Send\SendService
App\Services\Payment\PaymentService
App\Services\Contract\ContractService

💡 サンプルコード

namespace App\Services\Payment;

class PaymentService
{
    public function list()
    {
        return Payment::all();
    }

    public function store(array $data)
    {
        return Payment::create($data);
    }

    public function update(Payment $payment, array $data)
    {
        $payment->fill($data);
        $payment->save();

        return $payment;
    }
}

コントローラ例

namespace App\Http\Controllers;

class PaymentController extends Controller
{
    public function index(PaymentService $service)
    {
        return $service->list();
    }
}

メリット

・依存がシンプル
・読みやすい
・テストしやすい

🟡 パターン2: ユースケース単位で分割する

処理単位で1クラスにする方法。

App\UseCases\Payment\ListPaymentsAction
App\UseCases\Payment\StorePaymentAction
App\UseCases\Payment\UpdatePaymentAction

💡 サンプルコード

namespace App\UseCases\Payment;

class StorePaymentAction
{
    public function __invoke(array $data)
    {
        return Payment::create($data);
    }
}

コントローラ例

public function store(StorePaymentAction $action)
{
    return $action(request()->validated());
}

メリット

・1クラス1責務で可読性高い
・単体テストしやすい

デメリット

・ファイル数が増える(ただし慣れれば問題なし)

🧩 例外: 代表サービスが必要になるケース

代表サービスが合理的になるのは、
複数ユースケースを1つのフローとしてまとめたい時です。

例:「契約 → 決済 → 発送」を一つの一連処理として実行する場合

この時に作るのは フローサービス であり、単なるラッパーではなく 再利用できるシナリオ になります。

例: App\Flows\OrderFlowService

📌 まとめ

MainService は基本不要

ドメイン単位 or ユースケース単位で設計し、責務を無駄にまとめる抽象化は避ける
(ただし必要な場面でフロー用サービスを作るのはアリ)

設計は状況による部分もありますが、
迷ったらこの順で考えるとスッキリします。

一言

今回ふと保守中のプロジェクトを眺めていて、ごちゃついたサービスクラスを見て、本当はどうあるべきかを調べてみました。

ついでにサクッと文章化したくて、今回初めてAIくんを通してみたのですが、参考書みたいな感じになって個人的には微妙でしたね。
今後は頼らないと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?