
clean-architecture
熱門根據依賴規則(Dependency Rule)來組織軟體架構:原始碼的依賴方向由外而內,從框架指向使用案例再到實體。當使用者提到「架構層」、「依賴規則」、「連接埠與適配器(六角形架構)」、「洋蔥架構」、「吶喊架構」、「商業邏輯該放哪裡」、「與資料庫解耦」、「不重寫就能更換框架」或「保持商業規則獨立」時觸發。此外,在決定程式碼屬於哪一層、將核心邏輯與基礎設施隔離、定義模組邊界,或討論框架是否該呼叫你的程式碼(或反過來)時也適用。涵蓋元件原則、邊界與 SOLID。若需程式碼層級的品質,請參考 clean-code。若需領域建模,請參考 domain-driven-design。
根據依賴規則(Dependency Rule)來組織軟體架構:原始碼的依賴方向由外而內,從框架指向使用案例再到實體。當使用者提到「架構層」、「依賴規則」、「連接埠與適配器(六角形架構)」、「洋蔥架構」、「吶喊架構」、「商業邏輯該放哪裡」、「與資料庫解耦」、「不重寫就能更換框架」或「保持商業規則獨立」時觸發。此外,在決定程式碼屬於哪一層、將核心邏輯與基礎設施隔離、定義模組邊界,或討論框架是否該呼叫你的程式碼(或反過來)時也適用。涵蓋元件原則、邊界與 SOLID。若需程式碼層級的品質,請參考 clean-code。若需領域建模,請參考 domain-driven-design。
整潔架構框架
一種嚴謹的軟體結構方法,讓商業規則獨立於框架、資料庫與傳遞機制。在設計系統架構、審查模組邊界或提供依賴管理建議時,應用這些原則。
核心原則
原始碼依賴必須指向內層——朝向較高層級的政策。 內圈的任何東西都不能知道外圈的任何東西。這條單一規則產生的系統可測試,且獨立於框架、UI、資料庫及任何外部機構。商業規則才是重點;資料庫、網頁框架與傳遞機制都是細節——當細節依賴政策時,你可以延遲決策、更換實作,並隔離測試商業邏輯。
評分
目標:10/10。 根據七項快速診斷問題,每滿足一項得1分(0-7分),再對應到0-10分區間:滿足6-7項 = 9-10分(依賴規則成立,商業邏輯獨立於框架與資料庫);4-5項 = 6-8分(核心可測試,但部分細節洩漏至內層);2-3項 = 3-5分(框架或持久化主導結構);0-1項 = 0-2分(無邊界——商業規則存在於控制器與ORM模型中)。報告分數、未通過的診斷項目,以及修復所需的具體反轉。
1. 依賴規則與同心圓
核心概念: 將架構組織為同心圓——最內層是實體(企業商業規則),然後是使用案例(應用程式商業規則),再來是介面適配器,最外層是框架與驅動程式。原始碼依賴始終指向內層。
為何有效: 當高層級政策不依賴低層級細節時,你可以更換資料庫、網頁框架或API風格,而不需觸碰商業邏輯——系統能抵禦堆疊中最易變的部分。
關鍵見解:
- 內圈不能提及外圈的名稱——沒有來自外圈的類別、函式、變數或資料格式
- 跨越邊界的資料必須以對內圈最方便的形式傳遞,而非由外圈決定
- 依賴反轉(介面定義在內層,實作在外層)是執行此規則的機制
- 圓圈的數量不固定——典型是四個;規則保持不變
- 框架是細節,不是架構——它們屬於最外圈
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
| 層級方向 | 內圈定義介面;外圈實作 | UserRepository 介面在使用案例層;PostgresUserRepository 在適配器層 |
| 資料跨越 | DTO 跨越邊界,而非 ORM 實體 | 使用案例回傳 UserResponse DTO,而非 ActiveRecord 模型 |
| 依賴方向 | 匯入箭頭始終指向內層 | 控制器匯入使用案例;使用案例絕不匯入控制器 |
當內圈匯入指向外圈時,請參閱 references/dependency-rule.md 以取得四圈程式碼解說、資料跨越規則,以及修復問題的四步驟依賴反轉程序。
2. 實體與使用案例
核心概念: 實體封裝企業層級的商業規則——即使沒有軟體也存在的規則。使用案例包含應用程式特定的規則,協調資料進出實體的流程。
為何有效: 將企業做什麼(實體)與應用程式如何協調(使用案例)分開,讓你能跨應用程式重用實體,並在不改變核心商業規則的情況下變更應用程式行為。
關鍵見解:
- 實體不是資料庫列——它們是封裝關鍵商業規則的物件或純函式
- 使用案例接受請求模型並回傳回應模型——絕不使用框架物件
- 每個使用案例是一個單一的應用程式操作(
CreateOrder、ApproveExpense) - 互動器模式:使用案例類別實作輸入邊界介面,並呼叫輸出邊界介面
- 使用案例的變更不應影響實體;實體的變更可能波及使用案例
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
| 實體設計 | 關鍵商業規則,零框架依賴 | Order.calculateTotal() 套用稅務規則;對 HTTP 一無所知 |
| 請求/回應 | 簡單的資料結構跨越邊界 | CreateOrderRequest { items, customerId }——沒有 ORM 模型 |
| 單一職責 | 每個操作一個使用案例 | PlaceOrder、CancelOrder、RefundOrder 作為獨立類別 |
| 互動器 | 實作輸入埠,呼叫輸出埠 | PlaceOrderInteractor implements PlaceOrderInput |
在設計互動器或決定某事物屬於實體還是使用案例時,請參閱 references/entities-use-cases.md——完整說明企業與應用程式商業規則,並附有請求/回應模型範例。
3. 介面適配器與框架
核心概念: 介面適配器在使用案例/實體適用的形式與外部機構要求的形式之間轉換資料。框架與驅動程式是最外層——與外部世界連接的黏合程式碼。
為何有效: 當網頁框架、ORM 或訊息佇列被限制在外圈時,更換其中任何一個都是局部變更。資料庫是細節;網頁是細節;細節應該是商業規則的外掛程式,而不是應用程式的骨架。
關鍵見解:
- 控制器將 HTTP 轉換為使用案例輸入;呈現器將使用案例輸出轉換為檢視模型
- 閘道器實作使用案例定義的儲存庫介面——內圈定義合約,外圈履行合約
- 商業規則永遠不知道資料存在 SQL、NoSQL 或平面檔案中,也不知道傳遞方式是 HTTP
- 對框架保持懷疑——它們希望你與它們耦合;保持距離
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
| 控制器 | 傳遞機制 → 使用案例輸入 | OrderController.create(req) 建立 CreateOrderRequest,呼叫互動器 |
| 呈現器 | 使用案例輸出 → 檢視模型 | OrderPresenter.present(response) 格式化為 JSON/HTML |
| 閘道器 | 依資料庫實作的儲存庫介面 | SqlOrderRepository implements OrderRepository |
| 框架邊界 | 框架向內呼叫,絕不反過來 | Express 路由處理器呼叫控制器;控制器絕不匯入 Express |
在接線控制器、呈現器或閘道器,或論證資料庫/網頁是細節時,請參閱 references/adapters-frameworks.md——涵蓋外掛程式架構及如何將框架限制在邊緣。
4. 元件原則
核心概念: 元件是部署的單元。三個內聚原則決定元件內部的內容;三個耦合原則決定元件之間的關係。
為何有效: 組合不良的元件會產生漣漪效應,一個變更強迫重新部署不相關的程式碼;這些原則讓變更局部化,並讓版本獨立釋出。
關鍵見解:
- REP(重用/發布等價):元件中的類別必須可版本化並作為一個單元發布
- CCP(共同封閉):因相同原因、同時變更的類別應放在一起——元件的 SRP
- CRP(共同重用):不要強迫使用者依賴他們不使用的類別
- ADP(無環依賴):元件圖不能有循環——透過 DIP 或新元件打破循環
- SDP(穩定依賴):依賴方向應朝向穩定性
- SAP(穩定抽象):穩定的元件應抽象;不穩定的應具體
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
| 元件分組 | 將一起變更的類別分組(CCP) | 所有訂單相關的使用案例放在一個元件中 |
| 打破循環 | 應用 DIP 反轉依賴邊 | 將介面提取到新元件中以打破循環 |
| 穩定性指標 | 不穩定性 I = Ce / (Ca + Ce) | 許多傳入、無傳出依賴 → I 接近 0(穩定) |
在將類別分組為可部署元件或打破依賴循環時,請參閱 references/component-principles.md——透過不穩定性指標詳細說明 REP、CCP、CRP、ADP、SDP、SAP。
5. SOLID 原則
核心概念: 五個類別與模組層級的原則——單一職責、開放封閉、里氏替換、介面隔離、依賴反轉——是實現依賴規則的中層建構區塊。
為何有效: 每個原則處理依賴出錯的特定方式,防止僵化、脆弱與不可移動性,這些特性會將程式碼庫變成遺留系統的惡夢。
關鍵見解:
- SRP:一個模組只有一個變更理由——它服務一個角色(而非「做一件事」)
- OCP:透過新增程式碼擴展行為,而非修改現有程式碼——策略與外掛模式
- LSP:子型別必須可透過基底介面使用,且客戶端不需知道——被非預期例外或忽略的方法違反
- ISP:客戶端不應依賴他們不使用的方法——胖介面造成不必要的耦合
- DIP:高層級模組與低層級模組都應依賴由高層級模組定義的抽象
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
| SRP 違反 | 類別服務多個角色 | Employee 處理薪資(CFO)、報表(COO)、持久化(CTO) |
| OCP 透過策略 | 透過新類別新增行為 | 新增 ExpressShipping 實作 ShippingStrategy;Order 不變 |
| LSP 違反 | 子型別改變預期行為 | Square extends Rectangle 破壞 setWidth()/setHeight() 合約 |
| ISP 應用 | 將胖介面拆分為角色介面 | Printer、Scanner、Fax 而非一個 MultiFunctionDevice |
| DIP 接線 | 高層級定義介面;低層級實作 | OrderService 依賴 PaymentGateway,而非 StripeClient |
在對特定類別應用 SRP/OCP/LSP/ISP/DIP 或診斷違反時,請參閱 references/solid-principles.md——每個原則透過程式碼範例及其預防的壞味道詳細說明。
6. 邊界與邊界解剖
核心概念: 邊界是重要事物與細節之間的分界線,透過多型實作:依賴跨越時指向內層,而控制流可任意方向。
為何有效: 每個邊界買到延遲決策或更換實作的選項;策略性邊界放置決定系統多年維護是愉快還是痛苦。
關鍵見解:
- 完整邊界在兩側使用相互介面;部分邊界使用較簡單的策略或外觀模式
- 謙卑物件模式:將邊界程式碼拆分為難測試的部分(靠近邊界)與易測試的部分(邏輯)
- 服務不自動成為架構邊界——具有胖共享資料模型的微服務是帶有網路呼叫的單體
- 測試是最孤立的元件:它們向內依賴,沒有東西依賴它們
- 過早的邊界成本高,但缺少邊界也成本高——在可能變動的點畫出邊界
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
| 完整 vs. 部分邊界 | 相互埠,或單一策略 | 使用案例定義 PlaceOrderInput/PlaceOrderOutput;簡單案例使用 ShippingStrategy |
| 謙卑物件 | 將可測試邏輯與基礎設施分開 | PresenterLogic(可測試)產生 ViewModel;View(謙卑)渲染它 |
| Main 作為外掛 | 組合根組裝系統 | main() 接線所有具體實作並啟動應用程式 |
在決定邊界畫在哪裡、選擇完整或部分邊界,或應用謙卑物件模式時,請參閱 references/boundaries.md——也涵蓋服務作為邊界、測試邊界,以及 Main 作為最終外掛。
常見錯誤
| 錯誤 | 為何失敗 | 修正 |
|---|---|---|
| ORM 洩漏到商業邏輯 | 實體與 schema 耦合;資料庫變更重寫商業規則 | 將領域實體與持久化模型分離;在適配器層映射 |
| 商業規則在控制器中 | 無 HTTP 無法測試;跨端點重複 | 將邏輯移至使用案例互動器;控制器僅轉換與委派 |
| 框架優先架構 | 框架主導結構;更換意味著重寫 | 將框架視為外掛;按商業能力組織程式碼 |
| 循環元件依賴 | 變更不可預測地漣漪;無法獨立釋出 | 應用 DIP 或提取共享抽象元件 |
| 每個功能一個巨型使用案例 | 膨脹的數千行協調器 | 拆分為專注的單一操作使用案例 |
| 「因為簡單」而跳過邊界 | 耦合默默累積,直到成本巨大 | 在可能變動的點主動畫出邊界 |
| 微服務自動等於好架構 | 分散式單體比乾淨的單體更糟 | 在服務內與跨服務應用依賴規則;服務是部署邊界,不是架構邊界 |
快速診斷
| 問題 | 若否 | 行動 |
|---|---|---|
| 能否在沒有 DB、網頁伺服器或框架的情況下測試商業規則? | 規則與基礎設施耦合 | 將實體與使用案例提取到介面後方;模擬外層 |
| 所有原始碼依賴是否指向內層? | 違反依賴規則 | 引入邊界介面;反轉有問題的依賴 |
| 能否在不觸碰商業邏輯的情況下更換資料庫? | 持久化洩漏至內層 | 儲存庫模式;將持久化隔離在適配器中 |
| 使用案例是否獨立於傳遞機制? | 使用案例知道 HTTP/CLI/佇列 | 在使用案例簽章中使用純 DTO |
| 框架是否限制在最外圈? | 框架就是你的架構 | 將框架呼叫包裝在介面後方;推到邊緣 |
| 元件圖是否無環? | 存在循環依賴 | 應用 ADP:透過 DIP 或新元件打破每個循環 |
| Main(組合根)是否接線所有依賴? | 具體類別在內圈實例化 | 將建構移至 Main;使用 DI 或工廠 |
延伸閱讀
基於 Robert C. Martin 的軟體架構權威指南:
- 《Clean Architecture:軟體結構與設計的工匠指南》 by Robert C. Martin
關於作者
Robert C. Martin(「Uncle Bob」) 自 1970 年開始程式設計的軟體工程師,敏捷宣言的創始簽署人之一,也是《Clean Code》、《The Clean Coder》、《Clean Architecture》與《Clean Agile》的作者。他的 SOLID 原則是物件導向設計的基礎詞彙,他的著作主張架構在於管理依賴,並保持商業規則獨立於基礎設施細節。





