Clean Architecture
What is Clean Architecture
核心觀念
獨立於框架: 將框架視為工具,而非將系統侷限於框架之中。當需要更換框架時,系統的商業邏輯不會受到影響。
可測試: 能夠在沒有 UI、資料庫、網頁伺服器的情況下,單純測試商業邏輯。
獨立於 UI: 可以在不改變系統架構的前提下更換 UI 介面。
獨立於資料庫: 商業邏輯與資料庫系統分離,即使更換資料庫也不會影響核心業務運作。
獨立於任何外部代理: 商業邏輯能夠獨立於任何第三方服務運行。
層級結構(由外往內)
1 | 🔴 第1層:Frameworks & Drivers (最外層) |
第 1 層:Frameworks & Drivers(框架與驅動層)
包含內容:
- 資料庫系統:MySQL、PostgreSQL、MongoDB、Redis
- Web 框架:Express.js、Fastify、Koa.js
- 外部服務:第三方 API、支付系統、郵件服務
- 配置檔案:環境變數、設定檔
特性:
- 最容易變化的層級
- 具體的技術實現
- 與外部世界直接接觸
第 2 層:Interface Adapters(介面轉換器層)
包含內容:
- Controllers:處理 HTTP 請求、路由控制
- Presenters:格式化輸出數據、回應格式
- Gateways:資料庫存取實現、外部 API 呼叫
- Repository 實現:具體的數據存取邏輯
- Validators:輸入驗證、數據轉換
特性:
- 連接內外層的橋樑
- 數據格式轉換
- 實現內層定義的介面
第 3 層:Use Cases(應用層)
包含內容:
- Use Cases:具體的業務流程(CreateUser、ProcessOrder)
- Application Services:應用程式特定的業務邏輯
- 介面定義:Repository、Gateway 等介面契約
- Business Rules:應用程式層級的業務規則
特性:
- 協調 Entities 完成業務流程
- 定義對外部的介面需求
- 應用程式特定的邏輯
第 4 層:Entities(實體層)
包含內容:
- Domain Entities:核心業務物件(User、Product、Order)
- Value Objects:值物件(Money、Email、Address)
- Domain Rules:企業核心業務規則
- Domain Events:領域事件
特性:
- 最穩定的層級
- 不依賴任何外部技術
- 核心業務規則
實際範例對應
以電商系統為例:
1 | 🔴 Frameworks & Drivers |
依賴流向提醒
1 | 框架層 → 介面層 → 用例層 → 實體層 |
每一層都只能依賴比自己更內層的層級,這樣就能確保核心業務邏輯不會被外部技術細節污染!
Clean Architecture 三大原則
分層原則

▲ 上圖中,Entities 是架構中層級最高的一層,而最外圈屬於 I/O 層,是層級最低的。
高層與低層的定義
《Clean Architecture》書中對於分層有明確的定義:「離 I/O(輸入、輸出)越遠的元件層級越高,離 I/O 越近的層級越低」。
相依性原則 (Dependency Rule)
🔴 重點:依賴方向必須由外往內、由低層往高層。
- Entities 層的物件只能參考(依賴)同一層的其他物件,不可以參考其他層的物件
- Use Cases 層只能往內參考 Entities 層以及自己這一層的物件,不能往外參考其他層的物件
- 分層式架構又分為嚴格跟鬆散兩種,對於嚴格的來說,相依性是不能跨層的,但對於鬆散的來說,只要相依性保持向內,那麼跨幾層並未嚴格規定,設計架構時按照需求決定。

如果 Use Cases 需要將資料儲存到資料庫,那一定要參考到最外層的資料庫物件,這該怎麼辦?
Clean Architecture 透過依賴反轉 (Dependency Inversion Principle DIP),確保相依性原則能夠被遵守。
滿足相依性原則的兩大好處
- 因為高層的物件不再相依於框架等低層的物件,所以測試程式碼可以單獨進行測試,大幅簡化測試工作
- 系統的核心功能位於 Entities 與 Use Cases 這兩層,要更換 I/O 或應用程式框架變得簡單許多。例如,將 Web-Based UI 換成 Android App 時,只需要異動最外面兩層(Framework & Drivers 與 Interface Adapters),就能直接銜接原本的 Use Cases 與 Entities
跨層原則
🔴 重點:把 Entities 層的物件轉成 DTO 再往外傳遞。
請參考下圖,AddHostController 呼叫 AddHost Use Case,它再呼叫 Entity 層的 Host 物件,並獲得一個 Host 物件。接著 AddHost Use Case 直接把 Host 物件往外傳給 AddHostController。

當 Entities 層的物件離開 Use Case 層往外傳遞時(傳到 UI 或 DB 或其他外部系統),不要直接把 Entities 層的物件傳出 Use Case 層,而是要在 Use Cases 層定義往外傳遞的介面與資料結構。
如果直接把 Entities 層的物件往外傳,例如直接傳給 UI,很有可能因為 UI 端的需求,導致回頭影響 Entities 層的物件。