l

2022年7月5日 星期二

事件溯源(8):什麼是CQRS?

July 02 06:35~08:20

▲圖1:ezKanban套用CQRS架構圖

 

前言

CQRSCommand Query Responsibility Segregation的縮寫,中文翻成「命令與查詢責任分離」。今天介紹CQRS的涵義以及它可以解決什麼問題。

 

***

起源

CQRS是由Greg Young所提出的設計模式,它的概念很簡單:分開設計以下兩種操作:會改變系統狀態但不會回傳值的操作,稱之為Command,以及不會改變狀態但會回傳值的操作,稱為Query 。Greg Young同時也是Event Sourcing的提倡者,他在2011年開發EventStoreDB這個直接支援Event Sourcing與CQRS的資料庫,Teddy在<事件溯源(3):將Aggregate儲存至EventStoreDB>介紹如何用這個資料庫來做儲存領域事件。

***

CQRS有時又稱為簡稱讀寫分離,此概念並非Greg Young首創,最早由Eiffel語言與Design By Contract(DBC)發明人Bertrand Meyer所提出,稱為Command–Query Separation(CQS)。CQS的應用對象是物件,而CQRS則將範圍拓展到整個系統,包含API、Use Case、Domain Model、Database,通通可以讀寫分離。

這些資料網路上隨便查一下就找得到,聽過CQRS的鄉民可能都知道,但你知道Bertrand Meyer為什麼要提出CQS嗎?

CQS和DBC有關。在撰寫合約的時候,不管是preconditions或postconditions,都可能需要呼叫物件的method來做為狀態驗證。圖2是ezKanban系統中,Workflow Aggregate的deleteLaneById方法。ezKanban有套用DBC,第117行~119行是preconditions,第131行是postcondition。在第119行與131行中,呼叫getLaneById()方法檢查某個lane是否存在,getLaneById() 就是query,它回傳boolean但不會改變物件狀態。因為合約撰寫在物件身上,如果物件設計沒有遵守CQS,那麼你怎麼知道在合約中呼叫getLaneById()方法會不會不小心改變了系統狀態?如果沒有CQS,DBC就玩不下去了。

CQS提出至今已有30幾年,為什麼沒有大紅大紫,大部分的鄉民都是因為CQRS才知道CQS?答案很簡單,因為CQS的應用與DBC緊密相關,而大部分的開發人員並沒有實際應用DBC的機會,所以才會沒聽過CQS。

 

 

▲圖2:ezKanban系統Workflow的deleteLaneById方法

 

CQRS和DBC脫鉤,趕上分散式計算、微服務架構的大環境,拓展其應用的機會,所以才流行起來。

***

CQRS的好處

CQRS有以下三個主要的優點,簡稱為3S:

  • Simplicity:CQRS將系統分成Write Model與Read Model,這兩種模型的行為與責任大不相同。從單一責任原則(Single Responsibility Principle;SRP)的角度來看,CQRS進一步簡化不同模型之內的複雜度。
    • 寫入模型:
      • Strong consistency
      • normalized data model
      • one-way dependency
    • 讀取模型:
      • Eventually consistency
      • de-normalized data model (materialized view)
      • any-way dependency
  • Scalability:很多系統的讀取頻率遠大於寫入,例如在電子商務系統中大部分的使用者都在瀏覽資料,少部分的操作才會改變系統狀態。在這種情況下,套用CQRS可以單獨針對讀取部分加以拓展,如圖3所示。
  • Speed (Performance):綜合上述兩個優點,CQRS可以提升系統反應速度與效能,因為開發人員可以分別針對寫入端與讀取端採取不同的優化策略。例如,寫入端採取Event Sourcing簡化與加速寫入操作,讀取端因為不會改變狀態,可以用各種快取工具加快讀取。圖4是ezKanban團隊成員杜奕萱在她的碩士論文《套用命令與查詢責任分離以簡化聚合依賴:以 ezKanban 為例》中針對套用CQRS之後ezKanban的GetBoardContent查詢所做的效能測試,可以發現套用CQRS之後有著非常巨大的讀取效能提升。

 

▲圖3:CQRS可分別拓展寫入與讀取服務

 


▲圖4:杜奕萱碩士論文所做的ezKanban GetBoardContent 效能測試

 

***

沒有缺點嗎?

以上把CQRS講的好像很神,它有沒有缺點?當然有。

雖然CQRS的套用可以先簡單的從API層與Use Case層開始,不一定要做到Domain Model甚至是資料庫的讀寫分離。但依據ezKanban這兩年套用CQRS的經驗,最終還是走向Domain Model與資料庫讀寫分離。將系統在各個階層「精細地」分成讀取模型與寫入模型,雖然個別模型內責任單一,變得比較簡單,但跨模型之間還是有相依性,要如何管理這些相依性就變成一個挑戰

例如,如圖1所示,ezKanban在資料庫端也套用CQRS,因此讀取資料庫與寫入資料庫之間的狀態同步是最終一致性。為了維持最終一致性,就會有新的設計工作產生:同步的訊息就需要考慮順序(ordering)與at least once等議題,而負責產生讀取資料庫的Projector(投影器)設計則須考慮idempotent與replay events等問題。另外,如何撰寫Projector也是一個問題。

換句話說,CQRS走到底的技術門檻會比較高。

這也是為什麼許多文章或書籍會建議不要為了套CQRS而套CQRS,要看自己的業務需求沒有有強烈到需要CQRS所帶來的這些好處。

***

下集預告

說明完CQRS基本概念,下一集先介紹ezKanbana套用CQRS之後對於簡化領域模型所達到的效果,之後再介紹ezKanbana如何實作Projector以及套用CQRS對於架構的影響。

***

友藏內心獨白:活用之後其實也沒有那麼難。

沒有留言:

張貼留言