Rails 架構

by Aaron • 10/25/2024, 12:51:42 PM

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

© 2025 Aaron Li. All Rights Reserved.