コードロード

エラー討伐

【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;
    }
}