Zenn
💨

MVCモデルとは。service、routerとの違い フォルダ構成

2025/03/21に公開
1

はじめに​

こんにちは!
情報系の学部に通うインターン生です!

なんとか就活を終えました!
去年の8月前後から今年3月までの計7か月ほど就活をしました!
就活をする中で、たくさんのことを学べたので、それもまた時間があればまとめようと思います!

今回は、インターンでのソフトウェア開発中に先輩から教えていただいた「MVCモデル」についてまとめました!
そして、MVCモデルについて調べる中で、バックエンドをAPIサーバとして設計し、表示部分をすべてフロントエンドで行う場合、MVCの「V(View)」がどのように解釈されるか疑問に思ったので、わかったことをまとめます!

MVCモデルとは

MVCモデルとは、主にバックエンド側でアプリケーションの設計を以下の3つの要素に分割する手法

  • Model(モデル):​データやビジネスロジックを管理する。データベースとのやり取りや、データの加工などが含まれる。​

  • View(ビュー):​ユーザーに表示される部分を担当する。ユーザーインターフェースや画面のレイアウトなどが該当する。​

  • Controller(コントローラー):​ユーザーからの入力を受け取り、適切なModelやViewに処理を振り分ける役割を持つ。​

このように役割を明確に分けることで、コードの見通しが良くなり、保守性や再利用性が向上する。​

Claude3.7Sonnetで作成

バックエンドをAPIサーバとして設計する場合のMVCの「V」の解釈

近年、フロントエンドとバックエンドを明確に分離し、バックエンドはデータの提供(APIサーバ)に特化し、フロントエンドがユーザーインターフェース全般を担当するアーキテクチャが一般的になっている。(今のプロジェクトもそのようになっている)

​この場合、バックエンドのMVCにおける「View(ビュー)」はどのように解釈されるのだろうか。​

MCだけで構成する

バックエンドがAPIサーバとして機能する場合、主な役割は以下のようになる。

  • Model(モデル):​データベースとのやり取りやビジネスロジックの実装を担当する。​

  • Controller(コントローラー):​クライアントからのリクエストを受け取り、適切なModelを呼び出し、その結果をレスポンスとして返します。​

けれど、JSONやXMLといったデータフォーマットでレスポンスを返すものをViewと解釈する考え方もあるらしい。(GPTがいってた)​

その考えに乗っ取ると、ControllerがJSON形式のレスポンスをフロントに返すので、ControllerがViewも兼ねているということになる。

service・routerとの違い

service はControllerとModelの中間

Service層はビジネスロジックやアプリケーションの振る舞いを抽象化し、モデルやコントローラから分離するためのコンポーネント。

具体的には、データの取得・加工、入力値のバリデーション、他の外部サービスとの連携など、純粋な業務処理をServiceクラスにまとめる。これにより各層の責務が明確化し、Controllerは複数のServiceメソッドを呼び出して処理フローを組み立てることに専念できるため、Controller自体は薄く保たれる。

結果として、コードの見通しが良くなり保守性・再利用性・テスト容易性が向上する。

router はフロント(View)とControllerの中間

Router層は、HTTPリクエストのURLパスやメソッドに応じて適切なControllerへ処理を振り分ける役割を担う。ルーティング定義により、サーバーは受け取ったリクエストを対応するルートにマッチさせ、紐付いたコントローラを呼び出して処理を実行する。

例えばExpressやRailsでは、URLパス(エンドポイント)とHTTPメソッド(GET, POST等)の組み合わせをルーティングで定義し、それに対応するコントローラの関数やメソッドが実行される。
ルーティングは通常、コントローラの実装とは別ファイル・別モジュールにまとめられ、定義と実装を分離することでコードの見通しを良くしている。

Router層はあくまで「どの処理を呼び出すか」を決定する経路指定役であり、実際のビジネスロジックはControllerやServiceに委ねられる。これにより、ルーティング定義を見ればシステムのエンドポイント一覧と担当コントローラが一目で分かり、Controller側の実装を見ることで具体的な処理内容が把握できるという責務の分離が実現する。

Claude3.7Sonnetで作成

Flaskでの例

以下のフォルダ構成のユーザーが アイテム(名前・説明付き)を登録したり、一覧を取得したり、ID指定で取得できるような簡単なAPIサーバを考える。

フォルダ構成
my_flask_app/
├── run.py
├── routes/
│   ├── __init__.py
│   └── item_routes.py
├── controllers/
│   ├── __init__.py
│   └── item_controller.py
├── services/
│   ├── __init__.py
│   └── item_service.py
├── models/
    ├── __init__.py
    └── item_model.py

各フォルダの内容を以下に示す。

以下のファイルは「アプリケーションのエントリーポイント(起動スクリプト)」
Flaskアプリを作成
item_blueprint(ルーター)を登録
app.run()でローカルサーバーを起動

run.py
from flask import Flask
from routes.item_routes import item_blueprint

app = Flask(__name__)
app.register_blueprint(item_blueprint)

if __name__ == '__main__':
    app.run(debug=True)

以下のファイルは「ルーティング層(ルーター)」を担当する。
アプリケーションに届いたHTTPリクエストを、どのコントローラーの処理に繋げるかを定義する場所。
Flaskの Blueprint を使って
/api/items → get_items()
/api/items/<id> → get_item()
POST /api/items → create_item()
といったリクエストの流れをマッピングしている。

routes/item_routes.py
from flask import Blueprint
from controllers.item_controller import ItemController

item_blueprint = Blueprint('item_blueprint', __name__)
item_controller = ItemController()

item_blueprint.route('/api/items', methods=['GET'])(item_controller.get_items)
item_blueprint.route('/api/items/<int:item_id>', methods=['GET'])(item_controller.get_item)
item_blueprint.route('/api/items', methods=['POST'])(item_controller.create_item)

以下のファイルは「コントローラー層」を担当する。
ユーザーからのリクエスト(例:GETやPOST)を受けて、どんな処理をするべきか判断し、サービス層に処理を委ねる役割を持つ。
ここでは ItemController クラスを定義しており、
・get_items():全アイテムの取得
・get_item(item_id):指定IDのアイテム取得
・create_item():新しいアイテムの登録
などのAPI処理を提供する。

controllers/item_controller.py
from flask import jsonify, request
from services.item_service import ItemService

class ItemController:
    def __init__(self):
        self.item_service = ItemService()

    def get_items(self):
        items = self.item_service.get_all_items()
        items_dict = [{'id': item.id, 'name': item.name, 'description': item.description} for item in items]
        return jsonify(items_dict)

    def get_item(self, item_id):
        item = self.item_service.get_item_by_id(item_id)
        if item:
            item_dict = {'id': item.id, 'name': item.name, 'description': item.description}
            return jsonify(item_dict)
        else:
            return jsonify({'error': 'Item not found'}), 404

    def create_item(self):
        data = request.get_json()
        if 'name' in data and 'description' in data:
            new_item = self.item_service.add_item(data['name'], data['description'])
            item_dict = {'id': new_item.id, 'name': new_item.name, 'description': new_item.description}
            return jsonify(item_dict), 201
        else:
            return jsonify({'error': 'Invalid data'}), 400

以下のファイルは「サービス層」を担当する。
ビジネスロジック(実際の処理の中身)を記述する場所。
ItemService クラスでは、
・メモリ内でアイテムを管理(配列)
・IDの自動採番
・アイテムの追加や検索
といった具体的な処理を行う。
こうしてコントローラーから処理の中身を分離することで、保守・テストしやすくなる。

services/item_service.py
from models.item_model import Item

class ItemService:
    def __init__(self):
        self.items = []
        self.current_id = 1

    def get_all_items(self):
        return self.items

    def get_item_by_id(self, item_id):
        return next((item for item in self.items if item.id == item_id), None)

    def add_item(self, name, description):
        new_item = Item(self.current_id, name, description)
        self.items.append(new_item)
        self.current_id += 1
        return new_item
}
        """
        もしmodels/item_model.pyを作らない場合、以下のようにnew_itemを作成することになる
        new_item = {
            "id": current_id,
            "name": name,
            "description": description
        """

以下のファイルは「モデル層」を担当する。
アプリケーション内で扱うデータ(ドメインオブジェクト)を定義する。
ここでは Item クラスを定義していて、
・id
・name
・description
といったプロパティを持つ、アイテムの構造(型)を定義。

models/item_model.py
class Item:
    def __init__(self, item_id, name, description):
        self.id = item_id
        self.name = name
        self.description = description

実際のフォルダ構成

実際のプロジェクトでのフォルダ構成は、それぞれのチーム、プロジェクトによるみたい。

小規模なプロジェクトでは、必ずしもController・Router・Serviceなどに厳密に分割しなくても良い場合が多い。プロジェクト規模や複雑度、開発チームの人数などを考慮して、コードの規模に見合わないほど細かく分割してしまうと、かえって構成が複雑になり管理コストが増すこともある。

ドメイン別ファルダ構成

Flaskの例のように各層ごとにフォルダを構成するものもあれば、以下のようにドメインごとにフォルダを構成するパターンもある。

  • 中〜大規模アプリ開発では非常に実用的で、よく使われている構成
  • 各「機能(admin, user)」ごとにファイル群がまとまってて見やすい
  • 疎結合なので、機能単位での開発・保守がしやすい
  • 将来的にそれぞれを「Blueprintで独立したモジュール化」しやすい(Flask拡張性◎)
フォルダ構成(ドメイン別)
project/
├── app.py
├── admin/
│   ├── routes.py
│   ├── controller.py
│   └── service.py
├── user/
│   ├── routes.py
│   ├── controller.py
│   └── service.py

役割別フォルダ構成

  • 初学者や小規模開発ではシンプルで分かりやすい
  • 「役割の違い」を軸に管理できるので、MVCっぽさが強調される
フォルダ構成(役割別)
project/
├── app.py
├── routes/
│   ├── admin_routes.py
│   └── user_routes.py
├── controllers/
│   ├── admin_controller.py
│   └── user_controller.py
├── services/
│   └── user_service.py

最後に

MVCモデルというアプリケーションの設計を3つの要素に分割する手法を用いることで、開発効率や保守性の高いコードにできる。

バックエンドをAPIサーバとして設計し、表示部分をすべてフロントエンドで行う場合、従来のMVCモデルにおける「View(ビュー)」の役割は基本的にはフロントエンドに移行する。
けれど、ControllerがViewを兼ねているという考え方もある。

実際のプロジェクトでのフォルダ構成は、それぞれのチーム、プロジェクトによる。

参考元

1

Discussion

ログインするとコメントできます