在本文中,我們將介紹兩種相關的架構模式,人們通常會聽說過這兩種模式,而且通常認為它們是同一件事。它們是事件源(Event Sourcing)和 CQRS(命令查詢職責分離)這兩種相關模式。
什麼是事件源?
“我們可能不想知道當前的狀態,而是想知道發生了什麼事情才達到現在的狀態”
讓我們先了解一下什麼是事件源/事件溯源:
- 這種軟體設計模式涉及將應用程式狀態的更改捕獲為連續事件日誌中的不可變和歷史事件。
- 這些事件充當最終參考點,可以重播以在任何給定時刻重建應用程式的當前狀態。
- 隨後使用投影從事件日誌中提取系統的當前狀態以供查詢,利用事件歷史有效地投影當前狀態。(即我們使用事件歷史來投影當前狀態)。
這與非事件源系統(通常是 CRUD)的工作方式非常不同,我們不將事件儲存為歷史活動的日誌,而是不斷更新資料庫中各個記錄的狀態。
與基於 CRUD 的解決方案相比,事件源通常解決以下問題:
- 審計:在事件源中,系統狀態的每次更改都表示為一個不同的事件,提供全面的操作歷史記錄(審計),這在 CRUD 系統中通常是所缺乏的。
- 重放和時間旅行:事件源將系統狀態的變化作為順序日誌中的不可變事件捕獲,與傳統的 CRUD 系統不同,可以實現高效的重放和審計能力(這還包括除錯問題時的時間旅行)。
- 效能:與 CRUD 系統不同,事件源將寫入操作與讀取操作分開,透過基於事件日誌啟用定製的查詢機制來促進可擴充套件性和效能最佳化。
- 原子:事件源可以幫助防止對同一記錄的併發更新,這可能會導致衝突,因為它避免了直接更新資料儲存中的物件的要求。
什麼是 CQRS?
CQRS劃分了處理命令和查詢的職責,自然地與事件源保持一致,從而可以明確分離關注點,高效地從事件歷史中獲取可查詢狀態。在這方面,我們可以建立最終一致的命令(事件)歷史投影,這使我們能夠建立可查詢的只讀檢視,以滿足消費者的特定需求。
讀寫工作負載往往是相反的,對效能和規模的要求非常不同。
在傳統架構中,單一資料模型處理資料庫的查詢和更新,這非常適合簡單的 CRUD 操作。然而,對於複雜的應用程式,這種方法可能變得繁瑣且不切實際;特別是因為寫入可能包含複雜的業務邏輯,而查詢可能需要專門的資料檢視。
最終,讀取和寫入工作負載通常是相反的,對效能和規模的要求非常不同。
CQRS 通常解決以下問題:
- 最佳化操作:CQRS 通常解決資料的讀寫表示之間經常不匹配的問題。
- 效能:CQRS 可以消除使用一個資料儲存進行查詢和寫入資料時出現的效能問題。
為什麼要Event Sourcing + CQRS一起使用 ?
為什麼事件源和 CQRS 天生就契合?這是因為透過事件源進行的順序事件日誌本身並不能提供出色的查詢,但與 CQRS 結合構建一個或多個物化檢視以實現高效查詢效果很好!
在我們開始具體討論事件源之前,讓我們首先介紹一下下面提到的一些關鍵術語:
- 聚合— 聚合是相關實體周圍的一致性邊界。它們是按順序重播時從單個事件流生成的,在此操作期間,將計算聚合的當前(有效)狀態,以便可以用它來處理命令。在我們的示例中,這將是按順序排列的員工事件,從建立員工開始,然後是後續事件,例如請假和取消休假。
- 命令— 命令是一種意圖,它會生成一個新事件,我們會將其新增到事件流並儲存在資料庫中。例如,REQUEST_LEAVE最終會建立一個名為“ ”的新事件的“ LEAVE_REQUESTED”。在我們的示例中,我們的聚合收到“ ”命令REQUEST_LEAVE,然後我們讀取所有事件以生成員工的當前檢視,此時我們決定是否接受該命令(即,我們在聚合中應用業務規則或我們稱之為不變數)。如果接受,我們將生成“ LEAVE_REQUESTED”事件。
- 不變數— 不變數是我們聚合中的業務規則,它始終需要為真才能確保我們的聚合處於有效狀態。例如人事管理系統中,不允許員工在餘額為 0 時申請休假。
事件源有哪些優點和缺點?
那麼,在我們直接投入之前,事件源有哪些缺點呢?
- 複雜性:事件源可能會給系統帶來額外的複雜性,特別是在實施和理解方面。
- 事件版本控制:隨著領域的發展,事件可能需要進行版本控制以適應業務需求的變化。管理事件的向後和向前相容性可能變得複雜,尤其是在具有較長事件歷史的系統中。
- 讀取效能:根據事件重建實體的當前狀態可能需要耗費大量的計算資源,尤其是對於大型事件儲存或複雜事件處理而言。
- 資料儲存:將每個狀態變化儲存為事件會導致大量資料,這會增加儲存成本和複雜性,尤其是對於高吞吐量的系統。
原始碼:Github