CQRS與CURD

淺談命令查詢職責分離(CQRS)模式

在常用的三層架構中,通常都是通過數據訪問層來修改或者查詢數據,一般修改和查詢使用的是相同的實體。在一些業務邏輯簡單的系統中可能沒有什麼問題,但是隨着系統邏輯變得複雜,用戶增多,這種設計就會出現一些性能問題。雖然在DB上可以做一些讀寫分離的設計,但在業務上如果在讀寫方面混合在一起的話,仍然會出現一些問題。

本文介紹了命令查詢職責分離模式(Command Query Responsibility Segregation,CQRS),該模式從業務上分離修改 (Command,增,刪,改,會對系統狀態進行修改)和查詢(Query,查,不會對系統狀態進行修改)的行爲。從而使得邏輯更加清晰,便於對不同部分進行針對性的優化。文章首先簡要介紹了傳統的CRUD方式存在的問題,接着介紹了CQRS模式,最後以一個簡單的在線日記系統演示瞭如何實現CQRS模式。要談到讀寫操作,首先我們來看傳統的CRUD的問題。

一 CRUD方式的問題

在以前的管理系統中,命令(Command,通常用來更新數據,操作DB)和查詢(Query)通常使用的是在數據訪問層中Repository中的實體對象(這些對象是對DB中表的映射),這些實體有可能是SQLServer中的一行數據或者多個表。

通常對DB執行的增,刪,改,查(CRUD)都是針對的系統的實體對象。如通過數據訪問層獲取數據,然後通過數據傳輸對象DTO傳給表現層。或者,用戶需要更新數據,通過DTO對象將數據傳給Model,然後通過數據訪問層寫回數據庫,系統中的所有交互都是和數據查詢和存儲有關,可以認爲是數據驅動(Data-Driven)的,如下圖:

 Traditional CRUD Architecture

對於一些比較簡單的系統,使用這種CRUD的設計方式能夠滿足要求。特別是通過一些代碼生成工具及ORM等能夠非常方便快速的實現功能。

但是傳統的CRUD方法有一些問題

  • 使用同一個對象實體來進行數據庫讀寫可能會太粗糙,大多數情況下,比如編輯的時候可能只需要更新個別字段,但是卻需要將整個對象都穿進去,有些字段其實是不需要更新的。在查詢的時候在表現層可能只需要個別字段,但是需要查詢和返回整個實體對象。
  • 使用同一實體對象對同一數據進行讀寫操作的時候,可能會遇到資源競爭的情況,經常要處理的鎖的問題,在寫入數據的時候,需要加鎖。讀取數據的時候需要判斷是否允許髒讀。這樣使得系統的邏輯性和複雜性增加,並且會對系統吞吐量的增長會產生影響。
  • 同步的,直接與數據庫進行交互在大數據量同時訪問的情況下可能會影響性能和響應性,並且可能會產生性能瓶頸。
  • 由於同一實體對象都會在讀寫操作中用到,所以對於安全和權限的管理會變得比較複雜。

這裏面很重要的一個問題是,系統中的讀寫頻率比,是偏向讀,還是偏向寫,就如同一般的數據結構在查找和修改上時間複雜度不一樣,在設計系統的結構時也需要考慮這樣的問題。解決方法就是我們經常用到的對數據庫進行讀寫分離。 讓主數據庫處理事務性的增,刪,改操作(Insert,Update,Delete)操作,讓從數據庫處理查詢操作(Select操作),數據庫複製被用來將事務性操作導致的變更同步到集羣中的從數據庫。這只是從DB角度處理了讀寫分離,但是從業務或者系統上面讀和寫仍然是存放在一起的。他們都是用的同一個實體對象。

要從業務上將讀和寫分離,就是接下來要介紹的命令查詢職責分離模式。

二 什麼是CQRS

CQRS最早來自於Betrand Meyer(Eiffel語言之父,開-閉原則OCP提出者)在 Object-Oriented Software Construction 這本書中提到的一種 命令查詢分離 (Command Query Separation,CQS) 的概念。其基本思想在於,任何一個對象的方法可以分爲兩大類:

  • 命令(Command):不返回任何結果(void),但會改變對象的狀態。
  • 查詢(Query):返回結果,但是不會改變對象的狀態,對系統沒有副作用。

根據CQS的思想,任何一個方法都可以拆分爲命令和查詢兩部分,比如:

private int i = 0;
private int Increase(int value)
{
    i += value;
    return i;
}

這個方法,我們執行了一個命令即對變量i進行相加,同時又執行了一個Query,即查詢返回了i的值,如果按照CQS的思想,該方法可以拆成Command和Query兩個方法,如下:

private void IncreaseCommand(int value)
{
    i += value;
}
private int QueryValue()
{
    return i;
}

操作和查詢分離使得我們能夠更好的把握對象的細節,能夠更好的理解哪些操作會改變系統的狀態。當然CQS也有一些缺點,比如代碼需要處理多線程的情況。

CQRS是對CQS模式的進一步改進成的一種簡單模式。 它由Greg Young在CQRS, Task Based UIs, Event Sourcing agh! 這篇文章中提出。“CQRS只是簡單的將之前只需要創建一個對象拆分成了兩個對象,這種分離是基於方法是執行命令還是執行查詢這一原則來定的(這個和CQS的定義一致)”。

CQRS使用分離的接口將數據查詢操作(Queries)和數據修改操作(Commands)分離開來,這也意味着在查詢和更新過程中使用的數據模型也是不一樣的。這樣讀和寫邏輯就隔離開來了。

A basic CQRS architecture

使用CQRS分離了讀寫職責之後,可以對數據進行讀寫分離操作來改進性能,可擴展性和安全。如下圖:

A CQRS architecture with separate read and write stores

主數據庫處理CUD,從庫處理R,從庫的的結構可以和主庫的結構完全一樣,也可以不一樣,從庫主要用來進行只讀的查詢操作。在數量上從庫的個數也可以根據查詢的規模進行擴展,在業務邏輯上,也可以根據專題從主庫中劃分出不同的從庫。從庫也可以實現成ReportingDatabase,根據查詢的業務需求,從主庫中抽取一些必要的數據生成一系列查詢報表來存儲。

reportingDatabase

使用ReportingDatabase的一些優點通常可以使得查詢變得更加簡單高效:

  • ReportingDatabase的結構和數據表會針對常用的查詢請求進行設計。
  • ReportingDatabase數據庫通常會去正規化,存儲一些冗餘而減少必要的Join等聯合查詢操作,使得查詢簡化和高效,一些在主數據庫中用不到的數據信息,在ReportingDatabase可以不用存儲。
  • 可以對ReportingDatabase重構優化,而不用去改變操作數據庫。
  • 對ReportingDatabase數據庫的查詢不會給操作數據庫帶來任何壓力。
  • 可以針對不同的查詢請求建立不同的ReportingDatabase庫。

當然這也有一些缺點,比如從庫數據的更新。如果使用SQLServer,本身也提供了一些如故障轉移和複製機制來方便部署。

三 什麼時候可以考慮CQRS

CQRS模式有一些優點:

  1. 分工明確,可以負責不同的部分
  2. 將業務上的命令和查詢的職責分離能夠提高系統的性能、可擴展性和安全性。並且在系統的演化中能夠保持高度的靈活性,能夠防止出現CRUD模式中,對查詢或者修改中的某一方進行改動,導致另一方出現問題的情況。
  3. 邏輯清晰,能夠看到系統中的那些行爲或者操作導致了系統的狀態變化。
  4. 可以從數據驅動(Data-Driven) 轉到任務驅動(Task-Driven)以及事件驅動(Event-Driven).

在下場景中,可以考慮使用CQRS模式:

  1. 當在業務邏輯層有很多操作需要相同的實體或者對象進行操作的時候。CQRS使得我們可以對讀和寫定義不同的實體和方法,從而可以減少或者避免對某一方面的更改造成衝突
  2. 對於一些基於任務的用戶交互系統,通常這類系統會引導用戶通過一系列複雜的步驟和操作,通常會需要一些複雜的領域模型,並且整個團隊已經熟悉領域驅動設計技術。寫模型有很多和業務邏輯相關的命令操作的堆,輸入驗證,業務邏輯驗證來保證數據的一致性。讀模型沒有業務邏輯以及驗證堆,僅僅是返回DTO對象爲視圖模型提供數據。讀模型最終和寫模型相一致。
  3. 適用於一些需要對查詢性能和寫入性能分開進行優化的系統,尤其是讀/寫比非常高的系統,橫向擴展是必須的。比如,在很多系統中讀操作的請求時遠大於寫操作。爲適應這種場景,可以考慮將寫模型抽離出來單獨擴展,而將寫模型運行在一個或者少數幾個實例上。少量的寫模型實例能夠減少合併衝突發生的情況
  4. 適用於一些團隊中,一些有經驗的開發者可以關注複雜的領域模型,這些用到寫操作,而另一些經驗較少的開發者可以關注用戶界面上的讀模型。
  5. 對於系統在將來會隨着時間不段演化,有可能會包含不同版本的模型,或者業務規則經常變化的系統
  6. 需要和其他系統整合,特別是需要和事件溯源Event Sourcing進行整合的系統,這樣子系統的臨時異常不會影響整個系統的其他部分。

但是在以下場景中,可能不適宜使用CQRS:

  1. 領域模型或者業務邏輯比較簡單,這種情況下使用CQRS會把系統搞複雜。
  2. 對於簡單的,CRUD模式的用戶界面以及與之相關的數據訪問操作已經足夠的話,沒必要使用CQRS,這些都是一個簡單的對數據進行增刪改查。
  3. 不適合在整個系統中到處使用該模式。在整個數據管理場景中的特定模塊中CQRS可能比較有用。但是在有些地方使用CQRS會增加系統不必要的複雜性。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章