5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FatなControllerにお困りなあなた! ADR(Action Domain Responder)はいかが?

Posted at

初めに

MVC(Model View Controller)のデザインパターンが流行してから幾星霜。星の数ほどのWebアプリが作られては消え、また作られてを繰り返して来ました。
その流れの中で大きなサービスをMVCで作れば作るほど、とある問題に衝突することになりました。
そう、Controllerの肥大化です。
アプリが大きくなればなるほど要件、仕様、処理工程は増えいき、Controllerのサイズは増えていったはずです。そうなれば仕様把握だけでも一苦労、内部でメソッド同士が絡み合い、豊潤な香りを醸し出した事でしょう。
この問題を解決すべく、新しいデザインパターンが考案され始めました。
それが*ADR(Action Domain Responder)*です。
この記事では、ADRがどんな物なのか、既存コードに対してのリファクタリングなども軽く紹介していきたいと思います。

ADR(Action Domain Responder)

ADR(Action Domain Responder)とはMVC2(よくWebアプリで言われるMVC)の洗練系であり、サーバサイドアプリケーション向けのデザインパターンです。
そのコンポーネントはMVCの各コンポーネントととても似通っています。
以下はADRについて議論されているGithubから引用したコンポーネントの俯瞰図です。
adr.png
DomainはModel、ActionはController、ResponderはViewのコンポーネントにそれぞれ似通った働きを見せますが、データの受け渡しの流れ、扱うデータの入出力などが微妙に異なります。
ここで「ああ、MVCのこれとこれが同じやつね」と雑な解釈をすると、場外から怖ーいお兄さんたちがプルリクという棍棒で歪みを修正しにくるので、主観をなるべく排除して書いていきたいと思います。(3敗)

Action Component

Action ComponentはHTTP Requesから収集した情報を基に、Domain Componentを呼び出し、HTTP Responseを行うための情報を収集します。
その後、Domain Componentからの情報をResponder Componentに引き渡すまでがAction Componentの役割です。

Action Component と Controller Compomentの違い

Action ComponentとController Componentの大きくわかりやすい違いは、Controller Componentが異なるアクションに対応する複数のメソッドを持つクラスを定義するのに対し、Action Compomentは1つのメインメソッドのみを持つクラスとして実装します。
例えば以下のようなControllerがあったとします。従来のMVCセオリーに則ったCRUDの処理が1クラスにまとめられたような形式です。

<?php
namespace App;

class BlogController {
  public function get() {}
  public function read() {}
  public function create() {}
  public function delete() {}
}

これをAction Componentに書き換えると、

<?php
namespace App\BlogController;

  class get() {}
  class read() {}
  class create() {}
  class delete() {}

こんな形になります。
この例では小さなメソッドの分割にすぎませんが、これを様々な前後の処理工程を経るであろうプロダクションコードに適用するとなると話は変わります。
CRUDの処理を各クラスとして分割し、さらにファイルごとに分割することで、ぶくぶくと肥え太るだけだったControllerちゃんがスリムかつコンパクトに大変身を遂げることでしょう。心なしか開発者の心も軽くなったような気がしますね。どこからか賛美歌でも聞こえてきそうです~~ ・ω・)<♪ハーッレルッヤ ハーッレルッヤ ハレルーヤ ハレルーヤ ハァレェールヤァー~~
「なるほど、つまりはControllerの主要メソッドをクラスに置き換えるのがActionだな?」
と思った読者の皆様におかれましては、もう少しだけAction CompomentとController Componentの差異を説明させていただきたく存じます。

ADRの概要などをまとめたGithub RepositoryMVC2とADRのコンポーネント間の対比において、このような記述があります。

意訳
Action ComponentはController Componentと同じようにDomain/Model Componentと対話しますが、View ComponentやTemplateシステムとは対話しません。
Action ComponentはResponder Componentにデータを送り、HTTP Responseを構築するために呼び出されます。

原文
The Action interacts with the Domain in the same way a Controller interacts with a Model but does not interact with a View or template system. It sends data to the Responder and invokes it so it can build the HTTP Response.

つまり、Action ComponentはController Componentの責任範囲をさらに狭めた形になり、役割がより厳密化された形での実装が望まれます。
これまでの要点をまとめると、

  • Action Componentは主要メソッドのみが定義される
  • Action ComponentはViewやTemplateシステムの責任を持たない

の2点が既存のController Componentとの大きな違いと言えるでしょう。

Domain Component

Domain Componentはアプリケーションのメインロジックへのエントリーポイントです。トランザクションスクリプト、サービスアプリケーションなどを提供します。
このComponentについてはModel Componentと殆ど同じ役割を担っています。

Domain ComponentとModel Componentの違い

前の見出しでも述べたとおり、Domain ComponentとModel Componentの違いは殆どありません。その違いといえば、Domain ComponentはResponder Componentと対話をしないことぐらいです。
したがって、MVCからADRへのリファクタリングで加筆修正を行うべき箇所の列挙などは行いません。
強いて挙げるならば、MVCでModel ComponentとView Componentが直接対話している箇所などを剥離させることぐらいでしょうか。
というのも、ADRに置いてのView Component相当、Responder ComponentはHTTP Responseの作成のためにEntityやCollectionなどのDomain Objectを利用することはあれど、Domain Componentへ新しい情報などを要求したり、逆に情報を送ったりしないのです。
情報の登録、整理、抽出などはあくまでAction Componentの役割であり、Domain Componentはビジネス上のロジックであったり、RDBの管理などを行う事がその役割なのです。

Responder Component

Responder ComponentはAction Componentから受け取ったデータを基に、HTTP Responseを構築するためのComponentです。
status code、header、cookie、contents、templateやViewなどのプレゼンテーション層の処理を担います。

Responder ComponentとView Componentの違い

概要からすぐに読み取れるResponder ComponentとView Componentとの違いは、MVCではControllerが扱っていたstatus code、header、cookieなどの情報を扱う事です。

「何でわざわざControllerの仕事をそちらに明け渡したんだ?」

という疑問が思い浮かんだ方、鋭いです。
この理由は、

同じDomain Componentのデータに対して異なるロジックを提供する場合、Controller Componentが扱うプレゼンテーション層の処理が不適切な分野まで及ぶのを防ぐためです。

Webアプリケーションのサーバサイドにおいて、出力として配信されるデータは、単純なHTTP Responseだけではありません。
プレゼンテーション層のデータ、status codeやheader、cookieなどの複雑な状態管理をController Componentの役割に過剰に含めた結果、何が起こりうるか。
そう、タイトルで提示したFat Controllerです。
既存のMVCではController Component毎にView Componentを用意し、固有のロジックをController Componentに含めて行きました。これによって、同じような処理を複数のModel Componentを跨ぐために複数記述したり、要件などの追加でさらに追記していったりということが必要になってしまい、Controllerへの記述量が膨れ上がって可読性が低下してしまうという事象が頻発しました。
これに対し、ADRでは一つのResponder Componentが複数のAction Componentを使用することを大いに想定しており、Action Component毎にResponder Componentを用意する必要はないのです。
さて、そんなResponder Componentのリファクタリングですが、ADRの概要をまとめたGithub RepositoryにREFACTORINGのコード部分があったので引用させていただきます。
以下が、既存のMVCにおけるController Componentです。

<?php
class BlogController
{
    public function __construct(
        Request $request,
        Response $response,
        TemplateView $view,
        BlogModel $model
    ) {
        // ...
    }

    public function index()
    {
        // ...
    }

    public function create()
    {
        // is this a POST request?
        if ($this->request->isPost()) {

            // retain incoming data
            $data = $this->request->getPost('blog');

            // create a blog post instance
            $blog = $this->model->newInstance($data);

            // is the new instance valid?
            if ($blog->isValid()) {
                // yes, insert and redirect to editing
                $blog->save();
                $this->response->setHeader('Location', "/blog/edit/{$blog->id}");
            } else {
                // no, show the "create" form with the blog record
                $html = $this->view->render(
                    'create.php',
                    ['blog' => $blog],
                );
                $this->response->setContent($html);
            }
        } else {
            // not a POST request, show the "create" form with a new record
            $html = $this->view->render(
                'create.php',
                ['blog' => $this->model->newInstance()]
            );
            $this->response->setContent($html);
        }

        return $this->response;
    }

    public function read()
    {
        // ...
    }

    public function update()
    {
        // ...
    }

    public function delete()
    {
        // ...
    }
}

これを、Action ComponentとResponder Componentに分割するために、プレセンテーション層部分とデータ収集部分を切り離します。
具体的には以下、

<?php
class BlogCreateAction
{
    public function __construct(
        Request $request,
        BlogCreateResponder $responder,
        BlogModel $model
    ) {
        // ...
    }

    public function __invoke()
    {
        // is this a POST request?
        if ($this->request->isPost()) {

            // yes, retain incoming data
            $data = $this->request->getPost('blog');

            // create a blog post instance
            $blog = $this->model->newInstance($data);

            // is the new instance valid?
            if ($blog->isValid()) {
                // yes, insert it
                $blog->save();
            }

        } else {
            // not a POST request
            $blog = $this->model->newInstance();
        }

        // use the responder to build a response
        return $this->responder->response($blog);
    }
}

<?php
class BlogCreateResponder
{
    public function __construct(
        Response $response,
        TemplateView $view
    ) {
        // ...
    }

    public function response(BlogModel $blog)
    {
        // is there an ID on the blog instance?
        if ($blog->id) {
            // yes, which means it was saved already.
            // redirect to editing.
            $this->response->setHeader('Location', '/blog/edit/{$blog->id}');
        } else {
            // no, which means it has not been saved yet.
            // show the creation form with the current data.
            $html = $this->view->render(
                'create.php',
                ['blog' => $blog]
            );
            $this->response->setContent($html);
        }

        return $this->response;
    }
}

最後に

ARDについて初めて知ったのは、とあるFWがきっかけでした。こう始めると随分昔のことなのかと思われるような気がしますが、割と最近です。超直近です。なので私はADR初心者なため、もしかすると何か変な解釈や引用の仕方をしているかもしれませんので、この記事をバイブルにように扱うのはかなり難しいと思われます。
明らかな間違いなどがあればお手やわからにご指摘のほどお願いいたします。
あと、api-platformについても興味を持っていただけると幸いです。別にこのプロジェクトのコミッターでもないのですが、割と面白いプロジェクトなのでMVCでViewなんか使わなくね? という諸兄は一度遊んでみてください。
api-platformについての記事も近々書きたいなーと思いつつ、今回はここで筆を置かせていただきます。
ご一読ありがとうございました。

参考文献


Paul M. Jones | Action Domain Responder

pmjones/adr: Action-Domain-Responder: a web-specific alternative to Model-View-Controller.

5
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
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?