Rails開発からMVCアーキテクチャについて考えてみました。
各レイヤにおける構成と役割を整理することで、より良い開発を目指していきたいです。
M(モデル)・V(ビュー)・C(コントローラ)
Railsは一般的なフレームワークと同様、MVCアーキテクチャ(構造)となっています。
- Model(モデル)
- View(ビュー)
- Controller(コントローラー)
MVCそれぞれ役割分担されていることで、効率的に開発することができます。
各レイヤは以下のように対応されます。
- ユーザインタフェース層 View
- アプリケーション層 Controller
- ドメイン層 Model
Railsによるアプリケーション開発ではMVCアーキテクチャを意識しないと、規模が大きく開発期間が長くなるにつれ複雑怪奇なものになっていきます。
特に複雑な機能や機能数を増やしていくとControllerでロジックや処理が集中しコード量が増え、仕様変更や保守・運用が大変になります。
そのためにも今一度MVCアーキテクチャを整理し、理解したうえで開発に取り組むことが大事だと考えます。
Rails開発から各レイヤのアーキテクチャを整理する
■ユーザインタフェース層
views
・app/views
viewテンプレートファイルが置かれ、HTMLをレンダリングします。
view_objects
・app/view_objects
Viewで使用するデータオブジェクトです。
Modelを使いやすいよう整形し、Viewで表示する橋渡し的な役割です。
■アプリケーション層
controllers
・app/controllers
ユーザからのリクエストを受け、ドメイン層に対して命令をします。
また、ビジネスロジックはできるだけ記述せず、セッション管理やHTTPリクエスト・レスポンス処理などを担当します。
クラス名の命名規則として、末尾にControllerを追記します。
ex)
1 2 3 4 5 6 | class HogesController def index @hoges = Hoge.all end end |
decorators
・app/decorators
モデルからビューへ表示するためのロジックやデータ整形はdecoratorsに書きます
helpersでも良いですが、グローバルメソッドであるため、すべてファイルから呼び出せてしまうのが難点。
一応対策として、以下でHelperがすべてのViewから読み込むのを防ぐことができます。
1 | config.action_controller.include_all_helpers = false |
■ドメイン層
models
・app/models modelではリレーションやenumなどデータ管理を行います。 Controllerからの命令に従ってデータを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Office < ApplicationRecord # 定数 SYSTEM_ADMIN = -1 # リレーション belongs_to :company has_many :employers # enum enum system_admin_flg: { is_not_system: 0, is_system: 1 } end |
services
・app/services
ドメイン層における複雑なロジックをここに書きます。
登録・更新や絞り込み検索、計算など、複雑な機能では自然とservicesのコード量が増えていきますね。
queries
・app/queries
modelのクラスメソッドやscopeを記述します。
あまり使ってなかったですが、DBに対して操作するクラスメソッドやscopeの記述はこちらに書くが正しいようです。
1 2 3 4 | class Hoge < ApplicationRecord include Queries::TestQueries end |
1 2 3 4 5 6 | module Queries::HogeQueries scope :contents_type_is, -> (value) { where(contents_type: value) } end |
validators
・app/validators
カスタムバリデーションを追加する場合は、ここに書きます。
ActiveModel::EachValidatorを継承し、validate_eachメソッドを実装します。
modelsのバリデーション機能をvalidatorsに切り出すことで、modelがfatになるのを防ぎます。
ex)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class DateValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) value = value.to_s unless value.is_a?(String) value = value.gsub('/', '-') record.errors[attribute] << I18n.t('errors.messages.invalid_date') unless /\A\d{1,4}\-\d{1,2}\-\d{1,2}\Z/ =~ value begin (y,m,d) = value.split('-') Time.local(y, m, d, 0, 0, 0) rescue record.errors[attribute] << I18n.t('errors.messages.invalid_date') end end end class Hoge < ActiveRecord::Base validates :date end |
以上のような各レイヤにおけるアーキテクチャについて考え整理し、それぞれの役割を理解した上で開発することが大事だと実感しています。
他にもユーザが入力したデータを変換するparametersやデータオブジェクトを管理するview_objectsなどがあり、開発状況に合わせて使い合わせていきたいです。