貧血的領域模型

2003 年 11 月 25 日

這是其中一種已經存在很長一段時間的反模式,但似乎在此刻特別流行。我曾與 Eric Evans 聊過這件事,我們都注意到它們似乎越來越受歡迎。作為適當的 領域模型 的忠實支持者,這並非好事。

貧血領域模型的基本症狀是乍看之下,它看起來像是真實的東西。有物件,許多以領域空間中的名詞命名,而這些物件與真正的領域模型所具有的豐富關係和結構相連。當你檢視行為時,就會發現問題所在,你會意識到這些物件幾乎沒有任何行為,讓它們變成不比 getter 和 setter 的集合好多少。事實上,這些模型通常會附帶設計規則,說明你不得在領域物件中放置任何領域邏輯。相反地,有一組服務物件會擷取所有領域邏輯,執行所有運算,並使用結果更新模型物件。這些服務存在於領域模型之上,並使用領域模型作為資料。

這種反模式的基本恐怖之處在於,它與物件導向設計的基本概念完全相反;物件導向設計是將資料和流程結合在一起。貧血領域模型實際上只是一個程序式風格的設計,正是我(和 Eric)這種物件狂熱分子自從 Smalltalk 早期以來一直在對抗的那種東西。更糟的是,許多人認為貧血物件是真正的物件,因此完全錯失了物件導向設計的重點。

現在物件導向的純粹主義一切都很好,但我了解到我需要更多基本論點來反對這種貧血。貧血領域模型的問題本質上是它們會招致領域模型的所有成本,卻沒有產生任何好處。主要成本是對資料庫進行對應的笨拙性,這通常會導致 O/R 對應的整個層級。如果你使用強大的 OO 技術來組織複雜的邏輯,這是有價值的。然而,透過將所有行為抽取到服務中,你基本上會得到 交易指令碼,因此會失去領域模型可以帶來的優點。正如我在 P of EAA 中所討論的,領域模型並不總是最好的工具。

值得強調的是,將行為放入領域物件中不應與使用分層法將領域邏輯與持久性和呈現責任等事項分開的紮實方法相矛盾。應該在領域物件中的邏輯是領域邏輯 - 驗證、計算、商業規則 - 無論你喜歡如何稱呼它。(在某些情況下,你會提出將資料來源或呈現邏輯放入領域物件中的論點,但這與我對貧血的看法無關。)

所有這些混淆的一個來源是,許多 OO 專家確實建議在領域模型之上放置一層程序服務,以形成 服務層。但這並非讓領域模型沒有行為的論點,事實上,服務層倡導者將服務層與行為豐富的領域模型結合使用。

Eric Evans 出色的著作 領域驅動設計 對這些層級有以下說明。

應用程式層 [他稱之為服務層]:定義軟體應執行的作業,並指示表達性網域物件解決問題。此層負責的任務對業務有意義,或對於與其他系統的應用程式層互動是必要的。此層保持精簡。它不包含業務規則或知識,而僅協調任務,並將工作委派給下一層的網域物件協作。它沒有反映業務情況的狀態,但可以具有反映使用者或程式任務進度的狀態。

網域層 (或模型層):負責表示業務概念、業務情況資訊和業務規則。反映業務情況的狀態在此處受到控制和使用,即使儲存其技術細節的委派給基礎架構。此層是業務軟體的核心。

此處的重點在於服務層很精簡 - 所有關鍵邏輯都位於網域層。他在服務模式中重申此重點

現在,更常見的錯誤是輕易放棄將行為納入適當物件,逐漸滑向程序式程式設計。

我不知道為什麼這種反模式如此常見。我懷疑這是因為許多人從未真正使用過適當的網域模型,特別是如果他們來自資料背景。有些技術會鼓勵這樣做;例如 J2EE 的 Entity Beans,這就是我偏好 POJO 網域模型的原因之一。

一般來說,您在服務中發現的行為越多,您就越有可能剝奪自己網域模型的優點。如果您的所有邏輯都在服務中,您就剝奪了自己。