軟體系統中,真正解決領域問題的程式碼往往只占整體的一小部分,但其重要性卻遠超比例。為了讓我們能專注於領域模型的設計,必須將領域物件與系統的其他功能解耦,避免領域概念被技術細節淹沒。本章探討如何透過 Layered Architecture 達成這個目標,並討論在何種情境下可以選擇不同的架構策略。
Layered Architecture#
問題:領域邏輯的擴散#
以一個航運應用程式為例,使用者僅僅是從清單中選擇貨物的目的地城市,背後的程式碼卻要負責:
- 畫出 UI 元件
- 查詢資料庫取得城市列表
- 解讀並驗證使用者輸入
- 將選定城市與貨物關聯
- 將變更提交到資料庫
這些程式碼全部屬於同一個程式,但只有一小部分真正與航運業務相關。
當領域相關的程式碼散布在大量 UI、資料庫與其他技術程式碼中,會產生嚴重的後果:
- 修改 UI 可能不小心改變了業務邏輯
- 變更業務規則需要追蹤 UI、資料庫等各處程式碼
- 實作連貫的、模型驅動的物件變得不切實際
- 自動化測試也變得極為困難
四層架構#
業界經過多年經驗與慣例,收斂出以下四個概念層次:
| 層次 | 職責 |
|---|---|
| User Interface (Presentation Layer) | 負責向使用者展示資訊並解讀使用者的指令。外部角色也可能是另一個電腦系統。 |
| Application Layer | 定義軟體應完成的工作,協調領域物件來解決問題。此層保持精簡,不包含業務規則或知識,僅協調任務並將工作委派給下層的領域物件。 |
| Domain Layer (Model Layer) | 負責表達業務概念、業務狀態與業務規則。此層是業務軟體的核心。 |
| Infrastructure Layer | 提供通用的技術能力以支持上層:訊息發送、持久化、UI 繪製等。也可透過架構框架支持各層之間的互動模式。 |
flowchart TD
UI["User Interface Layer\n(展示與使用者互動)"]
App["Application Layer\n(協調任務、委派工作)"]
Domain["Domain Layer\n(業務概念、規則、狀態)"]
Infra["Infrastructure Layer\n(技術能力:持久化、訊息等)"]
UI -->|依賴方向| App
App -->|依賴方向| Domain
Domain -->|依賴方向| Infra
Infra -.->|技術服務支援| Domain
style Domain fill:#f9c74f,stroke:#f77f00,stroke-width:3px,color:#000分層的核心原則:任何層中的元素只依賴同層或其下方層的元素。向上通訊必須透過間接機制(如 callback 或 Observer 模式)。
設計準則#
將複雜程式分割為各層,每層內部發展出具內聚性的設計,且僅依賴下方的層。將所有與領域模型相關的程式碼集中在同一層,使其與 UI、Application 和 Infrastructure 程式碼隔離。這樣,領域物件就能擺脫顯示自身、儲存自身、管理應用程式任務等責任,專注於表達領域模型。
範例:線上銀行的分層設計#
以轉帳功能為例,使用者輸入兩個帳號與金額後發起轉帳。各層的職責分配如下圖所示:

Figure 4.1: Objects carry out responsibilities consistent with their layer and are more coupled to other objects in their layer.
幾個關鍵觀察:
- Domain Layer(而非 Application Layer)負責基本的業務規則——例如「每筆貸方都必須有對應的借方」
- 應用程式不假設轉帳請求的來源,UI 可以被替換為 XML wire request 而不影響 Application Layer 及以下各層
- 這種解耦的主要好處不在於真的要替換 UI,而在於各層的設計更容易理解與維護
Relating the Layers#
各層必須相互連接,同時不能失去分離帶來的好處:
- 向下呼叫:上層可以直接透過公開介面呼叫下層元素
- 向上通訊:需要透過間接機制,例如 callback 或 Observer 模式
- MVC 模式:最早用於連接 UI 與 Application / Domain Layer 的經典模式,後續衍生出許多 UI 架構變體
Infrastructure Layer 通常不會主動在 Domain Layer 中發起動作。作為「下層」,它不應該對所服務的領域有特定的了解。技術能力最常以 Service 的形式提供,例如:
- Application Layer 知道何時要寄送訊息
- Infrastructure Layer 知道如何寄送(email、fax 或其他方式)
- 呼叫端保持簡單,與 Service 介面封裝的複雜行為鬆耦合
Architectural Frameworks#
當 Infrastructure 以 Service 形式透過介面提供時,分層方式直觀且易於維持鬆耦合。但某些技術問題需要更具侵入性的基礎設施——Architectural Framework。
框架的理想目標是解決複雜技術問題,同時讓領域開發者能專注於表達模型。但框架也可能造成阻礙:
- 做出過多假設而限制領域設計的選擇
- 實作過於笨重而拖慢開發速度
使用框架的務實策略#
- 選擇性應用:只使用框架中最有價值的功能,而非全面採用
- 減少耦合:審慎挑選功能可降低實作與框架的耦合,保留後續設計決策的彈性
- 保持可讀性:在目前許多框架極為複雜的情況下,極簡主義有助於保持業務物件的可讀性與表達力
Evans 舉了早期 J2EE 應用的例子:當時常見的做法是將所有領域物件實作為 Entity Bean,導致效能和開發速度雙雙下降。後來的最佳實踐改為只對較粗粒度的物件使用 J2EE 框架,大部分業務邏輯改用一般 Java 物件實作。
The Domain Layer Is Where the Model Lives#
- Domain Model 是一組概念;Domain Layer 是這些概念在軟體中的體現
- 在 Model-Driven Design 中,Domain Layer 的軟體構造應當映射模型概念
- 當領域邏輯與程式的其他關注點混雜在一起時,這種對應關係無法實現
- 隔離領域實作是 Domain-Driven Design 的前提條件
Smart UI “Anti-Pattern”#
Evans 刻意討論這個與 DDD 互斥的替代方案,目的是對比說明為何以及何時需要隔離領域層。
做法#
將所有業務邏輯直接放入 UI。把應用程式切分為小功能,各自作為獨立的 UI 實作,業務規則內嵌其中。使用關聯式資料庫作為資料共享儲存庫,搭配自動化 UI 建構工具與視覺化程式設計工具。
優勢#
- 對簡單應用來說,生產力高且立即見效
- 能力較弱的開發者也能在短訓後使用
- 即使需求分析有缺陷,也可透過快速原型與迭代克服
- 各應用之間解耦,小模組的交付時程可以較精確地規劃
- 關聯式資料庫在資料層級提供良好的整合
- 4GL 工具運作良好
劣勢#
- 除了透過資料庫外,應用之間的整合非常困難
- 行為無法重用,業務問題沒有抽象化;業務規則必須在每個適用的操作中重複
- 快速原型和迭代會碰到天然上限——缺乏抽象使得重構選項受限
- 複雜度會迅速壓垮你,成長路徑只能走向更多簡單應用,無法優雅地演進到更豐富的行為
關鍵決策點#
Smart UI 和 Layered Architecture 是互斥的岔路。一旦選擇了 Smart UI,就無法輕易遷移到其他設計方式,只能整個替換應用程式。反之,承諾採用 Model-Driven Design 的團隊必須從一開始就以隔離的 Domain Layer 來設計。
- 適合 Smart UI 的情境:簡單功能、以資料輸入和顯示為主、少量業務規則、團隊不擅長物件建模
- 應選擇 DDD 的情境:複雜應用、需要豐富行為與靈活演進、團隊具備足夠技能或能取得專家協助
flowchart TD
Start{專案類型?}
Start -->|簡單 CRUD、少量規則| SmartUI[Smart UI]
Start -->|複雜行為、需擴展| DDD[Domain-Driven Design]
SmartUI --> R1[快速交付、低技術門檻]
DDD --> R2[Layered Architecture、可維護性]
style Start fill:#e0e0e0,stroke:#333,stroke-width:2px,color:#000
style SmartUI fill:#a8dadc,stroke:#457b9d,stroke-width:2px,color:#000
style DDD fill:#f9c74f,stroke:#f77f00,stroke-width:2px,color:#000
style R1 fill:#a8dadc,stroke:#457b9d,color:#000
style R2 fill:#f9c74f,stroke:#f77f00,color:#000
note["⚠ 兩條路徑互斥,無法漸進遷移"]
style note fill:#ffccd5,stroke:#c9184a,stroke-width:2px,color:#000中間也存在其他方案,例如 Fowler 所描述的 Transaction Script——分離 UI 與 Application 但不提供物件模型。底線是:只要架構能以某種方式隔離領域相關程式碼,使內聚的領域設計與系統其餘部分鬆耦合,那麼該架構就可能支持 DDD。
Other Kinds of Isolation#
除了 Infrastructure 和 UI 之外,還有其他因素可能破壞領域模型:
- 未完全整合到模型中的其他領域元件——第 14 章的 Bounded Context 和 Anticorruption Layer 會處理此議題
- 使用不同模型的其他開發團隊
- 領域模型本身變得過於龐大——第 15 章的 Distillation 討論如何在 Domain Layer 內部做區分,將核心概念從周邊細節中解放出來
隔離領域的最大好處,就是把所有其他東西移開,讓我們能真正專注於領域設計。