在設計 API、微服務或可擴展的後端架構時主動套用。觸發條件:DDD、Clean Architecture、Hexagonal、埠與適配器、實體、值物件、領域事件、CQRS、事件溯源、儲存庫模式、使用案例、洋蔥架構、outbox 模式、聚合根、防腐層。適用於處理領域模型、聚合、儲存庫或限界上下文。Clean Architecture + DDD + Hexagonal 模式,適用於後端服務,語言無關(Go、Rust、Python、TypeScript、Java、C#)。
Clean Architecture + DDD + Hexagonal
結合 DDD 戰術模式、Clean Architecture 依賴規則與 Hexagonal 埠/適配器的後端架構,打造可維護、可測試的系統。
此技能是多種相關架構傳統的綜合觀點,並非單一權威架構模型。請根據設計問題選用原始來源:領域建模用 DDD,埠/適配器用 Hexagonal Architecture,依賴方向用 Clean Architecture,領域中心分層用 Onion Architecture,CQRS/Event Sourcing 僅在特定讀寫或時序需求時使用。
使用時機(與何時不該用)
| 使用時機 | 跳過時機 |
|---|---|
| 複雜商業領域,規則繁多 | 簡單 CRUD,商業規則少 |
| 長期維護的系統(數年) | 原型、MVP、一次性程式碼 |
| 5 人以上團隊 | 單人開發或小團隊(1-2 人) |
| 多種進入點(API、CLI、事件) | 單一進入點、簡單 API |
| 需要更換基礎設施(資料庫、訊息佇列) | 固定基礎設施,不太可能變動 |
| 需要高測試覆蓋率 | 快速腳本、內部工具 |
從簡單開始,只在需要時增加複雜度。 大多數系統不需要完整的 CQRS 或 Event Sourcing。
模式邊界
| 模式 | 主要問題 | 用途 | 不要當作 |
|---|---|---|---|
| DDD | 如何建模複雜商業領域? | 通用語言、限界上下文、聚合、值物件 | 僅是資料夾結構 |
| Hexagonal Architecture | 應用程式如何與外部世界互動? | 埠、驅動適配器、被驅動適配器、可測試的應用核心 | 強制六邊形或特定套件佈局 |
| Clean Architecture | 依賴應該指向哪個方向? | 向內依賴規則、使用案例邊界、框架獨立性 | 通用的四層資料夾模板 |
| Onion Architecture | 如何讓領域模型保持核心? | 領域中心分層與依賴反轉 | 當 Clean/Hexagonal 已解決問題時,不需要額外要求 |
| CQRS | 讀取和寫入是否需要不同模型? | 讀寫負載不同的限界上下文 | 預設的應用架構 |
| Event Sourcing | 是否需要從完整事件歷史還原狀態? | 稽核、時序查詢、可重播工作流程 | CRUD 系統的持久化預設 |
關鍵:依賴規則
依賴只能向內。外層依賴內層,絕不反向。
Infrastructure → Application → Domain
(adapters) (use cases) (core)
需抓出的違規:
- Domain 匯入資料庫/HTTP 函式庫
- 在此架構風格中,控制器直接呼叫儲存庫而非應用程式使用案例
- 實體依賴應用服務
設計驗證:「讓你的應用程式在沒有 UI 或資料庫的情況下也能運作」— Alistair Cockburn。如果你能在沒有基礎設施的情況下從測試中執行領域邏輯,你的邊界就是正確的。
快速決策樹
「這段程式碼該放哪裡?」
該放哪裡?
├─ 純商業邏輯,無 I/O → domain/
├─ 協調領域邏輯 + 有副作用 → application/
├─ 與外部系統溝通 → infrastructure/
├─ 定義如何互動(介面) → port(domain 或 application)
└─ 實作 port → adapter(infrastructure)
容易出錯的邊界 — LLM 最常放錯的位置:
| 程式碼 | 層級 | 原因 |
|---|---|---|
| 商業不變量(「訂單需有項目才能確認」) | Domain(實體方法) | 這是規則,不是協調 |
| 輸入格式驗證(JSON 結構、必填欄位) | Adapter(controller/DTO) | 協定問題,非商業規則 |
| 交易開始/提交 | Application | 使用案例 = 交易邊界 |
| ORM 實體 / 資料表模型 | Infrastructure | 對應到領域物件;絕不讓 ORM 實體成為領域實體 |
| Domain ↔ DB 對應 | Infrastructure(mapper) | 持久化細節 |
| 授權(「使用者是否允許?」) | Application(policy)或 adapter middleware | Domain 保持授權無關;僅在商業規則時才將角色規則編碼進 domain |
| 時鐘、UUID 生成 | Domain/application 中的 port;infrastructure 中的 adapter | 保持領域確定性與可測試性 |
| 回應領域事件 | Application(事件處理器) | 副作用 = 協調 |
| 為畫面查詢多個資料表 | 讀取模型(application 介面,infrastructure 實作) | 不要強迫透過聚合 |
貧血領域模型的測試: 如果應用服務從實體讀取狀態、做出決定、然後寫回狀態(if (order.status === 'draft') order.status = 'confirmed'),請將該邏輯移到實體中作為 order.confirm()。處理器應該像腳本一樣:載入聚合 → 呼叫一個行為方法 → 儲存 → 發布。
「這是實體還是值物件?」
實體還是值物件?
├─ 具有持續存在的唯一識別 → Entity
├─ 僅由其屬性定義 → Value Object
├─ 「這是同一個東西嗎?」 → Entity(識別比較)
└─ 「這個值相同嗎?」 → Value Object(結構相等)
「這應該成為自己的聚合嗎?」
聚合邊界?
├─ 必須在交易中保持一致 → 同一個聚合
├─ 可以最終一致 → 分開的聚合
├─ 僅透過 ID 引用 → 分開的聚合
└─ 聚合內 >10 個實體 → 拆分
規則: 每個交易一個聚合。跨聚合一致性透過領域事件(最終一致性)達成。
目錄結構
src/
├── domain/ # 核心商業邏輯(無外部依賴)
│ ├── {aggregate}/
│ │ ├── entity # 聚合根 + 子實體
│ │ ├── value_objects # 不可變值類型
│ │ ├── events # 領域事件
│ │ ├── repository # DDD 儲存庫介面(被驅動埠)
│ │ └── services # 領域服務(無狀態邏輯)
│ └── shared/
│ └── errors # 領域錯誤
├── application/ # 使用案例 / 應用服務
│ ├── {use-case}/
│ │ ├── command # 命令/查詢 DTO
│ │ ├── handler # 使用案例實作
│ │ └── port # 驅動埠介面
│ └── shared/
│ └── unit_of_work # 交易抽象
├── infrastructure/ # 適配器(外部關注)
│ ├── persistence/ # 資料庫適配器
│ ├── messaging/ # 訊息佇列適配器
│ ├── http/ # REST/GraphQL 適配器(驅動)
│ └── config/
│ └── di # 依賴注入 / 組合根
└── main # 啟動 / 進入點
埠的放置: 此技能預設採用 DDD 中心佈局,聚合儲存庫介面放在 domain/ 中聚合旁邊。更嚴格的 Hexagonal 佈局可能將被驅動埠放在 application/ports/driven/ 下。每個程式碼庫選擇一種慣例,並保持依賴規則完整。
表現層: 驅動適配器(REST/gRPC/CLI)在此預設佈局中放在 infrastructure/ 下。有些程式碼庫將其提升為第四個頂層 presentation/ 層(references/LAYERS.md 顯示了該變體)。為控制器選擇一個位置,不要兩者都用。
事件發布: 儲存聚合然後將事件發布到佇列是兩次寫入;兩者之間的崩潰會無聲地丟失事件。當事件必須可靠地到達其他服務時,請將它們寫入與聚合相同交易的 outbox 資料表 — 請參閱 references/CQRS-EVENTS.md 中的 outbox 模式。
DDD 建構區塊
| 模式 | 目的 | 層級 | 關鍵規則 |
|---|---|---|---|
| Entity | 識別 + 行為 | Domain | 依 ID 相等 |
| Value Object | 不可變資料 | Domain | 依值相等,無 setter |
| Aggregate | 一致性邊界 | Domain | 只有根可被外部引用 |
| Domain Event | 變更記錄 | Domain | 過去式命名(OrderPlaced) |
| Repository | 持久化抽象 | Domain(埠) | 每個聚合一個,非每個資料表 |
| Domain Service | 無狀態邏輯 | Domain | 當邏輯不適合實體時 |
| Application Service | 協調 | Application | 協調領域 + 基礎設施 |
反模式(關鍵)
| 反模式 | 問題 | 修正 |
|---|---|---|
| 貧血領域模型 | 實體是資料袋,邏輯在服務中 | 將行為移入實體 |
| 每個實體一個儲存庫 | 破壞聚合邊界 | 每個聚合一個儲存庫 |
| 洩漏基礎設施 | Domain 匯入 DB/HTTP 函式庫 | Domain 零外部依賴 |
| 上帝聚合 | 太多實體,交易緩慢 | 拆分為較小聚合 |
| 跳過使用案例 | 控制器直接呼叫儲存庫 | 透過應用程式使用案例路由 |
| CRUD 思維 | 建模資料而非行為 | 建模商業操作 |
| 過早使用 CQRS | 在需要前增加複雜度 | 從簡單讀寫開始,逐步演進 |
| 跨聚合交易 | 一個交易中處理多個聚合 | 使用領域事件達成一致性 |
實作順序
- 探索領域 — Event Storming、與領域專家對話
- 建模領域 — 實體、值物件、聚合(無基礎設施)
- 定義埠 — 儲存庫介面、外部服務介面
- 實作使用案例 — 協調領域的應用服務
- 最後加入適配器 — HTTP、資料庫、訊息實作
DDD 是協作性的。 與領域專家的建模會議與程式碼模式同等重要。
參考文件
在執行左欄任務前,請先閱讀對應檔案:
| 在你... | 閱讀 |
|---|---|
| 在任何層級寫程式碼、注入依賴、或決定三層 vs 四層 | references/LAYERS.md |
| 將系統拆分為服務/上下文、與舊系統或第三方系統整合(ACL)、執行 Event Storming | references/DDD-STRATEGIC.md |
| 建模實體、值物件、聚合、儲存庫、領域服務或工廠 | references/DDD-TACTICAL.md |
| 定義埠/適配器、命名介面、或佈局埠優先結構 | references/HEXAGONAL.md |
| 加入命令/查詢、領域事件 vs 整合事件、outbox、saga、或評估 CQRS/Event Sourcing | references/CQRS-EVENTS.md |
| 為任何層級撰寫單元/整合/架構測試 | references/TESTING.md |
| 快速回答「哪個模式/哪個層級」問題而不深入 | references/CHEATSHEET.md |
來源
主要來源
- Hexagonal Architecture — Alistair Cockburn (2005)
- Domain-Driven Design: The Blue Book — Eric Evans (2003)
- The Clean Architecture — Robert C. Martin (2012)
- Onion Architecture — Jeffrey Palermo (2008)
- Implementing Domain-Driven Design — Vaughn Vernon (2013)
主要模式參考
- CQRS — Martin Fowler
- Event Sourcing — Martin Fowler
- Repository Pattern — Martin Fowler (PoEAA)
- Unit of Work — Martin Fowler (PoEAA)
- Bounded Context — Martin Fowler
- Transactional Outbox — microservices.io
- Effective Aggregate Design — Vaughn Vernon
實作指南
- Microsoft: DDD + CQRS Microservices
- Domain Events — Udi Dahan
補充綜合觀點
- Clean Architecture: Standing on the Shoulders of Giants — Herberto Graça
- Explicit Architecture — Herberto Graça(綜合觀點,非權威來源)
- Get Your Hands Dirty on Clean Architecture — Tom Hombergs






