什么是領域驅動設計的核心- 域 , 域模型 , 通用語言的抽象的概念。 我不會對此進行詳細介紹–對于那些感興趣的人,有維基百科(在頁腳中有很多參考文獻可供閱讀)。 從理論上講,這一切都是非常好的,并且域驅動的構建軟件的方式應該對所有人都具有吸引力–畢竟,構建該軟件是為了該領域的利益,而不是建筑師,開發人員或QA的利益。
但是現在談到了實際部分–如何實施DDD? 我將在當代的背景下回答這個問題,即使用spring和hibernate之類的框架。 我會證明它們的用法合理。 Spring是一個非侵入性的依賴注入框架。 Fowler也強烈支持DI,并且DI被認為是實現DDD的好方法。 休眠是使用關系數據庫時使用對象的一種方法。 另一種方法是使用JDBC并手動構造對象,但這很繁瑣。 因此,休眠不會影響體系結構部分-它是一種實用程序(當然,功能非常強大)。
在本文中,我將使用“休眠”和“彈簧”作為“給定的”,盡管它們可以通過任何DI框架以及任何依賴對象的ORM或其他持久性機制進行更改。
使用spring和hibernate實現DDD的公認方法是:
- 使用@Configurable使域對象適合進行依賴項注入(它們不會在spring之前實例化,因此它們需要這種特殊方法)
- 在域對象中注入存儲庫對象,以允許域對象執行與持久性相關的操作
- 使用薄的,無狀態的事務服務層(外觀)來協調域對象
這篇廣泛的文章顯示了這種方法的實現和描述。 另一個示例(沒有Spring)是http://dddsample.sourceforge.net/ 。 稍后再討論。
這種方法的替代方法是貧血域模型 。 它被認為是一種反模式,但同時非常普遍并且經常使用。 貧血數據模型的功能很簡單–域對象內部沒有業務邏輯–它們只是數據持有者。 而是將業務邏輯放在服務中。
之所以將其視為反模式,是因為,首先,這似乎是一種程序方法。 它破壞了封裝,因為對象的內部狀態完全不是內部狀態。 其次,由于領域對象是設計的中心,因此,如果它的操作不屬于它,而改為多個無狀態服務類,則很難更改它。 域驅動的設計針對中型到大型應用程序,這些應用程序發生了很大變化,并且需要一種簡便的方法來快速進行更改,而又不會破壞其他功能。 因此,在對象本身內具有對象的所有功能非常重要。 這也可以確保減少重復代碼。
因此,代替讓服務來計算價格: ProductServiceImpl.calculatePrice(complexProduct),我們應該簡單地擁有ComplexProduct.calculatePrice() 。 因此,每當領域專家說價格計算機制發生變化時,更改它的地方就是一種,也是最直接的一種。
如果考慮簡單的操作,這看起來很容易。 但是,當一個域對象需要另一個域對象來完成其工作時,它將變得更加復雜。 使用貧血數據模型,只需將另一個Service注入當前Service并調用其方法即可實現。 使用建議的DDD,可以通過將域對象作為參數來實現。
在我看來,域對象(它也是休眠實體)已經設置了其依賴項。 但不是在Spring之前,因為Spring無法確切知道要注入哪個領域對象。 它們由休眠“注入”,因為它確切知道應將哪個(由主鍵標識)域對象放置在另一個域對象中。 因此,有一個奇怪的例子–如果產品腐爛并且必須在倉庫中分配氣味,則必須調用例如Warehouse.increaseSmellLevel(getSmellCoeficient()) 。 并且它有精確的倉庫,不受彈簧的干擾。
現在,我不同意這一點。 大多數來源(包括上面鏈接的兩個來源)都指出應該將存儲庫/ DAO注入域對象中。 不,他們不應該。 只需調用“保存”或“更新”就不需要了解對象的內部狀態。 Hibernate仍然知道一切。 因此,我們只是將整個對象傳遞到存儲庫。
讓我們將其分為兩個部分- 業務邏輯和基礎架構邏輯 。 域對象應該對基礎結構一無所知。 那可能意味著它不應該知道它被保存在某個地方。 產品是否關心其存儲方式? 不,這是“感興趣”的存儲機制。 這是實際的缺點:
- 通過簡單地將存儲庫調用包裝在所有域對象中來實現CRUD –代碼重復
- 域對象可傳遞地依賴于持久性–即它不是純域對象,并且如果存儲庫發生更改,則也必須對其進行更改。 從理論上講,僅當域規則和屬性更改時,才應更改
- 人們很容易將事務,緩存和其他邏輯包含在域對象中
在上面的一篇文章中,我將在此處打開有關建議的解決方案的括號,以使代碼重復和樣板代碼更易于處理。 建議生成代碼。 而且我認為代碼生成是一種罪過。 它將無法刪除重復的或非常相似的代碼并將其抽象化為工具。 最引人注目的示例是生成ProductDAO,CategoryDAO,WarehouseDAO等。生成的代碼難以管理,無法擴展且嚴重依賴于外部元數據,這絕對不是面向對象的方法。
說到存儲庫,在建議的示例中,每個域對象都應該有一個存儲庫,該存儲庫又將調用持久性機制。 那我們得到什么:
用戶在UI中按“保存”>保存在服務上的UI調用(以便獲得事務支持)>保存在域對象上的服務調用>保存在資源庫上的域對象調用>保存在持久性機制上的資源庫調用>持久性機制保存對象。
是我自己,還是在這里調用域對象是多余的。 這是一種不增加任何內容的直通方法。 而且由于很多功能與CRUD有關(是的,即使在大型企業應用程序中也是如此),這對我來說似乎很糟糕。
最后,我發現@Configurable方法是一個hack。 它在后臺做了一些魔術,這不是任何通用語言功能(也不是設計模式),并且為了了解它是如何發生的,您需要大量的經驗。
所以,總結上面的大混亂
- 域對象不應由Spring(IoC)進行管理,它們不應具有DAO或與基礎結構有關的任何內容
- 域對象具有由休眠(或持久性機制)設置的它們依賴的域對象
- 域對象執行業務邏輯,就像DDD的核心思想一樣,但這不包括數據庫查詢或CRUD –僅對對象內部狀態進行的操作
- 幾乎不需要DTO-在大多數情況下,域對象本身就是DTO(這節省了一些樣板代碼)
- 服務執行CRUD操作,發送電子郵件,協調域對象,基于多個域對象生成報告,執行查詢等。
- 服務(應用程序)層并不薄,但不包括域對象固有的業務規則
- 應避免生成代碼。 應該使用抽象,設計模式和DI來克服代碼生成的需求,并最終–擺脫代碼重復。
參考:有關 領域驅動設計,貧乏領域模型,代碼生成,依賴項注入等的信息 ,請參見Bozho技術博客上的JCG合作伙伴 Bozho。
相關文章 :
- Spring和AspectJ的領域驅動設計
- 在域驅動設計中使用狀態模式
- ORM問題
- 什么是依賴倒置? 是IoC嗎?
- 框架使開發人員愚蠢嗎?
- 每個程序員都應該知道的事情
- JDK中的設計模式
- Java最佳實踐
翻譯自: https://www.javacodegeeks.com/2011/09/on-domain-driven-design-anemic-domain.html