🦁

3層アーキテクチャをベースにMVCモデルでロジックをどこに書けばいいのか理解する

2024/06/17に公開

はじめに

LaravelやRailsなどのMVCフレームワークを使っていると、ロジックをモデルに書けばいいのか、コントローラーに書けばいいのか迷うことがありませんか?

3層アーキテクチャとMVCの関係を理解すれば、その迷いを払拭することができます。
まずは3層アーキテクチャとMVC、それぞれについて説明した後、その関係を説明していきます。

3層アーキテクチャとは

3層アーキテクチャ(Three-Tier Architecture)は、ソフトウェアを3つの主要な部分に分ける設計パターンです。それぞれの層は特定の責任を持ち、システム全体のモジュール化と柔軟性を高めます。

注意してほしいこととしては、今回扱う3層アーキテクチャは、以下のWebアプリケーションの3層アーキテクチャではありません。

引用:https://logmi.jp/tech/articles/328326

今回扱うのは、アプリケーション層の3層アーキテクチャです。
上記のWebアプリケーションの3層アーキテクチャにあるアプリケーション層もまた、3層に分けられるのです。

引用:https://logmi.jp/tech/articles/328327

この記事では、アプリケーション層の3層アーキテクチャとMVCの関係を通じて、普段扱うLaravelなどMVCフレームワークでコードをどこにおけばいいのかを解説していきます。

まずは、それぞれの層の役割についてざっくり説明します。

プレゼンテーション層

プレゼンテーション層は、ユーザーインターフェースを担当する層です。
ユーザーからの入力を受け取ってビジネスロジック層に渡し、その処理結果をユーザーにフィードバックします。

ビジネスロジック層

ビジネスロジック層は、ビジネスロジックを処理する層です。
プレゼンテーション層からの要求を受け取り、必要に応じてデータアクセス層と連携して必要なデータを取得・処理します。処理結果はプレゼンテーション層へ返します。

ビジネスロジックとは、アプリケーションが実現するべき固有の処理のことです。
といっても、なんだかよくわからないですよね?

理解しやすいように言い換えると、ビジネスロジックとは、プレゼンテーションでもデータアクセスでもないところです。

以下の記事に詳しく書いてあるので、ぜひ読んでみてください。ここの理解大事です!
https://logmi.jp/tech/articles/328327#s3
https://qiita.com/os1ma/items/25725edfe3c2af93d735

データアクセス層

データアクセス層は、ビジネスロジック層とデータベースとの通信を仲介する層です。
ビジネスロジックからデータベースにアクセスするための処理を切り離す役割を持ちます。
この層のおかげで、ビジネスロジック層はデータの保存先がRDBだろうとファイルだろうと関係なく処理できます。

MVCモデルとは

MVCモデル(Model-View-Controller)とは、ソフトウェア開発において広く使用されるデザインパターンの一つです。

機能を「Model」(モデル)、「View」(ビュー)、「Controller」(コントローラ)の三つの役割に分離して実装し、それらが連携して処理を進めます。

【それぞれの役割】

  • Model:データとビジネスロジックを管理する。
  • View:ユーザーインターフェースを担当。ユーザーにデータを見せる役割を果たし、ユーザーの操作を受け付けます。
  • Controller:ユーザーの入力を受け取り、モデルとビューの橋渡しをする。


出典:https://bap-software.net/vi/knowledge/mvc-model/

3層アーキテクチャとMVCの関係

3層アーキテクチャとMVCの関係を表した図が以下になります。

引用:https://logmi.jp/tech/articles/328326

この図を見て、ロジックをどこに書けばいいのか分かってきたのでは無いでしょうか?
ここからが本題です。コントローラーに書くべきコードとモデルに書くべきコードを解説していきます。

コントローラーに書くべきコード

コントローラーはプレゼンテーション層の一部として機能します。
コントローラーには主に入力処理とビューの選択に関するロジックを記述し、ビジネスロジック自体はモデルに委譲することが推奨されます。以下に、Userコントローラの具体例を示します。

class UserController extends Controller
{
    /**
     * 指定された条件に基づいてユーザーを検索
     *
     * @param UserSearchRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function search(UserSearchRequest $request)
    {
        try {
            $users = User::searchUsers(
                $request->input('name'),
                $request->input('email'),
                $request->input('per_page')
            );
            
            return response()->json($users);
        } catch (Throwable $e) {
            return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
        }
    }
}

モデルに書くべきコード

モデルはビジネスロジック層とデータアクセス層を担い、データベース操作やビジネスロジックの実装を行います。以下に、Userモデルの具体例を示します。

class User extends Authenticatable
{
    /**
     * 指定された条件に基づいてユーザーを検索
     *
     * @param string|null $name
     * @param string|null $email
     * @param int|null $perPage
     * @return \Illuminate\Pagination\LengthAwarePaginator
     */
    public static function searchUsers(?string $name, ?string $email, ?int $perPage = 10): LengthAwarePaginator
    {
        $query = self::query();
        
        // 名前でフィルタリング
        if (!empty($name)) {
            $query->where('name', 'like', '%' . $name . '%');
        }
        
        // メールアドレスでフィルタリング
        if (!empty($email)) {
            $query->where('email', 'like', '%' . $email . '%');
        }

        return $query->paginate($perPage);
    }
}

おわりに

僕も含めて、初心者の頃はコントローラにビジネスロジックを全部書いちゃいますよね。
巷でよく聞くファットコントローラーとは、コントローラが入力の受付とビジネスロジックという2つの責任を持つことでコードが膨れ上がることを言います。

本来ビジネスロジックはモデルに記述されるべきです。
ですが、ビジネスロジックをモデルに書いたら書いたで、今度はファットモデルという問題が発生します。モデルがビジネスロジックとデータアクセスの2つの責任を持つからですね。

この問題を解決するために、サービスクラスというクラスを作成し、モデルからビジネスロジックを切り離す方法があります。サービスクラスを使うことで、モデルはデータアクセスに専念でき、コードの責務が明確になります。

参考

Discussion