clean-ddd-hexagonal

clean-ddd-hexagonal

在設計 API、微服務或可擴展的後端架構時主動套用。觸發條件:DDD、Clean Architecture、Hexagonal、埠與適配器、實體、值物件、領域事件、CQRS、事件溯源、儲存庫模式、使用案例、洋蔥架構、outbox 模式、聚合根、防腐層。適用於處理領域模型、聚合、儲存庫或限界上下文。Clean Architecture + DDD + Hexagonal 模式,適用於後端服務,語言無關(Go、Rust、Python、TypeScript、Java、C#)。

55星標
3分支
更新於 2026/7/7
SKILL.md
唯讀
名稱
clean-ddd-hexagonal
描述

在設計 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 在需要前增加複雜度 從簡單讀寫開始,逐步演進
跨聚合交易 一個交易中處理多個聚合 使用領域事件達成一致性

實作順序

  1. 探索領域 — Event Storming、與領域專家對話
  2. 建模領域 — 實體、值物件、聚合(無基礎設施)
  3. 定義埠 — 儲存庫介面、外部服務介面
  4. 實作使用案例 — 協調領域的應用服務
  5. 最後加入適配器 — 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

來源

主要來源

主要模式參考

實作指南

補充綜合觀點