概要

常常聽到「Clean Architecture」,但你是不是也會想:這到底是什麼啊💦
就連資深工程師,很多時候也不太會好好解釋。
所以這篇我會用連小學生都能理解的程度來說明。

這次是用 Rails 實作,不過
請大家把它套用到你們正在使用的語言來學習!

正因為是 AI 時代,更需要懂設計

光是寫程式碼的價值已經逐漸降低,所以我認為學設計的意義更大了!
因為現在更重要的是「能夠談設計的人」!

🏠 連小學生都能懂的說明

我們先用「餐廳」來想像吧!餐廳裡有各種不同的人對吧。

  • 👤 客人(畫面/使用者)
  • 📋 外場人員(Controller)→ 負責接單的人
  • 👨‍🍳 主廚(Use Case)→ 決定料理規則的人
  • 🥩 食材(Entity)→ 真正重要的東西
  • 🏪 倉庫/冰箱(Repository/DB)→ 存放食材的地方

最重要的規則是:「主廚只要考慮食材本身就好!」

主廚只需要思考「今天的菜單是什麼」「要怎麼料理」。
他不需要去想「食材放在哪個倉庫」。
就算倉庫換了(例如從 MySQL 換成 PostgreSQL),主廚的工作也不會改變!這就是 Clean Architecture 的核心。

ChatGPT Image 2026年6月18日 10_56_23.png

🎯 什麼是 Domain 和 Business Rule?

「Domain」和「Business Rule」這些詞在工程圈裡很常見,
常常會讓人覺得:這到底在講什麼啊。下面我來用簡單方式說明。

Domain =「那個世界的事情」

「Domain」指的是這個應用程式要解決的現實世界問題領域

  • 電商網站就是「商品買賣的世界」
  • 醫院系統就是「看診、預約、病歷管理的世界」
  • 足球遊戲就是「足球這項運動的世界」

Business Rule =「那個世界的規則」

而 Business Rule 指的是那個世界中存在的、與技術無關的規則

電商網站的例子:

  • 庫存是 0 就不能下單
  • 不是會員就看不到特價價格
  • 訂單成立後不能取消

足球遊戲的例子:

  • 越位的話進球無效
  • 比賽時間是 90 分鐘
  • 被紅牌罰下後就要以 10 人應戰

這些規則不管資料庫是 MySQL 還是 PostgreSQL,
不管是 Rails 還是 Next.js,都不會改變

不管技術力多高、就算用了 AI,如果根本不懂足球規則,也做不出足球遊戲。

不管技術力多高、就算用了 AI,如果根本不懂足球規則,也做不出足球遊戲。

這就是領域知識(Domain Knowledge)的本質

所以,當 Domain 很大或很複雜時,不管工程師多優秀,剛開始的進度通常都會比較慢。
如果你覺得「怎麼會花這麼久?」那可能不是工程師技術力的問題,而是正在從零學習那個商業規則
這樣的背景,真的希望大家能理解!(尤其是非工程師)

ChatGPT Image 2026年6月18日 11_25_58.png

Entity 要用 Domain 的語言來說話

把剛剛圖片中的「Business Rule」用程式表達,就是這樣。
所以 Entity 裡只要寫「Business Rule」就好!

class Order
  # 「訂單成立後不能取消」這個商業規則
  def cancelable?
    status == "pending"
  end

  # 「庫存是 0 就不能下單」這個商業規則
  def purchasable?(stock)
    stock > 0
  end
end

💉 什麼是依賴注入(DI)?

「依賴注入」這個詞聽起來很難,但概念其實很單純。

「需要的工具,從外面拿進來」 就這樣而已。

用餐廳來想像

不注入的情況(壞例子)

主廚自己決定:「好,今天我要自己去某某超市買食材!」

  • 如果超市關門了就完蛋
  • 測試(試吃)時也得真的去買食材
  • 如果要換別家超市,就得把主廚的整個流程都改掉

注入的情況(好例子)

老闆把「今天要用這些食材」交給主廚。

  • 主廚不管食材從哪裡來,都能料理
  • 測試時只要給假食材(Mock)就行
  • 食材來源變了,主廚也不用知道

主廚不會自己決定「用哪個倉庫」。老闆會把「今天要用這些食材」交給他。這就是依賴注入。

用程式來看:

# 壞例子:主廚自己去超市買食材
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。

  • 補充
    這裡所謂的「外部」,指的是 Controller。
    主廚(Use Case)不會自己選倉庫(Repository)。
    因為是呼叫端傳進來,所以可以自由替換。

ChatGPT Image 2026年6月18日 16_40_22.png

🛤 Rails 的目錄結構

Rails 通常是 MVC,但導入 Clean Architecture 之後會變成這樣:

app/
├── entities/         # 🥩 純粹的商業規則(不依賴 Rails!)
├── use_cases/        # 👨‍🍳 Use Case(主廚)
├── repositories/     # 🏪 資料存取入口(介面轉換)
├── controllers/      # 📋 外場人員(保持精簡!)
└── models/           # 與資料庫對應(ActiveRecord)

1️⃣ Entity(食材)— 不依賴 Rails 的純粹類別

# 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。重點是:完全不知道資料庫的存在


2️⃣ Repository(倉庫管理員)— 串接 DB 與 Entity

# 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

3️⃣ Use Case(主廚)— 執行商業規則

# 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 就可以了。


4️⃣ Controller(外場人員)— 保持精簡!

# 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?

讀到這裡的人,應該已經有感覺了。

Clean Architecture 是一種系統設計思維:即使發生變更,也能讓影響範圍保持最小,讓設計不容易崩壞。

為此要遵守兩個規則:

① 分離職責(把程式整理整齊)
主廚只需要知道主廚的工作,倉庫管理員只需要知道倉庫的工作。
只要建立「除了自己的工作以外都不用知道」的狀態,改動某一部分時其他地方就不容易壞掉。

② 內層的人不用知道外層的事
主廚(商業邏輯)不需要知道資料庫是 MySQL 還是 PostgreSQL。
就算倉庫換了,主廚的工作也不會變。

小型專案時,「一個人全部包辦」也能運作。
但當店變大時,一開始就把角色分工好的店,和所有事情都由一個人知道的店,成長速度會完全不同。

這就是學 Clean Architecture 的理由。

ChatGPT Image 2026年6月18日 12_24_03.png

🤔 不適合採用的情況

Clean Architecture 不是銀彈。
反而在系統很小的時候導入,可能會變成過度設計

用社團活動來想像

一開始先 3 個人試著做做看(MVP・PoC)

三個朋友說「先來踢足球吧!」的時候,
通常不會把位置、陣型、細節規則都訂得很死吧。
先踢看看就好。

如果變成 30 個社員,還要去比賽...

那就得好好決定「位置」「戰術」「練習菜單」,不然根本沒辦法比賽。
如果還是「一個人攻防全包」,就會遇到極限。

放到應用程式開發來說

不一定要採用的情況:

  • MVP(Minimum Viable Product):用最少功能驗證的階段
  • PoC(Proof of Concept):確認技術或想法可行性的階段
  • 規格經常變動的初期階段
  • 個人開發的小型應用程式
  • 團隊還不熟悉 Clean Architecture

建議考慮採用的情況:

  • 團隊開發,會有多人一起碰同一份程式碼
  • 預期會長期營運的產品
  • 預計會更換資料庫或外部服務
  • 想要扎實地撰寫測試

一開始先用 MVP 方式簡單做出來,等應用程式慢慢長大之後,
如果開始覺得「差不多該分工了」,再考慮導入吧。

ChatGPT Image 2026年6月18日 17_18_18.png

🎯 總結

常見的 Rails Clean Architecture 模型:把所有東西都寫在一起(Fat Model) Entity・Use Case 分開 Controller 很複雜 Controller 保持精簡 如果換資料庫就錯誤滿天飛 只要修 Repository 就好 難以測試 各層都可以獨立測試> Fat Model 指的是 Rails 的 Model 塞入太多商業邏輯,變得肥大化的狀態。

一開始你可能會覺得「好難…」,但應用程式越大,它的價值就越明顯
不過對小型應用來說,也可能變成過度設計,所以請依照團隊與規模來調整導入方式。

使用的 AI

Claude Code
ChatGPT


原文出處:https://qiita.com/Hashimoto-Noriaki/items/f451a4ecb49a9476f09b


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬1   ❤️1
464
🥈
我愛JS
📝1   ❤️1
22
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登