天天看點

IDDD 實作領域驅動設計-一個簡單的 CQRS 示例

學習架構知識,需要有一些功底和經驗,要不然你會和我一樣吃力,CQRS、EDA、ES、Saga 等等,這些是實踐 DDD 所必不可少的架構,是以,如果你不懂這些,是很難看懂上篇所提到的 CQRS Journey 和 ENode 項目,那怎麼辦呢?我們可以從簡單的 Demo 一點一滴開始。

IDDD 實作領域驅動設計-一個簡單的 CQRS 示例
IDDD 實作領域驅動設計-一個簡單的 CQRS 示例

說明:一張很醜陋的圖

CQRS.Sample 所描述的一個流程是 SendMessage(發消息),也就是之前 MessageManager 中的一個業務示例,這個業務流程用到 CQRS 示例中,可能會有些不太準确,或者是有些牽強,但我主要的目的是想做一次 Commond->Event 的過程,熟悉它們到底是怎麼互動的?是以,你檢視代碼的時候,盡量忽略這個業務流程,當然,如果你有針對這個業務流程更好的具體應用,我是非常歡迎交流。

首先,我們根據上面的流程圖一步一步進行,UI 建立一個 Commond,然後交給 CommandBus 進行 Send(發送),也就是下面這段代碼:

項目中所有的類型映射都是通過 IoC 進行注入,ICommandBus 接口定義為:<code>void Send&lt;TCommand&gt;(TCommand cmd) where TCommand : ICommand;</code>,CommandBus 的實作主要是在 Send 方法中,解析 <code>ICommandHandler&lt;TCommand&gt;</code> 注入的類型對象,然後調用 ICommandHandler 接口定義的 Handle 方法,傳入 TCommand 參數對象。

SendMessageCommond 的定義很簡單,主要是一些來自 UI 的參數,是以,我們一般會定義一些屬性字段,有時候我們會進行資料驗證,可以使用 Validate,方法簽名是: <code>IEnumerable&lt;ValidationResult&gt; Validate(ValidationContext validationContext)</code>,不過需要繼承 IValidatableObject 接口,這樣我們就可以在 MVC View 前端進行輸出驗證結果,使用起來非常友善,SendMessageCommond 繼承一個無實作的 ICommand 接口,主要用來限制類型,SendMessageCommond 對應 SendMessageCommondHandler,實作代碼:

CommondHandler 的功能有點類似于經典分層架構中的 Application(應用層),從它的具體實作中,我們就可以看出領域到底在做哪些工作,它的主要工作就是協調這些工作的流程,領域中的代碼我就不貼了,這裡我簡單說明一下,在之前的 SendMessage 實作中,設計了一個 SendMessageService 領域服務,裡面主要進行的工作是驗證收發件人,以及消息是否符合規則,後來我就想,SendMessageService 和它實際進行的工作不相符,發消息所蘊含的實際業務意義,我也一直沒有想明白,但是在具體實作中,發消息所做的工作是驗證,那驗證是不是發消息真正的業務含義呢?是以,稀裡糊塗就有了上面的代碼,VerifySenderService 和 VerifyReceiverService 是用來驗證收發件人資訊的,成功的話就傳回一個 Contact 對象,具體的驗證邏輯可以檢視下實作代碼。

下面說一下 <code>message.Send();</code>,這個可能有很大的問題,在 CQRS Journey 項目中,有很多類似于這樣的操作,就是在 CommondHandler 中,建立一個聚合根對象,然後執行聚合根中的一個行為,比如我搜刮的 <code>order.Confirm()</code>,訂單可以送出自己嗎?消息可以發送自己嗎?這樣做的含義是什麼?其實我也搞不太清楚,在 Message 聚合根中的 Send 方法中,主要是事件的釋出,

先抛開 Send 的合理性,看下 EventBus 是如何 Publish 的?我的實作中和 CommondBus 很相似,但我覺得可能有些問題,Commond 和 CommondHandler 是一一對應關系,我們知道事件釋出和事件訂閱是一對多關系,也就是說一個事件可能有很對的訂閱者,這些訂閱者所處理的過程可能會有些不同,比如使用者注冊的一個事件,可能會有郵件通知訂閱者,也可能會有統計資料更新訂閱者,在 CQRS Journey 項目中,EventBus 的 Publish 大概是這樣實作的:

而我的實作則是和 CommondBus 一樣,都是用 IoC 注入的,是以肯定有問題,我們來看下 MessageSentEventHandler 中的代碼:

消息發送之後,進行持久化操作,然後再進行郵件通知,這樣一整個 SendMessage 的流程就走完了。

這個流程中,隻有 CQRS 的 Commond,并沒有 Query,也沒有 ES、Saga 的概念,主要是它們太深奧了,我搞不來。CQRS.Sample 隻是一個簡單的示例,發消息的業務含義所表達的可能不是很準确,本來是想用使用者注冊、訂單送出業務示例,但還是想想用發消息進行嘗試下,除去 EventBus 的實作有問題,CQRS 的簡化版基本上都能表現出來了。

另外,從簡單分層架構改造成 CQRS 确實有很多挑戰,但有時候想想,領域模型都設計的有問題,那用什麼架構實作都毫無意義,如果在現有的項目中,你發現經典分層架構實作起來有很多别扭的地方,比如 Domain 引用了 DTO,你可以嘗試先把 Repositories 進行分離下,建立一個 Query 項目,把一些無業務邏輯的查詢發到裡面(主要是應用層調用的),這樣使 Repositories 更加簡化,如果可以簡化成隻有一個 GetById 方法,那麼就達到 CQRS 的标準了,因為 Repositories 的接口定義在領域層,因為 Query 項目的分離,是以,Domain 就可以去除 DTO 的引用了,應用層也就直接調用 Query,這隻是一個中和方案。

領域模型需要一點一滴設計,架構也需要一點一滴設計,但後者需要建立在前者的基礎上。

一個對我非常有幫助的 CQRS 系列(初級):

<a href="http://www.cnblogs.com/mouhong-lin/archive/2012/03/23/what-is-cqrs.html">CQRS實踐(1): 什麼是CQRS</a>

<a href="http://www.cnblogs.com/mouhong-lin/archive/2012/03/28/cqrs-command.html">CQRS實踐(2): Command的實作</a>

<a href="http://www.cnblogs.com/mouhong-lin/archive/2012/03/29/cqrs-command-execution-result.html">CQRS實踐(3): Command執行結果的傳回</a>

<a href="http://www.cnblogs.com/mouhong-lin/archive/2012/05/24/cqrs-domain-event.html">CQRS實踐(4): 領域事件</a>

本文轉自田園裡的蟋蟀部落格園部落格,原文連結:http://www.cnblogs.com/xishuai/p/iddd-cqrs-samlpe.html,如需轉載請自行聯系原作者