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

MVC 架構
- Model:負責處理數據邏輯,通常與數據庫交互,定義業務邏輯和數據驗證等。
- View:負責展示數據,將模型數據轉換成使用者可以理解的格式,通常是 HTML。
- Controller:負責處理用戶請求,調用模型和視圖,並將結果呈現給用戶。
而隨這專案的開發會發現 MVC 架構會經歷三個階段
- Fat Controller, Skinny Model: 太多邏輯放在 Controller, Model 內則空空的
- Skinny Controller, Fat Model: 邏輯從 Controller 搬到 Model, 導致 Model 被放太多種邏輯
- 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
然而這樣做有些缺點
- 商業邏輯無法復用 (dry)
- 違反 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 的邏輯整理過, 但是這也需要考慮到整個專案的複雜度會不會被架構弄的太複雜, 導致維護和開發的難度提高太多。