Rails 架構

· 2 min read

什麼是 MVC 架構? 為什麼會有 Fat Model, Fat Concern, Fat Controller 的狀況出現, 又會如何解決相關問題呢?

On this page
  1. MVC 架構
  2. Fat Controller, Skinny Model
  3. Skinny Controller, Fat Model
  4. Skinny Controller, Skinny Model
  5. Reference

rails

MVC 架構

  1. Model:負責處理數據邏輯,通常與數據庫交互,定義業務邏輯和數據驗證等。
  2. View:負責展示數據,將模型數據轉換成使用者可以理解的格式,通常是 HTML。
  3. Controller:負責處理用戶請求,調用模型和視圖,並將結果呈現給用戶。

而隨這專案的開發會發現 MVC 架構會經歷三個階段

  1. Fat Controller, Skinny Model: 太多邏輯放在 Controller, Model 內則空空的
  2. Skinny Controller, Fat Model: 邏輯從 Controller 搬到 Model, 導致 Model 被放太多種邏輯
  3. Skinny Controller, Skinny Model: Controller 負責基本的流程, 簡單的 params 驗證 (strong parameters), Model 負責資料的驗證以及關聯, 而其他的商業邏輯則放到其他地方, 例如 Service Object, Command Pattern 等等團隊最後選擇的架構模式

Fat Controller, Skinny Model

在專案一開始的時候, 大多數的商業邏輯會先被放在 Controller 中, 例如

class UsersController < ApplicationController
  def create
    # 驗證 params
    # 5 ~ 10 行的 code ....
    User.create!(validate_params)

    # 其他商業邏輯
    # N 行其他的 code
    render json: {...}, status: :created
  end
end

然而這樣做有些缺點

  1. 商業邏輯無法復用 (dry)
  2. 違反 SRP

所以根據 MVC 的架構, 會將一些邏輯給放進去 Model 中

# app/models/user.rb
class User < ApplicationRecord
  # 負責驗證資料
  validates :email, presence: true

  def register(*args, **kwargs)
    # .... 一些邏輯
  end
end

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def create
    @user = User.register(params)
    render json: {...}, status: :created
  end
end

Skinny Controller, Fat Model

然而隨著專案的成長, 會發現 Model 會處理太多業務邏輯, 導致 Model 難以擴展和管理, 而且也違反了 SRP

class User < ApplicationRecord
  has_many :orders
  validates :email, presence: true

  # 訂單相關邏輯
  def place_order(*args, **kwargs)
    # ...
  end

  # 註冊相關
  def register(*args, **kwargs)
    #...
  end

  # 通知相關
  def notify(*args, **kwargs)
    #...
  end

  # ...
end

這時候會希望把一些與其他 Model 的共通邏輯放進 Concern 中, 例如

module Orderable
  extend ActiveSupport::Concern

  included do
    has_many :orders
  end

  # 訂單相關邏輯
  def place_order(product, quantity)
    # ...
  end
end

之後對可以下訂單的 Model 只需要 include 這個 Concern 就可以使用訂單相關邏輯, 而 Model 只需要負責處理資料驗證跟關聯就好

class User < ApplicationRecord
  include Orderable
  validates :email, presence: true
  # ...
end

class Company < ApplicationRecord
  include Orderable
  # ...
end

Skinny Controller, Skinny Model

這個 Controller 負責基本的流程, 簡單的 params 驗證 (strong parameters), Model 負責資料的驗證以及關聯, 而其他的商業邏輯則放到其他地方, 例如 Service Object, Command Pattern 等等, 例如

# app/services/authenticate_service
class AuthenticateService
  class << self
    def register(*args, **kwargs)
      #...
    end
  end
end

# app/models/user.rb
class User < ApplicationRecord
  has_many :orders
  validates :email, presence: true
end

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def create
    @user = AuthenticateService.register(params)
    render json: {...}, status: :created
  end
end

當然這如果再細分的話還可以分出 Decorator, QueryObject 等等在把 View 和 Model 的邏輯整理過, 但是這也需要考慮到整個專案的複雜度會不會被架構弄的太複雜, 導致維護和開發的難度提高太多。

Reference