LoginSignup
12
2

More than 3 years have passed since last update.

神Controller とさよならするためにADRパターンでAPIを実装する(Laravel)

Last updated at Posted at 2019-12-25

これは何

ADRパターンを使った設計でAPIを作成する。
特にLaravelでの Routes, Action, Responder 実装方法について書く。

環境

  • Laravel 6.x
  • PHP 7

ADRパターンとは

Action–domain–responder (ADR) is a software architectural pattern that was proposed by Paul M. Jones[1] as a refinement of Model–view–controller (MVC) that is better suited for web applications.

ということで、MVCパターン類似のwebアプリを作るにあたりいい感じに使えるパターンです。

Paul M. Jones(pmjones)氏による説明: http://pmjones.io/adr/

Laravel でADRパターンを実装する

Action と Routing の実装

routing ではメソッド名ではなく、クラス名を指定します。

routes/api.php
Route::get('/user_info', \App\Http\Api\User\Actions\InfoGetAction::class);

以下のように Actionを定義しておくと、メソッドとしてクラス名が呼ばれた時にはマジックメソッドの__invoke()が呼ばれます(コードはイメージです)。

\App\Http\Api\User\Actions\InfoGetAction.php
class InfoGetAction extends Controller
{
    private $userDomainService;

    public function __construct(
        UserDomainService $userDomainService
    ) {
        $this->userDomainService = $userDomainService;
    }

    public function __invoke(Request $request)
    {   
        // user をとってくる
        $user = $this->userDomainService->get($request->input('id'));

        return new InfoGetResponder(
            $user->getName(),
            $user->getLocation()
        );
    }

これにより Action クラスには一つしかメソッドが生えない制約になるので、1アクション1メソッドになります。
APIにおいては、URLパスとクラス階層が対応しやすくなり管理しやすくなりますし、神Controllerが作られないメリットがあります。

Responder の実装

app/Http/Api/User/Responders/InfoGetResponder.php
class InfoGetResponder extends BaseResponder
{
    private $userName;
    private $userLocation;

    public function __construct(
        Name $name,
        Location $location
    ) {
        $this->userName = $name;
        $this->userLocation = $location;
    }
}

BaseResponder の実装は以下のようになっています。

app/Http/Api/Shared/Responders/BaseResponder.php
abstract class BaseResponder implements Responsable
{
    public function toResponse($request): JsonResponse
    {
        return new JsonResponse(object_to_array($this));
    }
}

Responsable インターフェースを implements して toResponse() を実装しておくと、前述の Action における return new InfoGetResponder() により勝手に toResponse() を呼んでレスポンスにして返してくれます。
また、BaseResponder で戻り値を JsonResponse に固定してしまうことにより、Responder を実装した場合には Json しか返さないように縛れます。これは環境によって良し悪しだとは思いますが、自分たちしか使わないクローズなAPIの場合はデファクトのJsonに決めてしまって問題ないだろうと考えてこうなっています。

object_to_array() は再帰的にオブジェクトを array に変換してくれるメソッドです。
レポジトリ: https://github.com/polidog/object-to-array

レスポンスの例

この object を array にする形により、 /user_info にアクセスすると以下のようなレスポンスが帰ってきます。

{
    "user_name": "yamotuki",
    "user_location": "tokyo"
}

レスポンスのjsonフィールドは Responder の引数で指定している namelocation ではなくて、 Responder のクラスフィールドの以下のものを snake_case に変換したものになっています。

    private $userName;
    private $userLocation;

これにより、Responder を new するときにDomainを渡して、今後Domainのバリューオブジェクトの値がリファクタリングで変更されても、レスポンスフォーマットは変更されないようになっています。つまり、レスポンスの形式をドメインの実装と切り離すことができました。

12
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
2