2018年5月28日

CQRS: Command Query Responsibility Separation

CQS (Command Query Separation) 是由 Bertrand Meyer (Eiffel 語言的爸爸) 在 1988 於 "Object Oriented Software Construction" 這本書中提出的軟體架構概念,所有的 computing method 只會分成兩類,一種是執行某個 action 的 command,另一種是呼叫查詢並取得回傳資料,但不應該同時做兩種工作。換句話說,發問時,不能在該 method 裡面修改答案。

CQRS (Command Query Responsibility Separation) 應用 CQS 的概念,進一步將 Query 及 Command 物件分離,分別處理取得資料及修改資料的工作。

CQS 遇到的問題,會是 re-entrant 及 multi-thread,就是當一件工作處理到一半,被新的工作中斷這樣的問題,也就是 thread-safe 的問題,也就造成實作複雜度的問題。

然而這樣的問題,搭配著 Event Sourcing 的方法,將某個 APP state 的變更,收集成一個 sequence of events,以這種方式處理 command action,就能解決 thread-safe 的問題。

不過我們還是會遇到,command action 執行的速度跟 query 的時機點的問題,如果修改資料的動作在 query 以前還沒有完成,那麼前端就會查詢到舊狀態的資料,但資料還是會達到最終一致性,而不會有強一致性。

CQRS 的優點:

  1. Command 及 Query 分工明確,可分別進行效能調整及最佳化
  2. 將業務上的命令和查詢的職責分離能夠提高系統的性能、可擴展性和安全性
  3. 企業邏輯簡單清楚,能夠從事件歷程看到系統中的那些行為或者操作導致了系統的狀態變化。
  4. 將開發的邏輯概念,從數據驅動 (Data-Driven) 轉到任務驅動 (Task-Driven) 以及事件驅動 (Event-Driven)

在以下狀況,可以考慮使用CQRS模式:

  1. 當在業務邏輯層有很多操作需要相同的實體或者對象進行操作的時候。CQRS使得我們可以對讀和寫定義不同的實體和方法,從而可以減少或者避免對某一方面的更改造成衝突
  2. 用在特定任務的用戶互動系統,通常系統會引導用戶通過一系列複雜的步驟和操作,通常會需要一些複雜的領域模型。寫入資料的部分有很多和業務邏輯相關的命令操作,輸入驗證,業務邏輯驗證來保證數據的一致性。讀取資料沒有業務邏輯,僅僅是回傳 DTO。讀與寫的資料只需要達到最終一致性。
  3. 適用於一些需要對查詢性能和寫入性能分開進行優化的系統,尤其是讀/寫比非常高的系統。例如在很多系統中讀取資料的使用次數遠大於寫入資料。
  4. 對於系統在將來會隨著時間不斷演化,有可能會包含不同版本的模型,或者業務規則經常變化的系統
  5. 需要和其他系統整合,特別是需要和事件歷程 Event Sourcing 進行整合的系統,這樣子系統的臨時異常不會影響整個系統的其他部分。

在以下狀況,不適合使用CQRS:

  1. 領域模型或者業務邏輯比較簡單,這種情況下使用CQRS會把系統弄得太複雜
  2. 對於簡單的,CRUD模式的用戶介面以及與之相關的數據訪問操作已經足夠的話,都只是一個簡單的對數據進行增刪改查,沒有必要使用CQRS
  3. 不適合在整個系統中全部都使用 CQRS,在特定模組中CQRS可能比較有用

以下是一些查詢到的 CQRS 架構圖,從圖片可以看到跟傳統的 CRUD Data-Driven 架構的差異。

這種架構區分了 Business 及 Query model 的 DataBase,也可以想成將資料寫入了 Business Database,而前端使用者不會觸及該資料庫,是比較強調資料安全性的方式,但不能保證 Query Model 的 DB 裡面的資料一定會跟 Business Model DB 的資料一樣。

這種架構比較接近一個一般性的系統,資料庫是單一的,且可以在 Event Handler 中確認並檢查 Database 及 Analysis Database 的資料一致性。

這種架構圖比較強調資料流的過程,但基本上架構跟上面那個很接近,不過在 Comamnd 的部分,可注意到 Command 沒有 Reply DTO,也可以說,只要 Comamnd 有送進 Command Handler,就視為一個成功執行的 Command。

這裡強調 Command Bus 是以非同步的方式,送進 Command Handler,非同步可強化系統效能的表現,但如果要用同步的方式也可以,要等到 Command Event 送進 Event Database 後,才回應給前端確認該命令已經完成。而 Query 是用同步的方式,發送 Query 並等待要回傳的 ViewModel。

References

CQRS - Martin Fowler

CQRS 介紹

CQRS 命令查詢職責分離模式介紹

從CQS到CQRS

From CQS to CQRS

深度長文:我對CQRS/EventSourcing架構的思考

DDD CQRS架構和傳統架構的優缺點比較

Introduction to Domain Driven Design, CQRS and Event Sourcing