MVCの本当のメリットは何か?
株式会社TECH LUCKという会社で代表兼エンジニアをしている齊藤です。
最近プログラミングを始めた人や、ある程度慣れてきて設計を考えるようになった人にとって、大きな1つの山にMVCモデルを理解することがあると思っています。
参考書や技術記事に、よくこんなことが書かれています。
「MVCモデルを意識してコードを書きましょう!」
「Modelにビジネスロジックを書きましょう!」
「Viewにはロジックを書かないようにしましょう!」
「Thin Controller, Fat Modelを目指しましょう!」
「クリスマスにもプログラミングをしましょう!」
そしてあなたはこう思います。
「じゃあ、とりあえずMVCを意識して書いてみますかね、、、。」
しかし、この記事にたどり着いたあなたはこうも考えているはずです。
「別にMVCを意識しなくても、動くものは作れるじゃないか。どうしてわざわざそんな面倒なことを考えて書かないといけないんだ!」
そうなのです。別にMVCを意識しなくても、きちんと動くアプリケーションは作れるのです。
調べてみても、「Controllerにビジネスロジックを書くのはよくない」
ということしか書いておらず、結局メリットがわかりません。
この記事では、私がプログラミングをやってきた中で
MVCモデルのメリットは何なのか?
についての1つの考えを、実際にコード書いて参考例と共に紹介したいと思います。
(参考例はRuby on Railsなので、他のフレームワークを学んでいる人はごめんなさい、、、。)
MVCモデルとは
そもそも、MVCモデルとは何でしょうか?
その答えを的確に示した記事がありました。
ざっくりまとめると、 ソースコードをModel, View,
Controllerと呼ばれる3つの役割に分割して、アプリケーションを設計しよう。 というものです。
- View・・・ユーザーに見える画面のところ
- Model・・・ビジネスロジックを記述するところ
- Controller・・・上記の、ModelとViewをつなぎ合わせるところ
となります。
画像で示すと以下のような形です。
MVCの成り立ち
では、なぜこのMVCモデルというのが確立されたのでしょうか。
これにもいい記事がありました。
こちらもざっくり要約すると、
- 最初はView, Model, Controllerもすべて1つのところで済まそうとしていた
- しかし、表示の見た目に関してはデザイナーが、データの処理の部分はエンジニアが担当した方がやりやすいのでViewとModelを切り離した
- Viewからの細かい指令により処理を分岐させるために、ViewとModelを繋ぐ橋渡し役としてControllerを切り離した
というものだと思います。
MVCのメリット
上記でも挙げられていますが、3つに分割することによって以下のようなメリットが挙げられます。
- デザイナーの人とエンジニアの人がお互いに作業がしやすくなる
- ModelとViewだけだと、Viewからの細かい命令によって処理が複雑化してしまうために、細かい命令を受け取るControllerがクッションになってくれる
これが主に挙げられるメリットですが、僕はこれ以外にもメリットがあると思っています。
これが本記事の主題です。
MVCのさらなるメリット
「ControllerはViewとModelを繋ぐ役割だから、ロジックはModelの方に書こう」
となるのはわかります。しかし、単にそれだけの理由ではなく、ロジックをModelに書くと以下のようなメリットがあると思っています。
- Modelにロジックを集中させることにより、コードの再利用がしやすくなる
- Modelにバリデーションやコールバックを集中させることにより、予期せぬエラーやデータの書き換えを防ぐことができる
実際にコードを書いて確認してみましょう。
コードを書いた例
Twitterのようなアプリケーションを考えます。
ユーザーを表すusers_table
、ツイートを表すtweets_table
、ツイートの添付画像のimages_table
があるとします。
ユーザーは複数のツイートをするので、user : tweet = 1 : N
の関係になっています。
また、ツイートは複数の画像を添付できるので tweet : image = 1 : N
の関係になっています。
ここで注意していただきたいのが、説明をわかりやすくするため、tweets_table
にpublished
というカラムを作成して、公開か非公開かを判定するフラグを作成していることです。
users_table
は以下のような構造になっています。
カラム名 | 説明 | データ型 |
---|---|---|
name | ユーザー名 | string |
tweets_table
は以下のような構造になっています。
カラム名 | 説明 | データ型 |
---|---|---|
content | ツイート内容 | text |
published | 公開か下書きか(true:公開 false:下書き) | boolean |
user_id | 紐づくユーザーのID | integer |
images_table
は以下のような構造になっています。
カラム名 | 説明 | データ型 |
---|---|---|
url | 画像URL | string |
tweet_id | 紐づくツイートのID | integer |
モデルのソースコードは以下のようになっています。
class User < ApplicationRecord
has_many :tweets
end
class Tweet < ApplicationRecord
belongs_to :user
has_many :images
end
class Image < ApplicationRecord
belongs_to :tweet
end
今の状態はアソシエーションを組んでいるだけですね。
では、実際にここからコードを書いていきます。
コードの再利用
ここで、ユーザーがツイートを(公開で)投稿する機能を作成するとします。
TweetsController
を作成し、createアクション
を定義しましょう。
class TweetsController < ApplicationController
def create
# 投稿したユーザーを取得
user = User.find(params[:user_id])
# Tweetレコードの作成(公開するためにpublishedはtrue)
tweet = Tweet.create(user_id: user.id, content: params[:content], published: true)
# もし、image_urlがparamsの中に存在していたら、Imageレコードの作成
if params[:image_url].present?
Image.create(url: params[:image_url], tweet_id: tweet.id)
end
end
end
単純にすべてをコントローラーに記述するとこのような形になります。
これでツイートの投稿機能が実現できました。
では、次にツイートを下書きで保存する機能を作成しましょう。
DraftsController
にcreateアクション
を作成しましょう。
実際はTweetsController
で処理してもいいのですが、ここでは便宜上分けることにします。
class DraftsController < ApplicationController
def create
user = User.find(params[:user_id])
Tweet.create(user_id: user.id, content: params[:content], published: false)
if params[:image_url].present?
Image.create(url: params[:image_url])
end
end
end
このようになります。
これでツイートの下書き保存ができるようになりました。
でも、待ってください。TweetsController
のcreateアクション
のコードと似てませんか?
はい、そうです。published
がtrue
かfalse
かの違いしかありませんね。
となると、これをひとまとめにしたら再利用できるんじゃない?
ということで、Modelにロジックを書いてみましょう。
class Tweet < ApplicationRecord
belongs_to :user
has_many :images
def self.create_with_image(user, params, published)
Tweet.create(user_id: user.id, content: params[:content], published: published)
if image_url.present?
Image.create(url: params[:image_url])
end
end
end
結構強引でコードが汚いですが、このように記述することができます。
すると、TweetsController
とDraftsController
は以下のようにスッキリします。
class DraftsController < ApplicationController
def create
user = User.find(params[:user_id])
Tweet.create_with_image(user, params, false)
end
end
class TweetsController < ApplicationController
def create
user = User.find(params[:user_id])
Tweet.create_with_image(user, params, true)
end
end
こうすることによって、ツイートと画像を保存する という処理を複数のコントローラで使いまわすことができるようになりました。
これがModelにロジックを記述することでコードの再利用がしやすくなるということの理由です。
予期せぬエラーやデータの書き換えを防ぐことができる
次に、ユーザーが新規登録した時のことを考えましょう。
ここでユーザー名に対してユニーク制約をかけたいとします。
このときに、Userモデル
に対してバリデーションをかけないでやろうとします。
そうすると、UsersController
は以下のようになります。
class UsersController < ApplicationController
def create
users = User.where(name: params[:name])
# params[:name]が他のユーザーの名前で使われているか確認して、なければ作成
if users.blank?
User.create(name: params[:name])
end
end
end
このようになりますね。
これでユーザー名がユニークになるように実装できました。
では、他のところもユーザーを作成したいとしたらどうでしょうか?
users = User.where(name: params[:name])
# params[:name]が他のユーザーの名前で使われているか確認して、なければ作成
if users.blank?
User.create(name: params[:name])
end
このコードを毎回毎回書かないといけないのでしょうか?
万が一これが抜けてしまったらユニークにならないということが起こり得てしまいます。
ここで先ほどの図が出てきます。
これを見てみると、データベースとのやり取りはモデルを介してしかすることができません。
つまり、モデルの中でバリデーションをかけると、ユニークかどうか確認することを忘れても、必ずバリデーションがかかり、データが保存できないようになります。
class User < ApplicationRecord
has_many :tweets
validates :name, uniqueness: true
end
これで、確実にユーザー名をユニークに保つことが保証されました。
これが予期せぬエラーやデータの書き換えを防ぐことができるということです。
まとめ
以上が僕の考えるMVCのメリットだと思っています。
(Ruby on Railsが定義に従っているだけというところも否定できませんが、、、。)
もっとメリットがあると思いますが、2つに絞って書いてみました。
アプリケーション設計は答えがなく、合っている間違っているということはないと思います。
その上で、自分なりのメリットデメリットを考えて、アプリケーション設計ができるエンジニアが一流のエンジニアではないかなと思います。
Author And Source
この問題について(MVCの本当のメリットは何か?), 我々は、より多くの情報をここで見つけました https://qiita.com/ryouzi/items/5ff3bef12ff07922ccc7著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .