トラストバンクふるさとチョイス事業本部CTO室に所属の海塩(うしお)と申します。
主に新規プロジェクトの立ち上げやアーキテクチャ設計を行っております。
前置き
PHP・Laravelにおけるクリーンアーキテクチャ実践の一例をご紹介します。
クリーンアーキテクチャ自体の説明はしておりませんのでご了承ください。
すでになんらかの形でドメイン駆動やクリーンアーキテクチャを実践している方に刺されば幸いです。
全体ボリュームがかなり大きいので、今回はディレクトリ構造のお話です。
今後は、
などを予定しています。
ディレクトリ全体像
こんな感じになります。
├── app │ ├── Console │ ├── Contexts │ │ └── {コンテキスト名} │ │ ├── Domain │ │ │ ├── Entity │ │ │ │ └── {エンティティ名} │ │ │ ├── Exception │ │ │ ├── Notification │ │ │ └── Persistence │ │ ├── Infrastructure │ │ │ ├── Http │ │ │ │ ├── Controller │ │ │ │ └── Request │ │ │ ├── Notification │ │ │ │ └── templates │ │ │ ├── Persistence │ │ │ └── Presenter │ │ │ └── templates │ │ └── UseCase │ │ └── {リソース名} │ │ └── {アクション名} │ ├── Exceptions │ ├── Http │ ├── Models │ ├── Policies │ ├── Providers │ └── View ├── bootstrap ├── config ├── database ├── public ├── resources ├── routes │ └── contexts │ └── {コンテキスト名} ├── storage ├── tests └── vendor
なるべくLaravel標準に従うことで、
- プロジェクト参画者のキャッチアップがしやすくなる
- Laravelのバージョンアップに低コストで追従できる
といったメリットが得られます。
app/Contexts
コンテキストを表現するディレクトリになります。
コンテキスト毎にレイヤー構成を持ち、コンテキストをまたいだ参照は一切無いようにします。
app/Contexts/{コンテキスト名}/Domain
クリーンアーキテクチャの「Enterprise Business Rules」と「Interface Adapters」にあたります。
ビジネスロジックがここに集まるようにします。
Laravelに関連するコード(グローバルメソッドやファサードも含む)は置いてはいけません。
app/Contexts/{コンテキスト名}/Domain/Entity
エンティティを配置します。
エンティティに紐づく値オブジェクトはサブディレクトリに配置します。
app/Contexts/{コンテキスト名}/Domain/Exception
コンテキストのビジネスロジックにより発生する例外を配置します。
app/Contexts/{コンテキスト名}/Domain/Notification
ビジネスロジックにおいて、何らかの通知を行う場合のインターフェースを配置します。
UserStoredNotification
みたいな名前で、handle
メソッドのみを持つインターフェースであることが多いです。
app/Contexts/{コンテキスト名}/Domain/Persistence
エンティティの永続化、および復元に関連するインターフェースを配置します。
以下のセットをリソース単位で用意することが多いです。
UserRepository
:単一エンティティの永続化と復元を行うインターフェースUserListRepository
:エンティティの一覧の復元を行うインターフェースUserRepositoryRecord
:エンティティとリポジトリとの間で使用するDTOインターフェース
app/Contexts/{コンテキスト名}/Infrastructure
クリーンアーキテクチャの「Frameworks and Drivers」にあたります。
ビジネスロジックの実現手段をここに配置します。
app/Contexts/{コンテキスト名}/Infrastructure/Http
Web経由でのコントローラを配置します。
LaravelのControllerやFormRequestを活用します。
本家クリーンアーキテクチャと大きく動きが異なります。
コントローラはユースケースを呼んで終わりではなく、戻りを受け取ります。
さらにユースケースからの戻りをプレゼンターへ渡してUIを生成し、Webのレスポンスとして返却します。
app/Contexts/{コンテキスト名}/Infrastructure/Notification
Domain/Notificationのインターフェースの実装を配置します。
メール通知であればLaravelのMailableやSendGrid APIを活用します。
app/Contexts/{コンテキスト名}/Infrastructure/Persistence
Domain/Persistenceのインターフェースの実装を配置します。
データベースへの永続化であればLaravelのEloquent Modelを活用します。
app/Contexts/{コンテキスト名}/Infrastructure/Presenter
Web経由でUIを伝えるための実装を配置します。
クリーンアーキテクチャの「Interface Adapters」にあたりますが、上述の通り構成はだいぶ異なります。
同じレイヤのコントローラから呼ばれて戻り値としてUIを返すことになるため、インターフェースと実装は分離しません。
app/Contexts/{コンテキスト名}/Infrastructure/Presenter/templates
コンテキスト固有のBladeテンプレートを配置します。
layoutや共通パーツなどはLaravel標準のresources/viewsに置きます。
\Illuminate\Support\Facades\View::addNamespace
によって動的にディレクトリを設定するため、configやLaravelコードはいじりません。
app/Contexts/{コンテキスト名}/UseCase
クリーンアーキテクチャの「Application Business Rules」にあたります。
Domainコードを活用し、ビジネスロジックを組み立てます。
ここにもLaravelに関連するコード(グローバルメソッドやファサードも含む)は置いてはいけません。
終わりに
いかがでしたでしょうか。
クリーンアーキテクチャの実践には正解がなく、日々改善を重ねています。
トラストバンクでは、堅牢で変更に強いプログラムを書きたいエンジニアの方を募集しています!