【新人エンジニア】MVCモデルの進化版!? ADRが使いやすかったお話

こんにちは!
7月からインターン生として株式会社Hajimariに入社した、難波 慧人です。

現在は、TUKURÜS事業部で受託開発の業務を行っています! 今行っている案件では、サブスクリプション型動画配信サイトの、新規機能開発・運用保守を担当しています!

開発言語に関しては、 バックエンドはPHP(laravelフレームワーク)を用いており、アーキテクチャADR(Action Domain Responder)を採用しています。

案件にジョインした当初、MVCアーキテクチャしか知らない私でしたが、ADRの有用性が少しずつ理解できてきました!

そこで今回は、MVCアーキテクチャと、ADRアーキテクチャの違いについてご紹介したいと思います!!

また、各項目にサンプルコード(ユーザーの一覧、詳細機能)を示していきます!!

MVC(Model View Controller)とは??

引用元( https://white-t007.com/tech/it%E5%9F%BA%E7%A4%8E/mvc/

ユーザーインターフェイスを持つアプリケーションを、モデル(Model)、ビュー(View)、コントローラー(Controller)の3つの責務に分割して、実装していく手法です。

qiita.com

モデル(Model)

モデルとは、アプリケーションの中で、ビジネスロジック、データベースとのアクセスを担っています。例えば、計算、データ取得、データ管理などが挙げられます。

Laravelフレームワークでは、Eloquent ORMと呼ばれる、モデルとデータベースの紐付けを行う機能があります。
Eloquent ORMにより、アプリケーションとデータベース間のデータのやりとりをより円滑にすることができます!! readouble.com

  • サンプルコード
Models/User.php

class User extends Model
{
    /**
     * usersテーブルから全てのユーザーを取得
     * 
     * @return Collection
     */
    public function fetchAllUsers(): Collection
    {
        return User::all();
    }

    /**
     * usersテーブルから指定ユーザーを取得
     * 
     * @return ?User
     */
    public function fetchUser($userId): ?User
    {
        return User::where('id', $userId)->first();
    }
}

ビュー(View)

ビューとは、webサイト上のレイアウトを作成する役割を持っています。主にHTMLで記述されます。また、ビューから生成されたレイアウトはアプリケーションとユーザーのインターフェイスになっています。

ユーザーはこのweb上のレイアウトを通して、アプリケーションにリクエストを送信、アプリケーションからのレスポンスを閲覧することができます。

コントローラー(Controller)

コントローラーとは、ユーザーからのリクエストに応じて、モデルとビューの制御を担う部分です。 実際のユーザーインターフェイスはこのコントローラーになります。アプリケーションとユーザー間のやり取りは全てここを経由します。

ユーザーがwebサイト上で商品一覧をみたい時を例にします。 ユーザーから「商品一覧がみたい」というリクエストが来たら、モデルに商品データを取得するように依頼します。次に取得データをビューに渡し、ビューが作成したレイアウトを、webサイト上に表示しています。これによって、ユーザーがweb上で、商品一覧を閲覧することができます。

  • サンプルコード
Controllers/UserController.php

class UserController extends Controller
{
    /**
     * ユーザー一覧
     *
     * @return \Illuminate\View\View
     */
    public function index(): View
    {
        // Userクラス(モデル)のインスタンス作成
        $userModel = new User();
        // Userクラス(モデル)のメソッドを使って、ユーザーを取得
        $users = $userModel->fetchAllUsers();

        return view('users.index', ['users' => $users]);
    }

    /**
     * ユーザー詳細
     *
     * @param  int  $userId
     * 
     * @return \Illuminate\View\View
     */
    public function show(int $userId): View
    {
        // Userクラス(モデル)のインスタンス作成
        $userModel = new User();
        // Userクラス(モデル)のメソッドを使って、ユーザーを取得
        $user = $userModel->fetchUser($userId);

        // 対象ユーザーが存在しなかったら、404ページを返す
        if(is_null($user)) {
            abort(404); 
        } else {
            return view('users.show', ['user' => $user]);
        }
    }
}

MVCの問題点

  • コントローラーの肥大化
    コントローラーは基本的に、ユーザーインターフェイスとしてアプリケーションの入出力のみを担う責務ですが、コントローラーにビジネスロジックやデーターベースのやり取りを記述される場合があります。

  • モデルの肥大化
    モデルとは、ビジネスロジック・データベースアクセスを書く場所である。しかし、アプリケーションの規模が大きくなると、ビジネスロジック・データベースアクセスも必然的に増えていき、結果モデルは肥大化していきます。。。

ADRの導入

以上の問題点を踏まえ、MVCの改良版でもあるADR(Action Domain Responder)を導入しています!!

ADR(Action Domain Responder)とは??

上記のMVCモデルの改良版として、Paul M. Jonesによって提案されたソフトウェアアーキテクチャパターンです。

引用元:( https://en.wikipedia.org/wiki/Action%E2%80%93domain%E2%80%93responder

ADRでは、アクション(Action)、ドメイン(Domain)、レスポンダー(Responder)の3つの責務に分割して、実装していく手法です!

ADR:wikipedia

アクション(Action)

ユーザーからのHTTPリクエストを受け取り、ドメインの処理結果をレスポンダーに渡す役割です。MVCでいうコントローラー的な位置付けです。 全てのHTTPリクエストはここで処理されます。
HTTPリクエストとは

  • サンプルコード
Actions/User/UserIndexAction.php

class UserIndexAction extends Controller
{
    /**
     * Instance of the Responder property.
     *
     * @var UserRepository
     * @var UserIndexResponder
     */
    protected $userRepository;
    protected $userIndexResponder;

    /**
     * Instantiate the class.
     *
     * @param UserRepository $userRepository
     * @param UserIndexResponder $userIndexResponder
     */
    public function __construct(
        UserRepository $userRepository,
        UserIndexResponder $userIndexResponder
    )
    {
        $this->userRepository = $userRepository;
        $this->responder = $userIndexResponder;
    }

    /**
     * Invoke our action, handle domain, respond.
     *
     *
     * @return View
     */
    public function __invoke(): View
    {
        $users = $this->userRepository->fetchAllUsers();

        return $this->responder->response($users);
    }
}
Actions/User/UserShowAction.php

class UserShowAction extends Controller
{
    /**
     * Instance of the Responder property.
     *
     * @var UserRepository
     * @var UserShowResponder
     */
    protected $userRepository;
    protected $userShowResponder;

    /**
     * Instantiate the class.
     *
     * @param UserRepository $userRepository
     * @param UserShowResponder $userShowResponder
     */
    public function __construct(
        UserRepository $userRepository,
        UserShowResponder $userShowResponder
    )
    {
        $this->userRepository = $userRepository;
        $this->responder = $userShowResponder;
    }

    /**
     * Invoke our action, handle domain, respond.
     * 
     * @param Request $request
     * 
     * @return View
     */
    public function __invoke(Request $request): View
    {
        $user = $this->userRepository->fetchUser($request->user_id);
        
        return $this->responder->response($user);
    }
}

ドメイン(Domain)

アクションから受け取った依頼をもとに、ビジネスロジック、データベースアクセスを担当する役割です。MVCでいうモデル的な位置付けです。
ビジネスロジックとは

今回は、Domainをさらにサービス(Service)、リポジトリ(Repository)、モデル(Model)に分けています。

  • サービス(Service)
    主にアプリケーションビジネスルールを記述する。
    (アプリケーションビジネスルール = システムであるがゆえに発生するビジネスルール)
    例)データに対する複合処理、データ加工など

  • リポジトリ(Repository)
    データベースとのインターフェイスとして使用。
    例)データベースアクセスのみ(加工はサービスの責務)

  • モデル(Model)
    主にエンタープライズビジネスルールを記述する。
    エンタープライズビジネスルール = アプリケーション都合でないビジネスルール)
    例)野球  勝敗:点数が多い方が勝ち 時間:9回で終了 試合人数:各チーム9人ずつ

  • サンプルコード (今回はビジネスロジックがない為、リポジトリのみ)

Domain/User/UserRepository.php

class UserRepository
{
    /**
     * usersテーブルから全てのユーザーを取得
     * 
     * @return Collection
     */
    public function fetchAllUsers(): Collection
    {
        return User::all();
    }

    /**
     * usersテーブルから指定ユーザーを取得
     *
     * @param int  $userId
     *
     * @return ?User
     */
    public function fetchUser($userId): ?User
    {
        return User::where('id', $userId)->first();
    }
}

レスポンダー(Responder)

ドメインの処理結果を受け取り、HTTPレスポンスを作成、処理を担当します。MVC モデルのビューとは異なり、httpステータスの変更、クッキー操作も行います。

  • サンプルコード
Responders/User/UserIndexResponder.php

class UserIndexResponder
{
    protected $view;

    public function __construct(ViewFactory $view)
    {
        $this->view = $view;
    }

    /**
     * 単体動画の表示
     *
     * @param Collection $users
     * 
     * @return View
     */
    public function response(Collection $users): View
    {
        return $this->view->make(
            'users.index',
            compact('users')
        );
    }
}
Responders/User/UserShowResponder.php

class UserShowResponder
{
    protected $view;

    public function __construct(ViewFactory $view)
    {
        $this->view = $view;
    }

    /**
     * 単体動画の表示
     *
     * @param ?User $user
     * 
     * @return View
     */
    public function response(User $user): View
    {
        if (is_null($user)) {
            return \App::abort(404);
        } else
        {
            return $this->view->make(
                'users.index',
                compact('users')
            );
        }
    }
}

MVCからの改善点

  • コントローラーの肥大化
    MVCのコントローラーでは、ユーザーインターフェイスとして、全てのリクエスト、レスポンスを処理していました。
    しかし、ADRではリクエストをアクション、レスポンスをレスポンダーといったように、責務を分けることで改善されています。
    また、ADRでは、1アクションに対し1クラスしか作成しません。これにより、1つのコントローラーにメソッドが集中することを避けれます!!

  • モデルの肥大化
    MVCのモデルでは、全てのビジネスロジックの責務を担当していましたが、サービス(Service)、リポジトリ(Repository)、モデル(Model)という責務に細分化することで、モデルの肥大化を防ぎます。

  • 保守性、可読性の向上
    実際、コードリーディングを行う際に、処理の流れが明確なので追いやすいです。
    また、データーベースアクセスが一目でわかる、追いたい処理を探す時にもファイル特定がしやすい(サービス層の処理ならDomain配下のserviceファイルを探せばいい)などの利点がありました!

■まとめ

以上が、MVCADRの違いでした!!

最初は、「ADRってなんぞや??」と戸惑いましたが、

MVCをある程度理解していれば、ADRのキャッチアップもやりやすいと思います!

是非ともADR(Action Domain Responder)を導入してみてはいかがでしょうか?

Fatなコントローラー 、モデル内で、無限スクロールする開発生活から解放されるはずです。。。


株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、
一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!

興味のある方は以下の記事をぜひご覧ください!
みなさまとお会いできるのを心からお待ちしております!

www.wantedly.com www.wantedly.com www.wantedly.com www.wantedly.com