リポジトリパターンと Laravel アプリケーションでのディレクトリ構造

[DMMグループ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/dmm) 4日目の記事です。

この記事では、弊チームの一部プロダクトで採用したリポジトリパターンについて紹介します。

## リポジトリパターンについて

リポジトリパターンとはビジネスロジックとデータ操作のロジックを分離し、データ操作を抽象化したレイヤに任せるデザインパターンのことです。
リポジトリパターンでは、DBの操作や外部APIによるデータ取得等のデータソースへのアクセス部分は Repository インターフェースから完全に隠蔽されます。
そのため、アプリケーションはデータソースがDBであっても外部API静的ファイルであっても、それを意識することなくデータ操作を行うことができます。

## ディレクトリ構造

今回採用したディレクトリ構造の例です。

```
app
|--Console
|--Exceptions
|--Http
| |--Controllers
| |--Middleware
| |--Requests
|--Models
| |--Article.php
| |--ArticleImage.php
| |--User.php
|--Providers
| |--AppServiceProvider.php
|--Repositories
| |--Article
| | |--ArticleRepository.php
| | |--EloquentArticleRepository.php
| |--User
| | |--UserRepository.php
| | |--DummyUserRepository.php
| | |--UserAPIRepository.php
|--Services
| |--ArticleService.php
```

Model クラスはデフォルトの場合 app 直下に設置されますが、Model が増えた際に視認性が悪くなるため、今回は models/ 配下に配置しています。


以下で各層の役割を説明します。

#### Service

この構造では Repository と Controller の間に、ビジネスロジックを管理するための Service を追加しています。
複雑なビジネスロジックを Service に切り出し、 Repository はデータの操作のみを行うように責務を分離することで、 Repository が複雑になることを防ぎます。

また、今回の場合だと Controller は外部からと外部への値の受け渡しのみを担っています。
しかし、ビジネスロジックがそれほど複雑でない場合は、 Service 層を実装せずに Controller に実装する方法でも良いかもしれません。

#### Repository

Repository 層は1つのインターフェースと1つ以上の Repository の実態で構成されています。

```
|--Repositories
| |--User
| | |--UserRepository.php
| | |--DummyUserRepository.php
| | |--UserAPIRepository.php
```

リポジトリパターンを使用することで、「特定の環境時は外部のAPIへの参照をダミーデータに変更する」「使用するDBを変更する」といった場合に対応しやすくなります。

例として、「通常は外部のAPIからユーザデータを取得し、テスト時はダミーデータを取得したい」という場合を考えてみます。

UserRepository はインターフェイスです。

```php:UserRepository.php
interface UserRepository
{
    public function findUserByToken(string $token): User;
}
```

UserAPIRepository では外部のAPIから取得したデータを返しますが、 DummyUserRepository ではダミーデータを返すような作りにします。

```php:UserAPIRepository.php
class UserAPIRepository implements UserRepository
{
    public function findUserByToken(string $token): User
    {
        try {
            $user = $this->getUser($token); // 外部APIからユーザー情報を取得する処理（省略）
            return $user;
        } catch(Exceptions $e) {
            throw new NoUserException();
        }
    }
}
```

```php:DummyUserRepository.php
class DummyUserRepository implements UserRepository
{
    public function findUserByToken(string $token): User
    {
        $dummyUser        = User(); // Eloquent モデルではないただのクラス
        $dummyUser->id    = 1;
        $dummyUser->name  = 'ほげほげ';
        $dummyUser->token = 'abcd1234';

        if ($dummyUser->token === $accessToken) {
           return $dummyUser;
        }
        throw new NoUserException();
    }
}
```

AppServiceProvider で環境ごとに注入する Repository を変更します。

```php:AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        // テスト環境の場合はダミーデータ用の Repository に向ける
        if (App::environment('testing')) {
            $this->app->bind(UserRepository::class, DummyUserRepository::class);
        } else {
            $this->app->bind(UserRepository::class, UserAPIRepository::class);
        }
    }
}
```

これにより、テスト環境のときはデータの取得方法を変更することができます。

このようにインターフェースを介すことで、実際の操作方法を意識することなくデータの操作を行うことができます。

#### Repository と Model

Model と Repository は 1:1 とは限りません。

例えば、以下のような要件があったとします。

- 「記事（ articles ）」には必ず「記事の作者であるユーザ（ users ）」がおり、記事の削除・更新は作者のみ可能
- 「記事（ articles ）」には「画像（ article_images ）」が紐付いており、この画像は全くない場合もあれば複数ある場合もある

![er.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214333/10a3cfd6-dc4a-95e3-ad68-2fe5c082d7c1.png)


仮に、Article クラスと ArticleImage クラスに対してリポジトリ、 ArticleRepository と ArticleImageRepository を実装したとします。

「画像（ article_images ）」は「記事の作者であるユーザ（ users ）」を知らないため、画像を削除・追加しようとした場合に「記事（ articles ）」を経由して「作者（ users ）」を取得するようなロジックを ArticleImageRepository に記述する必要があります。
もちろんこのロジックは ArticleRepository にも必要なため、データの整合性を担保するためのロジックが複数の Repository に分散してしまうことになります。
これを防ぐために、関連するオブジェクト郡（ Article と ArticleImage ）を1つの塊として考えて、両方のオブジェクトを扱う ArticleRepository を実装します。

関連するオブジェクトを集約することで、ロジックの分散を防ぎつつデータの整合性を担保することができます。

```
|--Models
| |--Article.php
| |--ArticleImage.php
| |--User.php
|--Repositories
| |--Article
| | |--ArticleRepository.php
| | |--EloquentArticleRepository.php

```

## おわりに

この記事では、弊プロダクトで採用したリポジトリパターンを紹介しました。

以下、まとめです。

- リポジトリパターンを使用することで、アプリケーションはデータの操作方法を意識することなくデータ操作を行うことができる
- Repository には複雑なビジネスロジックを記述せず、データの操作のみ行うよう責務を分離することで Repository の複雑化を防ぐ
- 関連するオブジェクト（ Model ）は1つの塊として考え、1つの Repository に集約する

この記事を通してどなたかのお役に立てれば幸いです！

明日の [DMMグループ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/dmm) の担当は、 @arika_nashika さんです！