【Laravel】レイヤードアーキテクチャで仕様変更に強くする
この本でLaravel勉強中なので、自分用にアウトプットしておく。
レイヤードアーキテクチャ
ビジネスロジックを表現するサービスレイヤから非機能要件などを可能な限り取り除き、影響範囲を小さくすることが最大の目的。
レイヤ化のための概念
- いくつかの概念をもとに分割して設計して、MVCパターンでのモデルやコントローラのクラスの肥大を防ぐ
- 上位レイヤから下位レイヤを呼び出すことを徹底する。その逆は禁止。
- ビジネスロジックの複雑化を防いで、ビジネスロジックを担当する層から、さまざまな依存を排除して抽象化する
-> 仕様変更への対応、テストの容易さなどに役立つ
下記のコントローラ上に記述されたDB操作を、レイヤードアーキテクチャに沿って分割していく。
<?php namespace App\Http\Controllers; use App\User; use App\Purchase; class UserController { public function index(string $id) { $user = User::find(intval($id)); $purchase = Purchase::findAllBy($user->id); // DBから取得した値を使った処理など return view('user.index', ['user' => $user]); } }
全体の流れ
モデルとコントローラの分離
DB処理がモデルとして役割を担っている場合、ビジネスロジック・Eloquentモデル・コントローラが強く結合する状態になる。これを解消していく。
まずは、コントローラからDB処理を排除するために、DB処理をサービスクラスに分離。
<?php namespace App\Service; use App\User; use App\Purchase; class UserPurchaseService { public function retrievePurchase(int $identifier): User { $user = User::find($identifier); $user->purchase = Pucrchase::findAllBy($user->id); // DBから取得した値を使った処理など return $user; } }
コントローラ側からDB処理を排除。
<?php namespace App\Http\Controllers; use App\User; use App\Purchase; class UserController { protected $service; public function __construct(UserPurchaseService $service) { $this->service = $service; } public function index(string $id) { $result = $this->service->retrievePurchase(intval($id)); return view('user.index', ['user' => $result]); } }
サービスレイヤとDBの分離
ビジネスロジックを解決するロジックは、まだDBに依存した状態。
DBへの依存を解決するために、DB操作を抽象化して直接的な操作から分離するリポジトリ層を作る。
リポジトリ層でDBを操作して、サービスレイヤからDB操作を切り離していく。
リポジトリのインターフェースを定義。
<?php namespace App\Repository; interface UserRepositoryInterface { public function find(int $id): array; }
リポジトリを実装。
<?php namespace App\Repository; use App\User; class UserRepository implements UserRepositoryInterface { public function find(int $id): array { $user = User::find($id)->toArray; // DBから取得した値を使った処理など return $user; } }
サービスクラスからDB操作を排除。
<?php namespace App\Service; use App\Repository\UserRepositoryInterface; use App\User; class UserPurchaseService { protected $userRepository; public function __construct(UserRepositoryInterface $userRepository) { $this->userRepository = $userRepository; } public function retrievePurchase(int $identifier): User { // レポジトリを介したデータの取得 $user = $this->userRepository->find($identifier); // DBから取得した値を使った処理など return $user; } }