常常聽到「Clean Architecture」,但你是不是也會想:這到底是什麼啊💦
就連資深工程師,很多時候也不太會好好解釋。
所以這篇我會用連小學生都能理解的程度來說明。
這次是用 Rails 實作,不過
請大家把它套用到你們正在使用的語言來學習!
光是寫程式碼的價值已經逐漸降低,所以我認為學設計的意義更大了!
因為現在更重要的是「能夠談設計的人」!
我們先用「餐廳」來想像吧!餐廳裡有各種不同的人對吧。
最重要的規則是:「主廚只要考慮食材本身就好!」
主廚只需要思考「今天的菜單是什麼」「要怎麼料理」。
他不需要去想「食材放在哪個倉庫」。
就算倉庫換了(例如從 MySQL 換成 PostgreSQL),主廚的工作也不會改變!這就是 Clean Architecture 的核心。

「Domain」和「Business Rule」這些詞在工程圈裡很常見,
常常會讓人覺得:這到底在講什麼啊。下面我來用簡單方式說明。
「Domain」指的是這個應用程式要解決的現實世界問題領域。
而 Business Rule 指的是那個世界中存在的、與技術無關的規則。
電商網站的例子:
足球遊戲的例子:
這些規則不管資料庫是 MySQL 還是 PostgreSQL,
不管是 Rails 還是 Next.js,都不會改變。
不管技術力多高、就算用了 AI,如果根本不懂足球規則,也做不出足球遊戲。
這就是領域知識(Domain Knowledge)的本質。
所以,當 Domain 很大或很複雜時,不管工程師多優秀,剛開始的進度通常都會比較慢。
如果你覺得「怎麼會花這麼久?」那可能不是工程師技術力的問題,而是正在從零學習那個商業規則。
這樣的背景,真的希望大家能理解!(尤其是非工程師)

把剛剛圖片中的「Business Rule」用程式表達,就是這樣。
所以 Entity 裡只要寫「Business Rule」就好!
class Order
# 「訂單成立後不能取消」這個商業規則
def cancelable?
status == "pending"
end
# 「庫存是 0 就不能下單」這個商業規則
def purchasable?(stock)
stock > 0
end
end
「依賴注入」這個詞聽起來很難,但概念其實很單純。
「需要的工具,從外面拿進來」 就這樣而已。
不注入的情況(壞例子)
主廚自己決定:「好,今天我要自己去某某超市買食材!」
注入的情況(好例子)
老闆把「今天要用這些食材」交給主廚。
主廚不會自己決定「用哪個倉庫」。老闆會把「今天要用這些食材」交給他。這就是依賴注入。
用程式來看:
# 壞例子:主廚自己去超市買食材
class RegisterUser
def initialize
@user_repository = UserRepository.new # 自己決定了
end
end
# 好例子:由老闆把食材交給主廚
class RegisterUser
def initialize(user_repository: UserRepository.new)
@user_repository = user_repository # 從外部接收
end
end
從外部傳進來之後,正式環境可以用真正的 Repository,
測試時可以很容易換成 Mock。

Rails 通常是 MVC,但導入 Clean Architecture 之後會變成這樣:
app/
├── entities/ # 🥩 純粹的商業規則(不依賴 Rails!)
├── use_cases/ # 👨🍳 Use Case(主廚)
├── repositories/ # 🏪 資料存取入口(介面轉換)
├── controllers/ # 📋 外場人員(保持精簡!)
└── models/ # 與資料庫對應(ActiveRecord)
# app/entities/user_entity.rb
class UserEntity
attr_reader :id, :name, :email, :age
def initialize(id:, name:, email:, age:)
@id = id
@name = name
@email = email
@age = age
end
# 商業規則寫在這裡
def adult?
age >= 18
end
def valid_email?
email.include?("@")
end
end
它不會繼承 ActiveRecord。重點是:完全不知道資料庫的存在。
# app/repositories/user_repository.rb
class UserRepository
# 從 DB 取資料,並轉成 Entity
def find(id)
record = User.find(id) # ← ActiveRecord 模型(一般 Rails 模型)
to_entity(record)
end
def save(user_entity)
record = User.find_or_initialize_by(id: user_entity.id)
record.update!(
name: user_entity.name,
email: user_entity.email
)
to_entity(record)
end
def find_all
User.all.map { |record| to_entity(record) }
end
private
# 將 ActiveRecord 記錄轉成 Entity(DB 世界 → 商業世界)
def to_entity(record)
UserEntity.new(
id: record.id,
name: record.name,
email: record.email
)
end
end
# app/use_cases/register_user.rb
class RegisterUser
# 從外部接收 Repository(依賴注入)
def initialize(user_repository: UserRepository.new)
@user_repository = user_repository
end
def call(name:, email:)
# 檢查商業規則
entity = UserEntity.new(id: nil, name: name, email: email)
raise "電子郵件地址不正確" unless entity.valid_email?
# 儲存(不用知道存在哪裡!)
@user_repository.save(entity)
end
end
Use Case 並不知道 DB 是 MySQL 還是 PostgreSQL。
只要替換 Repository 就可以了。
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
# Controller 只負責呼叫 Use Case
use_case = RegisterUser.new
user = use_case.call(
name: params[:name],
email: params[:email]
)
render json: { id: user.id, name: user.name }, status: :created
rescue => e
render json: { error: e.message }, status: :unprocessable_entity
end
end
「保持精簡」的意思,就是不要在 Controller 裡寫商業邏輯。
只要接單、交給主廚、把結果回傳給客人就好。
Controller 只做「接收並傳遞」,不寫商業邏輯。
職責分離會帶來兩個很棒的副產物。
① 更容易測試
可以只單獨測試主廚(Use Case)。只要丟一個假的倉庫(Mock)給它就行。
② 面對變更時比較不怕
就算把冰箱從 MySQL 換成 PostgreSQL,也只需要「修倉庫管理員(Repository)」就好。
主廚的程式碼一行都不用碰。
🏠 小房子可以什麼東西都亂放。但當房子變大時,如果不把「客廳」「廚房」「臥室」分清楚,就會搞不清楚東西放在哪裡。
職責分離,就是把程式整理整齊。
關於如何在 Next.js 實踐職責分離,這裡有解說👇
新人工程師不知道就糟了😱「職責分離」是什麼啊?🤔(以前端角度解說)
讀到這裡的人,應該已經有感覺了。
Clean Architecture 是一種系統設計思維:即使發生變更,也能讓影響範圍保持最小,讓設計不容易崩壞。
為此要遵守兩個規則:
① 分離職責(把程式整理整齊)
主廚只需要知道主廚的工作,倉庫管理員只需要知道倉庫的工作。
只要建立「除了自己的工作以外都不用知道」的狀態,改動某一部分時其他地方就不容易壞掉。
② 內層的人不用知道外層的事
主廚(商業邏輯)不需要知道資料庫是 MySQL 還是 PostgreSQL。
就算倉庫換了,主廚的工作也不會變。
小型專案時,「一個人全部包辦」也能運作。
但當店變大時,一開始就把角色分工好的店,和所有事情都由一個人知道的店,成長速度會完全不同。
這就是學 Clean Architecture 的理由。

Clean Architecture 不是銀彈。
反而在系統很小的時候導入,可能會變成過度設計。
一開始先 3 個人試著做做看(MVP・PoC)
三個朋友說「先來踢足球吧!」的時候,
通常不會把位置、陣型、細節規則都訂得很死吧。
先踢看看就好。
如果變成 30 個社員,還要去比賽...
那就得好好決定「位置」「戰術」「練習菜單」,不然根本沒辦法比賽。
如果還是「一個人攻防全包」,就會遇到極限。
不一定要採用的情況:
建議考慮採用的情況:
一開始先用 MVP 方式簡單做出來,等應用程式慢慢長大之後,
如果開始覺得「差不多該分工了」,再考慮導入吧。

常見的 Rails Clean Architecture 模型:把所有東西都寫在一起(Fat Model) Entity・Use Case 分開 Controller 很複雜 Controller 保持精簡 如果換資料庫就錯誤滿天飛 只要修 Repository 就好 難以測試 各層都可以獨立測試> Fat Model 指的是 Rails 的 Model 塞入太多商業邏輯,變得肥大化的狀態。
一開始你可能會覺得「好難…」,但應用程式越大,它的價值就越明顯!
不過對小型應用來說,也可能變成過度設計,所以請依照團隊與規模來調整導入方式。
Claude Code
ChatGPT
原文出處:https://qiita.com/Hashimoto-Noriaki/items/f451a4ecb49a9476f09b