CQRS---什麼是CQRS

什麼是CQRS?

這個問題網上能夠找到不少資料,未接觸過的童鞋請先查看Udi DahanGrey YoungRinat Abdullin園子裏dax.net,以及Jdon社區上的相關文章。html

例以下面幾篇文章:數據庫

1. http://www.cnblogs.com/daxnet/archive/2011/01/06/1929099.html框架

2. http://www.udidahan.com/2009/12/09/clarified-cqrs/異步

3. http://www.jdon.com/jivejdon/thread/37891性能

這裏只經過Udi Dahan的《Clarified CQRS》文章中的一張圖片簡要介紹一下:優化

UI上有兩種類型的操做:命令和查詢,例如顯示銷量最好的5個產品就屬於查詢,而提交一個訂單、修改密碼等則屬於命令。由於大部分系統都是讀多寫少,並且業務邏輯基本都出如今寫入的一端,因此查詢和命令的分離可讓咱們獨立的去優化查詢。spa

查詢 (Query)

上圖中,能夠看到Query不是經過DB來查詢,而是經過一個專門用於查詢的Read DB(上圖中的Cache,它不必定是數據庫,但爲方便起見,下面統稱Read DB),Read DB中的表(方便起見,暫且認爲這個Read DB是一個RDBMS)是專門針對UI優化過的,例如裏面可能會有LatestProductListModel(ProductId, ProductName, Price, BrandName, AddedTime)、BestSoldProductListModel(ProductId, ProductName, TotalSold)這樣的表,分別表示最新的產品列表,銷量最好的產品列表(它們其實就至關因而View Model)。LatestProductListModel中有一個BrandName的字段,注意,不是BrandId,所以,對於界面中的查詢,幾乎全均可以經過SELECT * FROM [TABLE]這樣的SQL語句來實現,可能有少數Where,但基本沒有Join,這對於界面的加載速度絕對是有利無弊的(其實也是在用空間換時間)。.net

命令 (Command)

業務邏輯大部分都發生在寫入的時候,例如用戶購買商品提交訂單時,咱們要驗證庫存,用戶信息訂單數據是否有效等。若是從傳統DDD的角度看,Command相似於Application Service,用戶的命令(如提交訂單)會以Command的形式獲得執行,而Command中也不會帶有業務邏輯,Command中作的事情基本上是:經過Repository獲得相關的領域對象,調用某些領域服務(Domain Service)執行一些操做(業務邏輯都將保留在領域模型中),而後執行Commit或SaveChanges之類的方法提交改動,以後,相關的數據就會寫入到Write DB中(圖的DB,下文統稱Write DB)。須要注意的是,UI上的查詢都是查Read DB,而不是Write DB。code

領域模型 (Domain Model)

這和Evans的DDD中說的領域模型沒有太多區別,是「the heart of software」。orm

領域事件 (Domain Event)

領域事件佔據的地位很是重要,不只限於CQRS。相信會有一部分人曾和我同樣碰到過這樣的問題:

Account實體(表示賬戶)有個Balance屬性(表示賬戶餘額),咱們通常不會公開這個屬性的setter,而是經過寫一些IncreaseBalance(decimal amount)之類的方法來實現賬戶餘額的變更。

這時問題就來了,咱們想在賬戶變更時添加一條AccountLog記錄,但Log記錄成千上萬,咱們不能直接經過ORM的一對多映射把AccountLog集合實現成Account的一個集合屬性,那咱們就須要在IncreaseBalance()中獲得AccountLogRepository,這樣纔有辦法插入AccountLog(從DDD的角度,AccountLog不是聚合根,因此不能有AccountLogRepository,但在性能影響嚴重的時候,也只好作些取捨了)。

無論用了依賴注入仍是什麼的,總之,Account已經依賴上Repository了,這就讓領域對象變得很不純淨,而且,假如咱們之後不只要記錄log,還要短信通知用戶呢?那要修改源代碼嗎?這也很不OCP。

而領域事件正好能夠解決這種問題:只要在IncreaseBalance()方法的末尾,觸發一個領域事件,而後咱們獨立寫一個EventHandler的類去實現log的添加(框架能夠保證EventHandler能夠和領域事件綁定到一塊兒)。

回到CQRS,由於Command將數據寫到了Write DB中,而UI查詢的是Read DB,那咱們就須要用某種方式實現這兩個數據庫的同步,解決辦法已經很明顯了,寫一堆的EventHandler類去監聽領域事件。例如咱們有一個更改產品價格的命令ChangePriceCommand,它執行後,一個叫作PriceChangedEvent會被觸發,那咱們只要寫一個PirceChangedEventHandler的類,在這裏面將Read DB中相關的價格信息更改到最新值便可實現同步(這裏會涉及到Read DB中表結構改變的問題,後面再說)。

結語

CQRS有意思的地方還不僅這些,還有常和CQRS一塊兒討論的Event Sourcing(事件溯源,下面簡稱ES)等。

總得來講,CQRS看起來很迷人,但在本身的實踐過程當中,碰到了各類各樣的問題,尤爲ES,這幾乎顛覆了日常的開發思惟。例如,使用了ES後,領域模型只能經過Id來查詢,若是你想查詢姓名爲「水哥」的用戶,是作不到的,由於不會存在一個叫作User的表。相信大部分剛接觸ES的朋友都會對此感到不適應。這須要思惟上的改變。

後續的幾篇文章裏,我會繼續分享本身在CQRS實踐過程當中碰到的各類感受比較典型的問題以及我目前能找到的最好方案(更但願到時有童鞋有更好的方案分享)。而後經過實現一個迷你型的CQRS框架以及基於其開發的一個BookStore示例項目來展現CQRS所帶來的好處。

這個迷你框架和示例項目中將會對常討論的CQRS進行簡化,剔除掉我的感受和日常開發跨度比較大的東西,例如ES,異步Command等,同時還會針對日常習慣的開發方案作一些取捨,例如UI中能夠根據須要混合查詢Read DB和Write DB(前提是在Write DB的查詢也很簡單的狀況下,好比一樣只須要一個SELECT)。

相關文章
相關標籤/搜索