MVCを理解して阪神タイガースをリアーキテクトする
まずソフトウェアアーキテクチャにおける一般的な抽象概念としてのMVC(Model-View-Controller)パターンの概要を説明し、その後にRailsにおけるMVCアーキテクチャの特徴を解説し、両者を対比しながらMVCの理解の促進を図ります。
最後に、阪神タイガースに対してMVCアーキテクチャを適応させてみたいと思います。
抽象概念としてのMVC(Model-View-Controller)パターンとは
MVCの3つの役割(責務)
-
Model(モデル)
- アプリケーションの「データ」や「ビジネスロジック」を担う部分。
- データの取得・保存・更新といった永続化の責務だけでなく、ドメインルール(例:商品の価格計算、在庫管理など)やバリデーション(例:入力チェック)のロジックもここに実装する。
- 「何を扱うか(ドメイン)」という視点を担う箇所。
-
View(ビュー)
- ユーザーに表示される「見た目(UI)部分」。
- HTMLやテンプレートエンジン、デスクトップアプリケーションならGUI部品、あるいはREST APIを返す際のJSONフォーマッティングなどが該当。
- データの見栄え(レイアウトやフォーマット)や、ユーザーからの入力フォームなど、あくまでも「表現」の責務。
-
Controller(コントローラ)
- ユーザーからの入力(例:WebではHTTPリクエスト)を受け取り、適切なModelを呼び出して処理を行い、その結果をViewに渡す役割。
- いわゆる「流れ」を制御する部分。
- 例:ユーザーが「会員登録」ボタンを押したとき → Controllerがリクエストを受け取り、Model(User)にユーザー情報を登録依頼 → ModelがDBに保存 → Controllerが保存結果を受け取ってViewをレンダリングして返す、など。
- 画面遷移やルーティングの振り分け、パラメータの受け取り・前処理、例外時のハンドリングなども担う。
MVCの何が嬉しいのか
最大のメリットは、アプリケーションの責務を明確化できることです。責務を明確化することにより、コンポーネントごとの独立性が保証され、分業のしやすさや再利用性、テスト対象が明確になることによる保守性の向上などが期待されます。
RailsにおけるMVC Architecture
MVC自体はあくまで「役割分担の考え方」であり、具体的にどのように実装するか(クラス構成、ディレクトリ構造、ORM利用の可否、テンプレートエンジンの選定など)はフレームワークやプロジェクトによって変わってきます。
例えばRuby/RailsのMVC、Java/SpringのMVC、PHP/LaravelのMVC、Python/DjangoのMTV(厳密にはMVCに近い)など、同じMVC思想でも実装は千差万別です。ここではRailsの文脈におけるMVC Architectureの具体について触れてみます。
Rails(Ruby on Rails)は、設計思想として「Convention over Configuration(設定より規約)」を重視しており、MVC構造も「こうすればこう動く」という決まったレールの上を走ります。以下ではRails独自の仕組みや「Rails流」のMVCについて説明します。
Model(Railsの場合)
-
ActiveRecord
- RailsのModel層は基本的にActiveRecordクラスを継承して実装する。
- データベースとのマッピング(ORM:Object-Relational Mapping)を担い、テーブル名とクラス名のマッピングや、カラムと属性のマッピングは自動的に行われる。
- 例えば
class User < ApplicationRecordと書くだけでusersテーブルと紐づき、User.find(1)で1番のレコードを取得できる。
-
バリデーションや関連付けの宣言的記述
- Modelクラスの中で
validates :email, presence: true, uniqueness: trueのように、バリデーションを宣言的に書くのが標準。 - 他のテーブルとのアソシエーションも
has_many :posts/belongs_to :userのように宣言する。 - こうした宣言により、DBスキーマとの連携や関連モデル同士の結合が簡単になる。
- Modelクラスの中で
-
ビジネスロジックの置き場所
- Railsでは「Fat Model, Skinny Controller(モデルを肥大化、コントローラは薄く)」が推奨される。
- とはいえ複雑になりすぎた場合は、ServiceオブジェクトやDomain Serviceオブジェクト、Concernsを使ってロジックを分割することが一般的。ただし、最初からRepository層を明示的に置くというよりは、ActiveRecordのScopesやクエリメソッド、モジュール分割で対応するケースが多い。
View(Railsの場合)
-
ERB(Embedded Ruby)テンプレート
- デフォルトでは
.html.erbファイルにRubyコードを埋め込んでHTMLを出力する。 -
app/views/コントローラ名/アクション名.html.erbのようにディレクトリ構造が自動的にルーティングに対応しており、Controller からrenderすると対応するViewがレンダリングされる。
- デフォルトでは
-
他のテンプレートエンジンも利用可能
- Haml, Slim, Builder(XML生成用)、さらにはReactやVueを組み込んで部分的にJSX/TSXを書くなど、拡張性はあるが「Rails標準のView=ERB」という認識が強い。
- また、JSONを返すAPIモードを使えば、
.jbuilderテンプレートやrender json:でView部分をRubyコードで組み立てるスタイルにも対応。
-
レイアウト(Layouts)とパーシャル(Partials)
-
app/views/layouts/application.html.erbのように全体に適用されるレイアウトを定義し、その中でyieldを使って各アクションのテンプレートを差し込む。 - 部分テンプレート(_header.html.erb など)を
render 'header'のように呼び出せる仕組みが標準装備されており、Viewの再利用性が高い。
-
Controller(Railsの場合)
-
ActionController::Base を継承
-
app/controllers/users_controller.rbなど、各リソース(例:User)に対応するControllerを作成し、そこでアクションメソッド(例:index, show, new, create, edit, update, destroy)を定義する。 - ルーティング(config/routes.rb)で
resources :usersのように書くと、RESTfulなURLとControllerアクションのマッピングが自動的に生成される。
-
-
フィルタ(before_action, after_action など)
- 認証チェックや共通前処理を
before_action :authenticate_user!のように宣言的に設定できる。 - これにより、各アクション内では「ビジネスロジックやモデル呼び出し」に集中しやすくなる。
- 認証チェックや共通前処理を
-
ストロングパラメータ
- パラメータの許可・制限は
params.require(:user).permit(:name, :email)のようにコントローラ側で定義する。 - これにより、許可されていない属性のマスアサインを防ぐ仕組みが組み込まれている。
- パラメータの許可・制限は
-
レスポンスの返し方
- アクション内で
render :showやredirect_to users_pathのように書くと、対応するビューが自動的にレンダリングされたり、別URLにリダイレクトされたりする。 - JSON形式で返したい場合は
render json: @userのように書くだけで、自動的にActiveModel::SerializersやJbuilderによるシリアライズが行われる。
- アクション内で
概念図まとめ
阪神タイガースのリアーキテクト
これまでまとめてきたMVCの概念を阪神タイガースに応用します。
Viewの位置付け
まずはViewの位置付けを定義します。ユーザー(阪神タイガースファン)が目にするのは、「球場のシーン」や「選手のプレー」「応援風景」です。これがMVCの「View」にあたります。
ControllerとModel: ユースケース1
前提、阪神ファンからViewに対して野次が飛んできます。ここでは具体的な野次の内容にはあえて触れませんが、単なる応援なのか、選手を奮い立たせるような叱咤激励味のある言葉なのか、選手のモチベーションを下げてしまう悪質なヤジなのかは様々です。それによって選手のパフォーマンスが上下し、勝敗に影響を与えるようでは阪神タイガースというアプリケーションはある種、脆弱とも捉えられます。そのため、野次リクエストを捌くためのControllerを用意しましょう。
このControllerを何とするかは議論の余地がありそうですが、一旦監督をControllerとしてみます。
Viewに対する全ての野次リクエストは、一旦監督Controllerへ遷移します。ここで、悪質なリクエストに関してはバリデーションし、選手へパケットが流れないようにします。
建設的な、叱咤激励味のある野次であれば、監督ControllerがPlayerServiceを呼び出し、必要に応じでリクエストデータを整形し、選手へ連携することが可能となります。
さらに、監督Controllerは「選手が全然機能していない」と判断すれば、スカウトサービス(ScoutService)を呼び出し、「今季限りで見切るべきか」「代替選手を補強するか」を判断するためのデータを集めるよう依頼します。選手DBの更新も随時ScoutService上で行われるわけです。
ControllerとModel: ユースケース2
また、阪神タイガースファンは巨人戦となるとより白熱し、阪神ファンと巨人ファンが一触即発のピリつき感を醸成します。可能性として乱闘トラブルリクエストに対応できる準備をしておくことが重要です。
ここでは野次リクエストとは毛色が違うため、別のControllerとして警備Controllerを用意しましょう。
この警備Controllerが、喧嘩の規模や怪我人の有無をチェックし、どの程度の対応が必要かをバリデーションおよび緊急度判定により判断します。
例えば、「軽い口論レベル」であれば現地の警備員が静止するのみの対応で問題ないでしょう。つまり、リクエストが単純であればControllerによる処理のみでModelとの連携は不必要となります。「本格的に暴力沙汰になっており、怪我人も出ている」となれば、救急サービス(AmbulanceService)を呼び出し、怪我人の対応を行います。
また、これによって「球場に行くのが怖い」というイメージが拡散してしまうと、観客動員数の低下や売り上げの減少につながる可能性もあるため、必要に応じて広報サービス(PublicRelationsService)を呼び出し、球団としてのイメージアップ対応を図ります。
ユースケース1では言及しませんでしたが、救急サービス(AmbulanceService)は、デッドボールを当たられた選手が発生した場合に、監督Controllerからも呼び出すことができるかもしれません。これもまたMVCアーキテクチャによる再利用性のメリットを享受することができます。
まとめ
抽象概念としてのMVCと、Rails ArchitectureにおけるMVCについてそれぞれ理解するため、図を用いながらまとめてみました。
ソフトウェアアーキテクチャとしてのMVCはあくまで役割分担の大きな概念であり、具体的な実装はフレームワークやプロジェクトごとに異なります。RailsのMVCの場合だと、最初から明確な規約とツール群が用意されており便利だと改めて感じました。
Discussion