1. 程式人生 > 其它 >DDD領域驅動設計-充血模型和貧血模型

DDD領域驅動設計-充血模型和貧血模型

貧血模型


最早廣泛應用源於EJB2,最強盛時期則是由Spring創造,把

  • “行為”(邏輯、過程)
  • “狀態”(資料,對應到語言就是物件成員變數)

分離到不同的物件中:

  • 只有狀態的物件就是所謂的“貧血物件”(常稱為VO——Value Object)
  • 只有行為的物件就是我們常見的N層結構中的Logic/Service/Manager層(對應到EJB2中的Stateless Session Bean)。(曾經Spring的作者Rod Johnson也承認,Spring不過是在沿襲EJB2時代的“事務指令碼”,也就是面向過程程式設計)

貧血領域模型的基本特徵是:它第一眼看起來還真像這麼回事兒。專案中有許多物件,它們的命名都是根據領域來的。物件之間有著豐富的連線方式,和真正的領域模型非常相似。但當你檢視這些物件的行為時,會發現它們基本上沒有任何行為,僅僅是一堆getter/setter。 其實這些物件在設計之初就被定義為只能包含資料,不能加入領域邏輯。這些邏輯要全部寫入一組叫Service的物件中。這些Service構建在領域模型之上,使用這些模型來傳遞資料。


這種反模式的恐怖之處在於,它完全和麵向物件設計背道而馳。 面向物件設計主張將資料和行為繫結在一起,而貧血領域模型則更像是一種面向過程設計,Martin Fowler和Eric在Smalltalk時就極力反對這種做法。更糟糕的時,很多人認為這些貧血領域物件是真正的物件,從而徹底誤解了面向物件設計的涵義。


如今,面向物件的概念已經傳播得很廣泛了,而要反對這種貧血領域模型的做法,還需要更多論據。貧血領域模型的根本問題在於,它引入了領域模型設計的所有成本,卻沒有帶來任何好處。 最主要的成本是將物件對映到資料庫中,從而產生了一個O/R(物件關係)對映層。只有當你充分使用了面向物件設計來組織複雜的業務邏輯後,這一成本才能夠被抵消。如果將所有行為都寫入到Service物件,那最終你會得到一組事務處理指令碼,從而錯過了領域模型帶來的好處。正如martin在企業應用架構模式一書中說到的,領域模型並不一定是最好的工具。


將行為放入領域模型,這點和分層設計(領域層、持久化層、展現層等)並不衝突。因為領域模型中放入的是和領域相關的邏輯——驗證、計算、業務規則等。如果你要討論能否將資料來源或展現邏輯放入到領域模型中,這就不在本文論述範圍之內了。


一些面向物件專家的觀點有時會讓人產生疑惑,他們認為的確應該有一個面向過程的服務層。但是,這並不意味著領域模型就不應該包含行為。事實上,service層需要和一組富含行為的領域模型結合使用。
Eric Evans的Domain Driven Design一書中提到:

  • 應用層(即Service層)

描述應用程式所要做的工作,並排程豐富的領域模型來完成它。這個層次的任務是描述業務邏輯,或和其它專案的應用層做互動。這層很薄,不包含任何業務規則或知識,僅用於排程和派發任務給下一層的領域模型。這層沒有業務狀態,但可以為使用者或程式提供任務狀態。

  • 領域層(或者叫模型層)

表示業務邏輯、業務場景和規則。該層次會控制和使用業務狀態,即使這些狀態最終會交由持久化層來儲存。總之,該層是軟體核心。
服務層很薄——所有重要的業務邏輯都寫在領域層。他在服務模式中複述了這一觀點: 如今人們常犯的錯誤是不願花時間將業務邏輯放到合適的領域模型中,從而逐漸形成面向過程的程式設計。
我不清楚為什麼這種反模式會那麼常見。我懷疑是因為大多數人並沒有使用過一個設計良好的領域模型,特別是那些以資料為中心的開發人員。此外,有些技術也會推動這種反模式,比如J2EE的Entity Bean,這會讓我更傾向於使用POJO領域模型。


總之,如果你將大部分行為都放置在服務層,那麼你就會失去領域模型帶來的好處。


優點

簡單

  • 對於只有少量業務邏輯的應用來說,使用起來非常自然
  • 開發迅速,易於理解

注意:也不能完全排斥這種方式

缺點

無法良好的應對複雜邏輯

  • 比如收入確認規則發生變化,例如在4月1號之前簽訂的合同要使用某規則.....
  • 和歐洲簽訂的合同使用另外一個規則.....

充血模型


面向物件設計的本質:“一個物件是擁有狀態和行為的”,比如一個人:

  • 他眼睛什麼樣鼻子什麼樣這就是狀態
  • 人可以去打遊戲或是寫程式,這就是行為

為什麼要有一個“人Manager”這樣的東西存在去幫人“打遊戲”呢? 舉個簡單的J2EE案例,設計一個與使用者(User)相關功能。 傳統的設計一般是:

  • 類:User+UserManager
  • 儲存使用者呼叫:userManager.save(User user)

充血的設計則可能會是:

  • 類:User
  • 儲存使用者呼叫:user.save()
  • User有一個行為是:儲存它自己

其實它們沒有什麼特別適用的方向,個人更傾向於總是使用充血模型,因為OOP總是比面向過程程式設計要有更豐富的語義、更合理的組織、更強的可維護性—當然也更難掌握。

因此實際工程場景中,是否使用,如何使用還依賴於設計者以及團隊充血模型設計的理解和把握,因為現在絕大多數J2EE開發者都受貧血模型影響非常深。

另外,實際工程場景中使用充血模型還會碰到很多很多細節問題,其中最大的難關就是“如何設計充血模型”或者說“如何從複雜的業務中分離出恰到好處且包含語義的邏輯放到VO的行為中”。

如果一個物件包含其他物件,那就將職責繼續委託下去,由具體的 POJO 執行業務邏輯,將策略模式更加細粒度,而不是寫 ifelse。