談DDD與貧血領域模型:再次為失血模型辯護 -Codecentric AG部落格

banq發表於2019-10-19

在討論如何在應用DDD時如何最好地實現我們的領域物件(最近變得越來越流行)的討論中,一位同事向我指出了Martin Fowler關於Anemic Domain Models的文章(2003年)。馬丁是我的英雄之一。我嘗試閱讀儘可能多的著作,並儘可能多地觀看他的演講,因為它們給了我深刻的見解和對所有可用選項的平衡理解,我認為這對於做出明智的軟體設計決策至關重要。在前面提到的文章中,馬丁使用了比較固執的語言來表示“貧血領域模型”是一種反模式。

現在,在2019年,即使多次重讀這篇文章,我也很難將其視為普遍存在的問題。因此,我認為提出我的觀點,即使這可能會擾亂DDD社群。

什麼是貧血或失血領域模型?

在Martin開創性的EAA書(2002年)中,領域模型被定義為“結合了行為和資料的領域的物件模型”。這顯然將其與實體物件區分開來,實體物件僅是儲存在資料庫中的資料的物件表示(關係型與否),而行為位於單獨的類中。請注意,這種劃分並不是真正的分層,而只是處理純資料結構的過程。這是在像C這樣的過程語言中工作的唯一方法。

因此,撇開不同的術語並簡化事情,Anemic Domain Model基本上只是使用純資料實體類的另一個詞,其行為提取為我們可以稱為操作行為的類

純實體+操作有什麼問題?

Martin承認他看到的主要痛苦是“對映到資料庫的笨拙”。他認為只有在被對映的物件實際利用其潛力的情況下,這才是值得的。

但是,有兩個因素可能使他的前提無效:(a)諸如JPA之類的ORM工具已經得到了很大的改進;您還可以將物件及其依賴項作為文件直接儲存到例如MongoDB中。實際上,直接將實體對映到資料庫或從資料庫對映實體比直接使用JDBC結果集或手動對映資料要容易得多,也更清潔。

(b)更重要的是,如今,資料庫模型通常僅由單個應用程式控制;幸運的是,團隊通常通過與其他團隊共享表進行資料庫整合。因此,如果域模型需要發展,則無需進行任何協調就可以遷移資料庫模式。我們可以使用統一的甚至是普遍存在的模型。

實體可以是真實的域物件嗎?

很自然地可以向實體新增Bean驗證約束:例如,您可以簡單地向實體欄位新增@Past註釋,並且只要要儲存它,驗證就會自動完成。這可以看作是您用於例如的型別系統的擴充套件。無需額外的程式碼。

您還可以建立自定義約束,例如@Name,防止人們名字中出現笑臉和其他不適當的字元;您需要編寫的驗證器也是純域邏輯程式碼。Bean驗證程式可以處理相當大的複雜性:您可以(a)組合驗證多個欄位;(b)使用驗證組,例如用於實體的不同狀態;(c)為儲存庫和其他注入幫助程式類。例如,這使您可以檢查房地產經紀人是否具有出售特定建築物的法律要求的證書,具體取決於建築物的位置和其他因素。

JPA實體類還可以具有其他域方法,例如,我們的Person類可以具有一種getAge從出生開始計算人員當前年齡的方法。或建立Customer物件可能會自動設定首次註冊日期。

某些域方法需要儲存庫或其他幫助程式;例如,當(重新)計算訂單金額時,我們可能必須獲取商品價格。我們可以將我們需要的所有幫助者注入我們的實體中 ; 但是,將複雜的互動從Entity移入Operation類是一個很好的Clean Code習慣;尤其是 如果涉及多個實體;這樣,測試也變得更加容易。

請注意,這些操作類仍然是域模型的一部分:它們應該是純域邏輯,並且可能與實體位於同一包中。只有呼叫是不同的:沒有呼叫order.calculateTotalPrice(),而是有一個操作行為OrderOperations類用於呼叫calculateTotalPrice(order)。

應用邏輯

全面的業務交易或事務有時會變得非常複雜。例如,關閉保險銷售可能必須觸發許多其他過程;我們甚至可能必須跨越多個有界上下文。通常,這通常被正確地提取到單獨的應用程式層(有時稱為服務層)中,該應用程式層“僅協調任務並委託工作”。您可以將其稱為“ 事務指令碼”。但是核心領域和應用程式邏輯之間的界線通常是模糊的,並且不同的人將其繪製在不同的位置。

構建前端後端時,前端必須顯示其他資料,例如,將商品新增到訂單中時的等級,這顯然是應用程式邏輯;還是應該堅持每個步驟的多步驟建立操作,也顯然是應用程式邏輯:決策是關於UX的,它由定義為表現層的視覺化決策來完成:

Web應用程式區別於桌面應用程式介面,有不同針對終端的介面,但它可能具有相同的應用程式邏輯[ 6 ];應用程式邏輯可以通過subcutaneous tests輕鬆進行單元測試,而測試表現層則需要進行嚴格的整合設定。

Martin引用了Eric Evans的宣告,即應用程式層“不包含業務規則或知識”。但是,業務交易中所涉及流程的協調不是業務領域知識嗎?它不執行業務規則嗎?我發現很難憑空得出應用程式和領域邏輯之間的區別。我更喜歡將諸如圖層之類的設計工件的建立推遲到出現特定需求之前。(banq:作者將包含業務規則 與使用呼叫指揮業務規則 兩個意思混淆,交通警察可以指揮交通,但是不必親自開車,做協調的職責不必親自參與某事的實作)

將資料與行為分離會導致人們在資料庫一側定義業務約束,這是一種過時思維,胖前端應用程式可以做這些工作。我可以理解為什麼像Martin這樣的物件導向的純粹主義者會忽略這種方法(在資料庫定義約束的方法)。但是它也有其優勢:當我們僅分離複雜的行為時,可能不清楚我們應該在哪裡尋找特定的操作。我們需要其他約定來定義何時做某事以及何時做另一件事。當我們從資料中分離出所有行為(驗證邏輯以及計算和複雜的操作)時,事情就變得更加統一,並且可能更容易掌握和測試!

而且這絕不是一成不變的:進入網際網路規模後,可能會導致諸如SOLID  專案之類的事情,在此  專案中,您的個人資料(圖片,部落格等)與您使用的應用程式嚴格分開(Facebook,Twitter等),因此您可以輕鬆切換到其他應用程式而不會丟失資料。這不僅是關於重新實現相同型別的應用程式;這是關於處理相同資料的不同型別的應用程式的。沒有貧血域模型,這將是不可能的。(banq注:針對相同資料使用不同型別程式去處理,這是技術架構,不是模型設計,作者將技術模型與業務模型混淆,技術模型可以採取貧血模型,但是業務模型必須直接翻譯業務領域的情況,不能將行為與資料分離,人們的思考方式很多是先名詞後動詞的方式)。

結論

我同意,過早地從失血域模型開始設計並將其宣告為最佳實踐是一種壞味道;但我不同意OO純度是獲得域模型資格的硬性要求。將操作類與實體類分開是一種有效的解決方案。只需將它們放在相同的“域模型”層中即可!有時有令人信服的理由將邏輯與資料分開;有時最好不要這樣做。軟體開發人員必須(使)能夠(自己)看到何時最好做一個或另一個。並且,他們應該知道如何使用bean驗證。

命名事物是電腦科學中的難事之一,它的確很重要。有人可能會爭辯說,當物件貧血時,我們不應稱其為領域模型。但是說DDD僅適用於純OO範例,不僅排除並否認了上述實用的方法,而且還排除了例如函數語言程式設計。那將是巨大的損失。我認為,Anemic失血域模型仍然可以稱為適當的域模型-將它作為有效的工具帶在您的腰帶中是很好的。

 

相關文章