インフラストラクチャ層のモジュール設計について

この記事は[Laravel Advent Calendar 2020](https://qiita.com/advent-calendar/2020/laravel)の18日目です。

# はじめに

ドメイン駆動設計で開発していてインフラストラクチャ層がとっ散らかりがちになるのが悩みの種でした。
『[実践ドメイン駆動設計](https://www.shoeisha.co.jp/book/detail/9784798131610)』の第9章にモジュールを使ってクラスを整理する方法が書かれていますが、ドメイン層のモジュールについての話がほとんどで、インフラストラクチャ層のモジュールについては書かれていません。

そんな折に見つけた『[Domain-Driven Design in PHP](https://leanpub.com/ddd-in-php)』という本に、いいなと思うインフラストラクチャ層のモジュール設計が書かれていたので紹介したいと思います。

# 『Domain-Driven Design in PHP』で推奨されているインフラストラクチャ層のモジュール設計

この本ではインフラストラクチャ層にドメイン層と同じ構成のモジュールを作ることを推奨しています。

```
|-- Domain
|   `-- Model
`-- Infrastructure
    `-- Domain
        `-- Model
```

ドメイン層のインターフェースをインフラストラクチャ層で実装するときは、対応するモジュールに配置します。

```
|-- Domain
|   `-- Model
|       `-- Bill
|           `-- BillRepositoryInterface.php
`-- Infrastructure
    `-- Domain
        `-- Model
            `-- Bill
                `-- EloquentBillRepository.php
```

私がこの設計をいいなと思った理由は、インターフェースの実装がどこにあるのかわかりやすくなる点と、インフラストラクチャ層のモジュール名にもユビキタス言語が使われるようになる点です。

ORMなどドメイン層に対応しないものは、その技術に関連したモジュールに配置します。

```
Infrastructure
|-- Domain
`-- Persistence
    `-- Eloquent
        `-- Models
            `-- Bill.php
```

本には書かれていませんでしたが、アプリケーション層のインターフェースをインフラストラクチャ層で実装するときもドメイン層のときと同じようにするのがいいと思います。

```
|-- Application
|   `-- DataTransformer
|       `-- Bill
|           `-- BillDataTransformerInterface.php
`-- Infrastructure
    `-- Application
        `-- DataTransformer
            `-- Bill
                `-- BillLaravelPaginatorDataTransformer.php
```

# Webアプリケーションフレームワークとの付き合い方

この本にはWebアプリケーションフレームワークとの付き合い方についても書かれています。
Webアプリケーションフレームワークを使う場合は、インフラストラクチャ層にデリバリーメカニズム（Web、API、コンソールなど）ごとのモジュールを作り、さらにその中にフレームワークごとのモジュールを作ることを推奨しています。

```
Infrastructure
|-- Delivery
|   |-- Api
|   |   `-- Laravel
|   |-- Console
|   |   `-- Symfony
|   `-- Web
|       `-- Silex
`-- Domain
```

上記の例ではAPIにLaravelを使い、コンソールにSymfonyを使い、WebにSilexを使っています。

ユーザーインターフェイス（コントローラやビューのことだと思います）に関しては、各フレームワークのモジュールに配置することを推奨しています。

本では各フレームワークのモジュール内部については特に書かれていませんでしたが、

> Frameworks should obey you, and not the other way around. （フレームワークがあなたに従うべきであり、その逆ではない。）

との言葉があったので、各フレームワークのモジュール内部はフレームワークの規約に縛られることなく自由に設計していいということだと思います。

# Laravelの例

以下は本に書かれていることではないですが、本を読んでみてLaravelを使う場合どうするか自分なりに考えてみました。

Laravelモジュールの中に自分のアプリケーションで使うLaravel関連のファイルを好きなように配置します。

```
Infrastructure
`-- Delivery
    `-- Web
        `-- Laravel
            |-- Controllers
            |-- Middleware
            |-- Providers
            |-- Requests
            |-- resources
            |   |-- fonts
            |   |-- js
            |   |-- lang
            |   |-- less
            |   `-- views
            `-- routes
```

これらのファイルをLaravelに使わせるには[設定ファイル](https://readouble.com/laravel/6.x/ja/configuration.html)を書き換えることになります。

また、設定ファイルを書き換える以外の方法として、[パッケージ機能](https://readouble.com/laravel/6.x/ja/packages.html)を使ってLaravelに公開することもできると思います。
この方法であれば設定ファイルを書き換えることすら不要になるので、Laravelをほとんど素のままで使うことができるはずです。

Laravelのドキュメントには書かれていませんが、ミドルウェアやイベントもパッケージとして公開できます。

ミドルウェアは`Illuminate\Support\ServiceProvider`を継承したサービスプロバイダーを作って、`boot`メソッドの中でLaravelの[API](https://laravel.com/api/6.x/Illuminate/Routing/Router.html)を使うことで登録できます。

```php:ミドルウェアの登録
public function boot()
{
    $this->app['router']->aliasMiddleware('my_middleware', MyMiddleware::class);
}
```

イベントも`Illuminate\Foundation\Support\Providers\EventServiceProvider`を継承したサービスプロバイダーを作れば、あとは普通にイベントを登録するときと同じように登録できます。

```php:イベントの登録
protected $listen = [
    // ...
];
```

# おわりに
『Domain-Driven Design in PHP』に書かれていたインフラストラクチャ層のモジュール設計について紹介しました。
また、Laravelを使う場合どうするか自分なりに考えてみました。

# 参考
- [Domain-Driven Design in PHP](https://leanpub.com/ddd-in-php)