現在,每個開發人員都很熟悉MVC標準體系結構設計模式。大多數的應用程式都是基於這種體系結構進行建立的。它允許我們建立可擴充套件的大型企業應用程式,但近期我們還聽到了另外的一些有關於CQRS/ES的相關資訊。這些方法應該被放在MVC中一起使用嗎?他們可以解決什麼問題?現在,讓我們一起來看看CQRS/ES是什麼,以及他們都有哪些優點和缺點。
CQRS — 模式介紹
CQRS(Command Query Responsibility Segregation)是一種簡單的設計模式。它衍生與CQS,即命令和查詢分離,CQS是由Bertrand Meyer所設計。按照這一設計概念,系統中的方法應該分為兩種:改變狀態的命令和返回值的查詢。Greg young將引入了這個設計概念,並將其應用於物件或者元件當中,這就是今天所要將的CQRS。它背後的主要思想是應用程式更改物件或元件狀態(Command)應該與獲取物件或者元件資訊(Query)分開。
下面,將通一張圖來說明應用程式中有關CQRS部分的組成結構:
Commands(命令)—表示使用者的操作意圖。它們包含了與使用者將要對系統執行操作的所有必要資訊。
- Command Bus(命令匯流排):是一種接收命令並將命令傳遞給命令處理程式的佇列。
- Command Handler(命令處理程式):包含實際的業務邏輯,用於驗證和處理命令中接收到的資料。Command handler負責生成和傳播域事件(Event)到事件匯流排(Event Bus)。
- Event Bus(事件匯流排):將事件釋出給訂閱特定事件型別的事件處理程式。如果存在連續的事件依賴,事件匯流排可以使用非同步或者同步的方式將事件釋出出去。
- Event Handler(事件處理程式):負責處理特定型別的事件。它們的職責是將應用程式的最新狀態儲存到讀庫中,並執行終端的相關操作,如傳送電子郵件,儲存檔案等。
Query(查詢):表示使用者實際可用的應用程式狀態。獲取UI的資料應該通過這些物件完成。
下面我們將介紹有關CQRS的諸多優點,它們是:
- 我們可以給處理業務邏輯部分和處理查詢部分的開發人員分別分配任務,但需要小心的是,這種模式可能會破壞資訊的完整性。
- 通過在多個不同的伺服器上擴充套件Commands和Query,我們可以進一步提升應用程式的讀/寫效能。
- 使用兩個不同的資料庫(讀庫/寫庫)進行同步,可以實現自動備份,無需額外的干預工作。
- 讀取資料時不會涉及到寫庫的操作,因此在使用事件源是讀資料操作會更快。
- 我們可以直接為檢視層構建資料,而無需考慮域邏輯,這可以簡化檢視層的工作並提高效能。
儘管使用CQRS模式具有上述諸多的優點,但是在使用前還需要慎重考慮。對於只具有簡單域的簡單專案,其UI模型與域模型緊密聯絡的,使用CQRS反而會增加專案的複雜度和冗餘度,這無疑是過度的設計專案。此外,對於資料量較少或者效能要求較低的專案實施CQRS模式不會帶來顯著的效能提升。
Event Sourcing — 案例研究
有這樣一個案例,我們想要檢索任何一個域物件的歷史狀態資料,而且在任何時間都可以生成統計資料。我們想要檢查上個月、上個季度或者過去任何時間的狀態彙總。想要解決這個問題並不容易。我們可以在特定的時間範圍內將額外的資料儲存在資料庫中,但這種方法也存在一些缺點。我們不知道範圍應該是什麼樣子,以及未來統計資料需要哪些資料項。為了避免這些問題,我們可以每天為所有聚合建立快照,但它們同樣會產生大量的冗餘資料。
Event Sourcing(ES)似乎是目前解決這些問題的最佳方案。Event Sourcing允許我們將Aggregate(聚合)狀態的每一個更改事件儲存在Event Store的事件儲存庫中。通過Command Handler將事件寫入到事件儲存庫中,並處理相關的邏輯。要建立Aggregate(聚合)物件的當前狀態,我們需要執行建立預期域物件的所有事件並對其執行所有的更改。下面我們將通過一張圖來說明這一架構設計方式:
下面我們將列舉一些使用ES的優點:
- 時間穿梭機:可以及時重建特定聚合的狀態。每個事件都包含一個時間戳。根據這些時間戳可以在特定的時間內執行事件或者停止事件。
- 自動審計:我們不需要額外的工作就可以檢查出在特定的時間範圍內誰做了什麼以及改變了什麼。這和可以顯示更改歷史記錄的系統日誌不同,事件可以告知我們每次更改背後所對應的操作意圖。
- 易於引入糾正措施:當資料庫中的資料發生錯誤時,我們可以將應用程式的狀態回退到特定的時間點上,並重建當時的應用程式狀態。
- 易於除錯:如果應用程式出現問題,我們可以將特定事件內的所有事件取出,並逐條的重建應用狀態,以檢查應用程式可能出現問題的地方。這樣我們可以更快的找到問題,縮短除錯所需的時間。
Aggregates
**Aggregate(聚合)**一詞在本文中多次被提及,那它到底是什麼意思?**Aggregate(聚合)**來自於領域驅動設計(DDD)的一個概念,它指的是始終保持一致狀態的實體或者相關實體組。我們可以簡單的理解為接收和處理Command(包含Command Handler)的一個邊界,然後根據當前狀態生成事件。在通常情況下,Aggregate root(聚合根)由一個域物件構成,但它可以由多個物件組成。我們還需要注意整個應用程式可以包含多個Aggregate(聚合),並且所有事件都儲存在同一個儲存庫中。
總結
CQRS/ES可以作為特定問題的解決方案。它可以在標準N層架構設計的應用程式的某些層中進行引入,它可以解決非標準問題,常規架構中我們所拿到的是最終狀態,在很多情況下,固然當前狀態很重要,但我們還需要知道當前狀態是如何產生的。CQRS和ES兩種概念應該一起使用嗎?事實表明,並沒有。我們想要統計任何時間範文內的域物件狀態,而寫庫只能儲存當前狀態。引入CQRS並沒能幫助我們解決這一問題。在下一章節中,我們將引入Axon框架,Axon框架時間了CQRS/ES,用於解決某些域物件的一些特定問題,尤其是收集歷史統計資料。我們將闡述如何使用Axon框架實現CQRS/ES並實現與Spring Boot應用程的整合。
作者:LukaszKucik ,譯:譚朝紅,原文:CQRS and Event Sourcing as an antidote for problems with retrieving application states