《Domain-Driven Design 領域驅動設計》Podcast 準備稿:分層架構與建構區塊
書名: 領域驅動設計 Domain-Driven Design(Tackling Complexity in the Heart of Software) 作者: Eric Evans 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Domain-Driven Design.Entity 與 Value Object 的分野 (4/13) 涵蓋範圍: Part II 第4章 隔離領域(Isolating the Domain)+ 第5章 在軟體中表達模型(A Model Expressed in Software)
背景速覽
進入 Part II「模型驅動設計的建構區塊」——也就是大家最熟、最常被當成「DDD 全部」的戰術模式。這集打地基:先講為什麼要用分層架構把領域隔離出來,再講三個最基礎的建構區塊——Entity(實體)、Value Object(值物件)、Service(服務),以及組織它們的 Module。這兩章內容多(合計約 1.4 萬字),所以這集我會抓主幹、不糾結每個細節。
一句話重點
先用分層架構把領域層從 UI、應用、基礎設施裡隔離出來,再用三把尺判斷每個物件:要不要追蹤身分(Entity)、只關心屬性值(Value Object)、還是某個不屬於任何物件的操作(Service)。
值得討論的重點
1. 四層架構:把領域層解放出來
Evans 的經典四層:
- User Interface:展示資訊、解讀指令。
- Application:定義軟體該做什麼、協調領域物件——但這層要薄,不放業務規則。
- Domain:表達業務概念、狀態、規則——這是核心。
- Infrastructure:技術能力(持久化、訊息、繪圖)。
依賴方向往下,向上溝通要靠 callback/Observer。核心原則:把所有領域相關的程式碼集中在一層、和其他關注點隔離,讓領域物件擺脫「顯示自己、儲存自己、管理任務」這些雜事,專心表達模型。重點不在於真的要替換 UI,而在於各層分開後更好理解、更好維護。
2. Entity:由身分定義,不由屬性定義
有些物件的本質不在屬性、而在一條貫穿生命週期的連續性與身分。人、城市、汽車、銀行交易——任何需要「跨越狀態變化被追蹤」的東西。關鍵警告:程式語言內建的 == 只比記憶體位址,對領域身分毫無意義——物件從資料庫重載、或跨網路傳輸後,記憶體身分就沒了。Entity 的身分必須來自領域模型本身(自然鍵、系統產生 ID、外部機構發放 ID、使用者指定 ID)。建模原則:把 Entity 剝到最本質的識別屬性,其餘行為和屬性移到關聯物件。
3. Value Object:只關心「是什麼」,不關心「是哪一個」
沒有概念身分、描述某事物特徵的物件——顏色、金額、地址(看情境)。設計原則:視為不可變(immutable),變更就是整個替換;屬性要構成概念整體(街道、城市、郵遞區號該組成一個 Address,而不是 Person 的三個散落屬性)。不可變還帶來自由:可以安全共享(FLYWEIGHT)。
Evans 的經典提問:Address 是 Entity 還是 Value Object?取決於誰在問。 郵購公司只用來寄包裹 → Value Object;郵政服務要管遞送路線階層 → Entity;電力公司要識別同地址的不同申請 → Entity。同一個概念,在不同情境分類不同。
4. Service:不該硬塞給任何物件的操作
有些重要的領域操作,在 Entity 或 Value Object 裡找不到自然歸屬,硬塞會讓物件失去概念清晰度。這時用 Service。三個特徵:操作不自然屬於任何 Entity/VO、介面用領域模型的元素定義、無狀態。Service 命名傾向用動詞(強調「能為客戶做什麼」),名字來自統一語言。重要的是分清層級:Application Service(協調、解析輸入)、Domain Service(真正的業務操作,如 Funds Transfer 借貸)、Infrastructure Service(發 email)。
注意事項
⚠️ 這集就是「戰術模式被簡化誤用」最容易發生的地方。 很多框架(早期 J2EE、各種 DDD 樣板專案)把 Entity、VO、Repository 變成一堆機械化的 annotation 和樣板,結果大家以為「貼了這些標籤就是 DDD」。但 Entity/VO 的真正價值是逼你想清楚「這東西的身分是什麼」「這個值是不是概念整體」——是建模的判斷,不是貼標籤。
⚠️ Service 容易被濫用成「貧血模型」的幫凶。 如果你把所有行為都塞進 Service、Entity 只剩 getter/setter,那就退化成「貧血領域模型」(Anemic Domain Model)——這是 Fowler 點名批評的反模式。Service 是給「真的不屬於任何物件」的操作用的,不是讓你逃避把行為放進 Entity。這條一定要平衡地講。
⚠️ Smart UI「反模式」這段別誤讀。Evans 刻意討論它,不是全盤否定——對簡單的 CRUD、少量規則、團隊不擅長物件建模的情境,Smart UI(業務邏輯直接寫進 UI)反而生產力高、立即見效。重點是:Smart UI 和分層架構是互斥的岔路,選了 Smart UI 就很難遷移到 DDD。所以這是一個誠實的決策點:不是每個專案都該上 DDD。
⚠️ Module(套件)的劃分要按領域概念分組,不是按技術分層(別把所有 Entity 放一包、所有 VO 放一包)。第7章的例子會血淋淋示範按模式分組的下場(低內聚、高耦合,講的是「開發者讀了什麼書」而不是「貨運領域的故事」)。
專家補充
💡 「貧血領域模型」這個詞值得展開講。它指 Entity 只有資料、沒有行為,所有邏輯都跑到 Service 裡——表面上很物件導向,實際上是程序式設計披了 OO 的皮。Fowler 和 Evans 都反對。但有趣的是:在 Spring/Java 的主流實務裡,貧血模型反而非常常見(因為框架、交易管理、ORM 都偏好這樣)。所以這是一個「理想 vs 主流實務」的張力,很值得跟聽眾聊:你的 Entity 有行為嗎,還是只是 DTO?
💡 Value Object 是 DDD 裡最被低估、卻 CP 值最高的模式。把「金額」「日期區間」「地址」「座標」做成不可變的 Value Object,能消滅一大堆 primitive obsession(基本型別偏執)的 bug。現代語言(Kotlin data class、Java record、C# record)讓 Value Object 幾乎零成本。如果聽眾只想從這集帶走一個能立刻用的東西,我會推 Value Object。
💡 Evans 對 Address 的「看誰在問」這個答案,其實是整本 DDD 的縮影:沒有絕對正確的模型,只有對特定情境有用的模型。 同一個 Address,換個 Bounded Context 就換個分類。這條會在第 10 集(限界上下文)大放異彩——同一個詞在不同上下文意義不同,正是 Bounded Context 的核心。
討論問題
🎙️ 開場鉤子:「Entity、Value Object、Service——這三個詞你一定聽過,但你真的分得清什麼時候用哪個嗎?很多人學 DDD 就卡在這裡:把所有東西都做成 Entity,然後納悶為什麼設計越來越亂。」
🎙️ 自問自答:「怎麼判斷一個東西是 Entity 還是 Value Object?」——問一句:「我需要在它狀態變了之後,還認得出『這是同一個』嗎?」需要 → Entity;只關心值、兩個一樣的可以互換 → Value Object。
🎙️ 帶走的一題:「翻一下你 code 裡某個塞滿 getter/setter 的 Entity——它有任何業務行為嗎?如果完全沒有,你可能正養著一個貧血模型。」
更大範圍關聯
- 大家誤以為的「DDD 全部」:Part II 這幾個戰術模式,是最廣為人知、也最常被當成 DDD 全貌的部分。再次提醒整季定錨:這只是 DDD 的一塊,戰略精華在 Part III、IV。 只學戰術、漏掉戰略,是最常見的 DDD 誤讀。
- 連到 GoF:Service 裡的 Funds Transfer、Value Object 的 FLYWEIGHT 共享,都和 GoF 設計模式接軌。DDD 借用 GoF 的零件,但加上「必須契合領域概念」這個額外要求(第 9 集會專門講)。
- 連到 Clean Architecture:四層架構(依賴往內/往下、領域在核心)和 Uncle Bob 的同心圓架構是同一個訴求的不同畫法。Application Layer 要薄、Domain 是核心,這點兩本書完全一致。
- Smart UI 決策點:呼應「不是每個專案都該上 DDD」。簡單 CRUD 用 Smart UI 沒問題——這個務實態度,和「微服務不是銀彈」是同一種工程成熟度。
錄製建議
- 建議時長:約 24-26 分鐘(兩章合併,內容偏多)。配比:分層架構約 7 分、Entity vs Value Object 約 10 分(主菜,Address「看誰在問」一定要講)、Service + 貧血模型警告約 6 分、Smart UI 決策點約 3 分。
- 討論策略:Entity vs Value Object 是這集最有教學價值的部分,用「狀態變了還認不認得出是同一個」這把尺反覆操練幾個例子(人、座位、金額、地址)。「貧血模型」的警告要平衡地帶——不是說不能用 Service,而是別讓 Entity 變成空殼。Smart UI 放最後,當成「DDD 不是萬靈丹」的誠實提醒。
- 因為兩章合一、資訊密度高,記得放慢、多用具體例子,別變成念清單。