Clean Architecture

Ting

What is Clean Architecture

核心觀念

  • 獨立於框架: 將框架視為工具,而非將系統侷限於框架之中。當需要更換框架時,系統的商業邏輯不會受到影響。

  • 可測試: 能夠在沒有 UI、資料庫、網頁伺服器的情況下,單純測試商業邏輯。

  • 獨立於 UI: 可以在不改變系統架構的前提下更換 UI 介面。

  • 獨立於資料庫: 商業邏輯與資料庫系統分離,即使更換資料庫也不會影響核心業務運作。

  • 獨立於任何外部代理: 商業邏輯能夠獨立於任何第三方服務運行。

層級結構(由外往內)

1
2
3
4
🔴 第1層:Frameworks & Drivers (最外層)
└── 🟠 第2層:Interface Adapters
└── 🟡 第3層:Use Cases / Application Business Rules
└── 🔵 第4層:Entities (最內層)

第 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
🔴 Frameworks & Drivers
├── Express.js 伺服器
├── MySQL 資料庫
├── Redis 快取
└── 第三方支付 API

🟠 Interface Adapters
├── ProductController (處理商品請求)
├── OrderPresenter (格式化訂單回應)
├── MySQLProductRepository (商品數據存取)
└── PaymentGateway (支付服務介面實現)

🟡 Use Cases
├── CreateProductUseCase (建立商品流程)
├── ProcessOrderUseCase (處理訂單流程)
├── ProductRepository (介面定義)
└── PaymentService (介面定義)

🔵 Entities
├── Product (商品實體)
├── Order (訂單實體)
├── User (用戶實體)
└── Money (金額值物件)
依賴流向提醒
1
2
框架層 → 介面層 → 用例層 → 實體層
(外) (內)

每一層都只能依賴比自己更內層的層級,這樣就能確保核心業務邏輯不會被外部技術細節污染!

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 層的物件

Comments