Clean Architecture 實作指引
任務分派需要一個明確的判斷基準:哪些任務屬於同一層、哪些必須依序完成、哪些可以並行。
Clean Architecture 除了是架構模式,同時是一套責任分層的語彙——它讓人與 AI 代理人用同一組詞討論誰負責哪個部分、什麼時候可以動手。
核心原則:依賴只能由外向內
Clean Architecture 最核心的一句話是「依賴只能由外向內」。內層不知道外層的存在,外層依賴內層定義的介面。業務邏輯因此得以獨立,換資料庫、換 UI 框架都不需要動到核心規則。
架構由內到外分為四層:
Entities(核心業務規則) 封裝核心業務概念,例如書籍、訂單、使用者。定義實體屬性和業務不變量的驗證,不依賴任何框架或資料庫。
Use Cases(應用業務邏輯) 協調 Entities 之間的互動,定義系統功能。同時定義 Input Port、Output Port、Repository Port,只依賴 Entities 和這些自己定義的介面。
Interface Adapters(介面轉接層) 橋接業務邏輯和外部技術。Controller 接收外部請求並轉換為 Use Case 輸入,Presenter 格式化 Use Case 輸出,Repository 實作在這一層開始成形。
Frameworks & Drivers(框架與外部系統) 包含所有技術細節——資料庫、UI 框架、第三方服務。可以隨時替換,完全不影響內層。
依賴反轉原則(DIP)讓這一切成立
Use Case 需要存取資料,但不能直接依賴 SQLite。正確做法是:在 Use Cases 層定義 Repository Port(抽象介面),Use Case 只依賴這個介面。真正的資料庫實作在最外層,它去實作這個介面。
依賴方向因此被反轉:資料庫實作依賴 Use Case 定義的介面,而非 Use Case 依賴資料庫。最後在 Composition Root 把具體實作注入進去,這就是依賴注入的本質。
設計從內到外,實作從外到內
兩個階段的順序相反,這是分層架構在執行面最容易被漏掉的一點。
設計階段由內到外
- 設計 Entities:識別業務實體、定義 Value Objects(ISBN、Title 這類有驗證規則的值物件),在建構子中驗證業務不變量。
- 設計 Use Cases:定義 Input Port、Output Port、Repository Port。確定業務邏輯需要什麼,但不決定如何實作。
- 設計 Interface Adapters:Controller 如何轉換外部請求、Presenter 如何格式化輸出。
- 設計 Frameworks:選擇資料庫方案,實作 Repository。到這步才碰具體技術。
實作階段由外到內
- 先定義所有 Ports。在寫任何實作之前,確立 Use Case 介面和 Repository 介面,這是系統的骨架。
- 外層用 Mock 介面開發和測試。Controller 在 Use Case 還沒真正實作前就能測試,因為它依賴的是抽象介面。
- 補完內層實作:Interactor 實作業務邏輯,Repository 存取真正的資料庫。
- Composition Root 組裝依賴注入,系統就能真正執行。
設計階段確保業務邏輯不被技術細節污染,實作階段讓每一層都能獨立開發和測試。
驗證架構是否正確
每個 Phase 完成後對照四個方向確認。
依賴方向
- Entities 沒有任何外層的 import
- Use Cases 只 import Entities 和自己定義的介面
- Frameworks 層實作的是 Interface Adapters 定義的介面
介面契約
- Repository Port 定義在 Use Cases 層(不是 Frameworks 層)
- Repository 回傳的是 Entity 而不是資料庫 DTO
- Input/Output Port 沒有洩漏框架的型別(例如 HTTP Request、SQLite Row)
業務邏輯位置
- 業務不變量的驗證在 Entity 建構子
- 應用層邏輯在 Use Case Interactor
- Controller 只負責轉換和呼叫,不包含業務判斷
Interface-Driven Development
- 所有 Ports 在設計階段就定義完成
- 外層真的用 Mock 介面在開發測試
- 組裝注入在 Composition Root 統一處理
為什麼這對 AI 協作特別有價值
架構邊界的維護若依賴經驗判斷,它在時間壓力下的執行率會下降。AI 代理人執行的是明確規則:規則寫得夠清楚,遵守的一致性就跟壓力無關。
分層邊界就是這樣的規則。告訴代理人「這個 class 屬於 Use Cases 層,所以不能 import 任何 Framework 層的東西」,代理人就能機械性地驗證和維護這個邊界。
這套語言也讓任務拆分有了清楚的依據。「這個功能需要修改 Entity 和 Use Case,但不涉及 Repository 實作」是一個清楚的任務描述,對應到具體的修改範圍,不會模糊。
引入分層之後,實作結果的可預測性來自邊界明確:每個任務的修改範圍事先就限定在一層。代價落在設計階段——Ports 要在寫任何實作之前定義完成。
Ticket 怎麼按層拆、按什麼順序執行,走 層級隔離;分層規則怎麼在 commit 階段自動驗證,走 架構合規交給機制。