Laravelでリポジトリパターン使う派?使わない派?

西山秀治 / 2025年12月19日

UX / UI のデザインに強いWebシステムの開発と、BtoB Webマーケを支援するWeb制作を提供するN’s Creates (エヌズクリエイツ) 株式会社の西山です。

Laravelコミュニティでは、定期的にこのような議論が巻き起こります。

「Eloquent(ORM)が優秀すぎるから、リポジトリパターンなんて作るだけ無駄じゃない?」

確かに、Eloquentは非常に強力です。コントローラーやサービスの中で User::where(...)->get() と書けば、直感的かつ爆速で機能を作れます。
わざわざインターフェースを切って、クラスを作って…というリポジトリパターンは、単なる「オーバーエンジニアリング(過剰設計)」に見えるかもしれません。

しかし、クリーンアーキテクチャの観点や長期的な運用を考えると、リポジトリには独自の重要な役割があると考えます。

今回は、不要論の背景にある誤解を解きつつ、「私がそれでもLaravelでリポジトリパターンを採用する理由」について解説します。


よくある誤解:「将来DBを交換するため」

リポジトリパターン導入の理由として、教科書的によく語られるのがこれです。

  • 「将来MySQLからPostgreSQLやNoSQLに移行するかもしれない」
  • 「リポジトリを使っていれば、呼び出し側のコードを変えずにDBを差し替えられる」

現実的には、そのようなケースは稀です。

実際のプロジェクトで、運用途中からRDBMSを丸ごと入れ替えることはめったにありません。もしあったとしても、それはリポジトリ層だけで吸収できるほど簡単な作業ではないことが多いでしょう。

「DB差し替え」だけをメリットとして挙げると、「そんなことしないから不要」という結論になりがちです。
私が考えるリポジトリパターンの真の目的は、もっと実務的な「テストのしやすさ」「関心の分離」にあります。


リポジトリがない場合の弊害(密結合)

Eloquentをビジネスロジック(Service層など)で直接使うとどうなるか、コードで見てみましょう。

❌ 悪い例:ビジネスロジックがEloquentに依存している

class PurchaseService
{
    public function checkout(User $user)
    {
        // ビジネスロジックの中に「DBの具体的な操作」が混ざっている
        // 「どのカラムを見るか」「どう結合するか」をここで知ってしまっている
        $vipUsers = User::where('is_vip', true)
                        ->where('points', '>', 1000)
                        // WAF対策のため記述を簡略化しています
                        ->get();

        // ... 購入処理 ...
    }
}

このコードの問題点は2つあります。

  1. テストが難しい: User::where... は静的メソッド呼び出しです。このメソッドを単体テストしようとすると、実際にDBを用意するか、Eloquentの複雑なモック(shouldReceiveのチェーン)を書く羽目になります。
  2. 関心の漏洩: 「VIPユーザーの条件」というビジネスルールが、SQL(クエリビルダ)と混ざっています。もしVIPの条件が変わったら、アプリ中に散らばった where を探して直さなければなりません。

✅ 良い例:リポジトリによる「関心の分離」

リポジトリパターンを使うと、コードはこう変わります。

// 1. インターフェース(契約)
interface UserRepositoryInterface
{
    public function findVipUsers(): Collection;
}

// 2. 実装(詳細)
class EloquentUserRepository implements UserRepositoryInterface
{
    public function findVipUsers(): Collection
    {
        // 具体的なSQL(Eloquent)はここに閉じ込める
        return User::where('is_vip', true)
                   ->where('points', '>', 1000)
                   ->get();
    }
}

// 3. 利用側(ビジネスロジック)
class PurchaseService
{
    public function __construct(
        private UserRepositoryInterface $userRepo
    ) {}

    public function checkout(User $user)
    {
        // 「どうやって取るか」は知らない。「VIPユーザーが欲しい」と頼むだけ。
        $vipUsers = $this->userRepo->findVipUsers();

        // ... 購入処理 ...
    }
}

こうすることで、Service層は「Eloquent」や「DBのカラム名」を知る必要がなくなりました。
これが「ビジネスロジックをフレームワーク(詳細)から切り離す」ということです。


最大のメリット:テストが爆速・簡単になる

インターフェースに依存させたおかげで、ユニットテストを書く際に「本物のDB」も「Eloquent」も不要になります。
単純なモックに差し替えるだけで済むからです。

// テストコード例(Pest / PHPUnit)

// リポジトリのモックを作成
$mockRepo = Mockery::mock(UserRepositoryInterface::class);

// 返却するダミーデータを用意
$vipUsers = collect([ new User() ]);

// 「findVipUsersが呼ばれたら、このダミーデータを返せ」と命令
$mockRepo->shouldReceive('findVipUsers')->andReturn($vipUsers);

// Serviceにモックを渡してテスト実行
$service = new PurchaseService($mockRepo);
$service->checkout(new User());

DBに接続しないのでテストは一瞬で終わります。
「複雑なSQLが正しく動くか」はリポジトリのテストで確認し、「データが取れた後の計算処理」はServiceのテストで確認する。これが健全な役割分担です。


まとめ:リポジトリは「通訳」である

リポジトリパターンの要点は以下の通りです。

  • 誤解: DBをMySQLからPostgreSQLに変えるために使う。
  • 正解: ビジネスロジック(Service)を、DBの実装詳細(Eloquent)から守るために使う。
  • 正解: DBなしで高速にユニットテストを回すために使う。

リポジトリは、「ドメイン(ビジネスの世界)」と「インフラ(DBの世界)」をつなぐ通訳です。
小規模なアプリなら不要かもしれませんが、長く運用し、テストで品質を担保したいプロジェクトであれば、この「通訳」を雇うコストを払う価値は十分にあります。

UX / UI のデザインに強いWebシステムの開発と、BtoB Webマーケを支援するWeb制作を提供する
N's Creates 株式会社は、神戸三宮オフィスまで週1出社(それ以外はリモートワーク)できる「デザイナー」「エンジニア」を募集しています。

興味のある方は、カジュアル面談しますので気軽にお問い合わせください!

同じテーマの記事