2011-09-05 61 views
17

儘管現在已經研究了Domain Driven Design很長時間,但仍然有一些基本知識我只是想到了。將現實世界的邏輯放入DDD域層時遇到困難

似乎每一次我嘗試設計了豐富的domain layer的時候,我還需要很多的Domain Services或厚Application Layer,我結束了在他們沒有真正的邏輯一堆近貧血域的實體,除了從「GetTotalAmount」等等。關鍵問題是實體不知道外部的東西,並且向實體注入任何東西是不好的做法。

讓我舉一些例子:

1.用戶註冊的服務。用戶被保存在數據庫中,生成並保存文件(用戶帳戶需要),併發送確認電子郵件。

確認電子郵件的例子已在其他主題中進行了大量討論,但沒有真正的結論。一些人建議將邏輯放在application service中,EmailServiceFileServiceinfrastructure layer中注入。但是,我會在域之外擁有業務邏輯,對吧?其他建議建立一個domain service是可以獲得infrastructure services注入 - 但在這種情況下,我需要有domain layerIEmailServiceIFileService)內infrastructure services的界面看起來並不太好或者(因爲domain layer不能引用infrastructure layer) 。另一些人建議實施Udi Dahan's Domain Events,然後讓EmailService和FileService訂閱這些事件。但是這似乎是一個非常鬆散的實現 - 如果服務失敗會發生什麼?請讓我知道你認爲這是正確的解決方案。

2.一首歌是從數字音樂商店購買的。購物車被清空。購買依然存在。支付服務被調用。發送電子郵件確認。

好的,這可能與第一個例子有關。這裏的問題是,誰負責編排這筆交易?當然,我可以通過注入服務將所有內容放入MVC控制器中。但是如果我想要真正的DDD,所有的業務邏輯都應該在域中。但是哪個實體應該有「購​​買」方法? Song.Purchase()Order.Purchase()OrderProcessor.Purchase()(域名服務)? ShoppingCartService.Purchase()(應用服務?)

這是一種情況,我認爲在域實體內使用真正的業務邏輯非常困難。如果向實體注入任何東西都不是好習慣,他們怎麼能做其他事情而不是檢查自己(及其總體)的狀態?

我希望這些例子足夠清楚地表明我正在處理的問題。

+0

DDD建議使「文件」和「電子郵件」實體成爲域的一部分。當相應的實體出現在域層中時,基礎設施負責實際生成文件並實際發送電子郵件。 – Lightman

回答

8

用戶註冊一項服務。用戶被保存在 數據庫中,生成並保存(用戶帳戶需要)文件 併發送確認電子郵件。

您可以在這裏申請Dependency Inversion Principle。定義一個域接口這樣的:

void ICanSendConfirmationEmail(EmailAddress address, ...) 

void ICanNotifyUserOfSuccessfulRegistration(EmailAddress address, ...) 

接口可以被其他域類使用。使用真正的SMTP類在基礎設施層實現此接口。在應用程序啓動時注入此實現。通過這種方式,您在域代碼中陳述了業務意圖,而您的域邏輯沒有直接引用SMTP基礎結構。這裏的關鍵是界面的名稱,它應該基於無處不在的語言。

一首歌曲是從數字音樂商店購買的。購物車 已清空。購買依然存在。支付服務被調用。 發送電子郵件確認。好的,這可能與第一個例子有關。這裏的問題是,誰負責編排這筆交易?

使用OOP最佳實踐來分配責任(GRASP和SOLID)。單元測試和重構會給你一個設計反饋。編排本身可以是應用層的一部分。從DDD Layered Architecture

應用層:定義軟件應該做的工作,並指示 表現域對象制定的問題。這個層 負責的任務對於業務來說是有意義的或者對於與其他系統的應用層進行交互所需的 。

此圖層保持精簡。它不包含業務規則或知識,但僅協調任務並將工作委託給 下一層下的域對象的協作。它不具有 具有反映業務情況的狀態,但它可以具有反映用戶或程序的任務進度的狀態 。

+0

謝謝!與域接口的好主意。在檢查了更多DDD示例之後,我確信我不應該擔心在應用程序層中放置某些業務邏輯 - 例如管理調用域對象的順序。 – lasseschou

+0

嗨德米特里,我有一個問題域接口。你會建議直接在域實體上注入域接口的實現嗎? (這是一個很好的做法嗎?) - 或者你會創建一個帶有注入實現的域服務UserService,然後從應用層調用該服務('UserService.SendConfirmationEmail()')?我仍然不太清楚如何實施這個解決方案。 – lasseschou

11

Dimitry的答案指出了一些好的事情,尋找。通常/很容易,您會發現自己處於您的場景中,數據通過不同層從數據庫鏟到GUI。

我受到了Jimmy Nilsson的簡單建議「價值對象,價值對象和更多價值對象」的啓發。通常人們傾向於將注意力集中在名詞上,並將它們塑造成實體。當然,你經常遇到尋找DDD行爲的麻煩。動詞更容易與行爲相關聯。一件好事是讓這些動詞作爲Value對象出現在您的域中。

一些指導我嘗試開發域時用於自我(必須說,構建一個豐富的域需要時間,通常需要重構幾次迭代...):

  • 最小化屬性(get /套)
  • 使用值對象,就像你可以
  • 公開爲小即可。使您的域聚合方法直觀。

不要忘記,您的域可以通過驗證豐富。只有你的域名知道如何進行購買,以及需要什麼。

當您的實體從一個狀態轉換到另一個狀態(工作流程驗證)時,您的域也應負責驗證。

我給你一些例子: 下面是一篇關於你對貧血的域名問題,我在我的博客中寫道:http://magnusbackeus.wordpress.com/2011/05/31/preventing-anemic-domain-model-where-is-my-model-behaviour/

我也真的建議吉米·博加德的博客文章關於實體驗證和使用驗證模式一起用擴展方法。它使您可以自由地驗證基礎設施,而不會讓域名變得骯髒: http://lostechies.com/jimmybogard/2007/10/24/entity-validation-with-visitors-and-extension-methods/

我使用Udi的域名事件取得了巨大的成功。如果您認爲自己的服務可能會失敗,那麼也可以使它們異步。你也把它包裝在一個事務中(使用NServiceBus框架)。

在你的第一個例子中(現在只是集思廣益,讓我們的思想更多地考慮價值對象)。

  1. MusicService.AddSubscriber(User newUser)應用服務得到演示/控制器/ WCF與新用戶通話。 該服務已獲得IUserRepositoryIMusicServiceRepository注入ctor。
  2. 音樂服務「Spotify」通過IMusicServiceRepository加載
  3. 實體musicService.SignUp(MusicServiceSubscriber newSubsriber)方法需要Value對象MusicServiceSubscriber。 此Value對象必須在ctor (值對象不可變)中攜帶用戶和其他強制對象。在這裏,您還可以放置邏輯/行爲,如處理subscriptionId等。
  4. 什麼SignUp方法也會這樣做,它會觸發域事件NewSubscriberAddedToMusicService。 它被EventHandler HandleNewSubscriberAddedToMusicServiceEvent捕獲,其中IFileServiceIEmailService注入到它的ctor中。該處理程序的實現位於應用程序服務層,但該事件由Domain和MusicService.SignUp控制。這意味着該域名處於控制之中。事件處理程序創建文件併發送電子郵件。

您可以通過事件處理程序持久化用戶或使MusicService.AddSubscriber(...)方法達到此目的。兩者都會通過IUserRepository這樣做,但這是一個有趣的問題,也許它將如何反映實際的域名。

終於......我希望你能夠把握上述的東西......無論如何。最重要的是開始爲實體添加「動詞」方法並進行協作。您也可以在您的域中擁有未持久化的對象,它們僅用於在多個域實體之間進行調解,並且可以託管算法等。

+0

嗨 - 感謝你們所有偉大的創意。太糟糕了,我只能標記一個答案,因爲這個帖子引導我朝着正確的方向發展,就像德米特里一樣。只有一個問題 - 如果您使用的是域名事件,甚至可能是異步的 - 您如何處理電子郵件通知無法發送的情況?這只是在事件處理程序中的一些自定義異常處理? – lasseschou

+0

如果你使用NServiceBus作爲平臺,它使用MSDTC事務,所以如果你的代碼失敗了,這個消息將會回滾(我猜)。但是,如果一切正常,並且你的郵件隊列中,然後由於郵件服務器故障SMTP服務器超時......好吧。然後,您必須捕獲該錯誤消息並通知用戶郵件失敗。但NserviceBus會嘗試發送此電子郵件5(默認5)次。然後你可以讓它向錯誤隊列報告錯誤。然後,您可以處理該錯誤消息並通知用戶失敗。 –

+0

好的,謝謝Magnus。來自丹麥的Heja! – lasseschou