經典DDD分層中領域層領域實體自身高內聚,在領域層內經過聚合,實體,事件和倉儲接口完成領域業務邏輯。
最近在實際項目中遇到一種場景,有以下所示實體Device:網絡
public class Device { void ExecuteCmd(string cmd) { //Do something "Domian" related before sending.. //Send command to real device via network } }
其中ExecuteCmd方法須要首先將指令經過網絡發送到實際設備。因爲領域實體自身不能完成諸如Email,網絡操做等實際業務功能,故該功能只能經過某種服務完成。可是執行這個動做和其結果,流程卻屬於領域部分,故不能所有交由應用層來實現,不然將致使部分業務邏輯從領域層泄露。
爲了解決這個問題,前後嘗試了三種方式:ide
public Interface IDeviceService { void Execute(string cmd); }
public class Device { private IDeviceAgent _agent = ServiceLocator.GetInstance<IDeviceService>(); void ExecuteCmd(string cmd) { //Do something "Domian" related before sending.. //invoke agent _agent.Execute(cmd); } }
該模式最簡單,但倒是DDD中的反模式,形成的後果是使得實體再也不「純淨」,須要依賴一個服務接口,這樣將給單元測試帶來麻煩,而且容忍了這種方式以後,極有可能形成後續將Repository接口等注入實體,使得領域實體進一步腐化。post
該方式下在須要定義領域事件,而在事件處理器中注入服務接口。此方法符合DDD要求,但在經典DDD分層項目中顯得比較繁瑣,須要頻繁定義細粒度事件及事件處理器。而且調用依賴領域服務總線實現,總線消息實現爲延遲傳遞則會帶來必定問題。一樣的,在這種方式下,單元測試也有必定的麻煩。單元測試
Chassaging在這篇文章測試
If I have mail message entity and I want a Send method on it ?
Tiding your entity to the service that will use it is not conceptually satisfying. The
server >that will send the message is a medium and is not part of the entity itself.
It’s seems better to call server.Send(message).
The problem is that you can end breaking the tell don’t ask principle because the Send
method will ask the message for each property value. And you can be tempted to put
computations that should be in the message entity inside the Send method.spa
Let’s call Double Dispatch to the rescue !code
- Design a server interface presenting a Send method that take the parameters it need (title, recipients, message body⋯)
- Add a SendThrough method to your entity that takes the server interface as parameter
- Implement SendTrhough so that it computes values to pass to the Send method.
That’s it.server
簡單來講,就是定義一個服務接口及其所需的參數,而後在實體中添加一個ExecuteThrough方法,將參數和接口傳入,在基礎層等實現該接口:接口
public class Device { public void ExecuteCmdThrough(string cmd, IDeviceService service) { //Do something "Domian" related before sending.. //Dispatch behaviors to interface service.Execute(cmd); } }