被誤解的 Event Sourcing

我們經常看到隨着 Event Sourcing 一起出現的,還有幾個大家比較熟知的概念:CQRS, EDA(Event-driven Architecture),當然還有 DDD。在經歷過採用 Event Sourcing 的項目後,我想和大家討論一下,當我們提到 Event Sourcing 時,我們在說什麼?再簡單闡述一下這四個概念之間的關係。

Event Sourcing 的概念

提到 Event Sourcing,我們會聯想到一個非常相近的生活中的例子就是會計賬本,會計賬簿上的會計條目按照發生的時間順序,記錄了對賬戶餘額產生變更的事件。通過會計賬簿的記錄,我們可以計算出任意時間點的賬戶餘額。如果說有一類應用程序需要留存對最終結果造成改變的所有事件,那 Event Sourcing 就像是這類應用程序的概念抽象。所以我們看到 Event Sourcing 也有着和會計賬簿一樣的特徵:

  1. 不是保留當前狀態,而是保留所有導致狀態改變的事件

  2. 事件會按發生的時間順序被記錄下來

  3. 通過事件重建得到狀態

聽起來 Event Sourcing 的概念也沒有那麼難理解,更加貼合現實,還保存了所有真實事件,如果有審計相關的需求,顯然是很容易得到審計需要的數據。

Event Sourcing 在 Node.js 裏並不是一個被廣泛使用的成熟設計,我們很難在市面上找到成熟的 Node.js 的 Event Sourcing 框架,這意味這我們可能會面臨更多的未預知的問題。除了未預知的問題之外,開發團隊還面臨着巨大的思維轉變:系統中的一等公民變成了事件,所有的邏輯都是圍繞着事件展開,系統狀態不再是一個一定需要被持久化的元素了。這聽起來很簡單但是實踐起來卻並非只言兩語可概括般的容易。

在什麼情況下需要用到 Event Sourcing?

我瞭解到有的項目有基於命令轉化爲事件,並將事件持久化到數據庫,但是在此同時他們也把 command 轉化爲 snapshot 保存了下來。讀模型的構建全部基於 snapshot。該團隊確實將系統發生的真實事件全部留存了下來,但實際並沒有通過事件重建得到狀態,所有的狀態都是來自於另外處理得到的 snapshot。嚴格上來說並不能算做是使用了 Event Sourcing,系統中做到了留存 Event,但是並沒有用到 Sourcing。

基於一些背景信息,當時該項目使用 Event Sourcing 的出發點在於,客戶強烈要求將 DDD 的思想和產出的模型完全代碼化,特別是在 Event Storming 過程中的產出。

上面的例子不禁讓我們思考一個問題:究竟在什麼情況下需要用到 Event Soucing?

爲了回答這個問題我們先來看看 Event Sourcing 中的核心概念:

Event:發生的事實,也是唯一真實的數據來源。用過去式來表述。系統中的事件是 immutable 的,不能被更改和刪除,只能通過添加新的事件來改變當前的系統狀態。

完全重建(Rebuild):我們可以完全丟棄系統狀態,在任何時間通過事件日誌按照時間順序重演出當前系統狀態。

事件回放(Replay):就像平時瀏覽視頻一樣,如果視頻總時長是半小時,我們想回到 25 分,我們可以直接把進度條向後拉到 25 分。在 Event Soucring 的系統裏,我們可以基於某個重建出來的系統狀態,回放後續的事件,得到我們想要的某個時間節點的系統狀態。

又比如:我們的會計賬簿裏保存了過去一年全部的會計條目,現在想要得到 5 月 30 號當天的賬戶餘額,在此前因爲業務要求我們已經得到了每個季度結束的賬戶餘額。那我們可以通過已知的 5 月 31 號的賬戶餘額對 5 月 31 號發生的所有存款和取款進行反向的重放得到 5 月 30 號的餘額。

使用 Event Sourcing 的好處

基於 Event Sourcing 的特性,我們可以來探討下它究竟能給我們的系統或者說業務帶來怎樣的好處?

  1. 審計追蹤:首先它留存所有真實事件的設計天然地爲後期審計追蹤提供了便利,因爲系統裏留存下了所有現實中產生的痕跡,並且這些痕跡都不被允許修改;

  2. 適應多樣的查詢需求:我們的系統狀態都是來自於事件,那意味着我們可以根據不同的查詢需求構建出不同的讀模型,以適應業務需求。這也是爲什麼我們看到 Event Sourcing 會經常伴隨 CQRS 出現的一個原因。因爲在 Event Sourcing 的系統裏我們可以利用其特性,分離讀寫模型;

  3. 調試:這個優點的來源同樣是保存了所有的事件,這意味着當我們線上環境出問題時,我們可以把線上環境的所有 event 拿到一個類線上環境下測試, 找到問題出在哪兒;

  4. 可以得到系統任何時間點的狀態;

  5. 系統狀態可以是內存內的,不一定要持久化到數據庫:任何事情發生時,就像服務崩潰的時候,我們都可以通過事件重建得到系統狀態。這樣你就不需要考慮持久化到數據庫會涉及到的各種 Data Mapping 的邏輯了;

  6. 領域事件是有價值的,存下產生的領域事件,不丟失所有的現實痕跡,爲支撐後期業務擴展,提供商業數據分析的數據源。

從它能帶來的優點來看,當我們的業務需求有:

  1. 能夠保留下所有的事件以適應審計的需求;

  2. 客戶認爲系統中發生的事實都是很有價值的,一定要保存下來,以便支撐後續業務擴張的商務分析;

  3. 需要經常查詢不固定時間點的系統狀態;

  4. 多種多樣的基於不同維度的查詢需求時,不妨考慮一下 Event Sourcing。

當然決定用它之前我們還是得考慮一下它的缺點:

  1. 事件的版本:對於不同類型或者不同聚合根下的事件我們有着不一樣的 Event Handler, 而當業務演進的過程中,相應地對事件的處理也會不同。這意味着我們在業務擴展的時候需要考慮兼容舊的事件;

  2. 業務發生改變後,爲適應業務需求我們需要 replay 出的 application state 也會可能發生改變,那我們要如何兼容舊的事件 rebuild 或 replay 出新結構的 application state?

  3. 讓開發團隊感到陌生的設計思想;

  4. 較少成熟的 Event Sourcing 的框架支持;

  5. 在 Event store 中需要序列化 Event。

Event Sourcing 和其他架構之間的關係

回到文章開頭提到的四個經常被拿來一起說的概念:當我們決定使用 Event Sourcing 作爲架構選擇之時,通常我們也會選擇 DDD 去構建得到領域事件。DDD 裏提到的 Event 指的是對系統狀態產生改變的現實事件,同樣我們在 Event Sourcing 的系統中存儲的也是會導致系統狀態改變的事件。似乎這兩種不同的軟件開發思想,對 Event 的認識有着不謀而合的默契。

用到 Event Sourcing 的系統又絕大部分都會採用 CQRS。因爲我們持久化的 event 和查詢所需的結構很顯然是有區別的,與其每次查詢都通過 Rebuild 或者 Replay 去得到查詢所需的狀態,我們一般都會根據查詢需求構建出查詢需要且方便的讀模型。即便如此,當我們決定選用 CQRS 時,還是得考慮引入後會增加的的複雜度。這也意味着不是所有的 Event Sourcing 的系統都需要採用 CQRS。

至於 EDA 那其實是完全沒有太大關係的概念了,不過我們經常在處理服務之間通信的問題的時候會用到。當我們的項目恰好是微服務,又採用了 DDD,還加上 Event Sourcing 和 CQRS 那我們還需要引入 EDA 的時候,就要小心我們平時的技術討論中一定要分清楚我們所說的 Event 是在怎樣的上下文下的。想要更多的瞭解 EDA 的概念可以參看 Martin Fowler“當提到 “事件驅動” 時,我們在說什麼?”的文章,其中也提到了我們經常會混用 Event Sourcing,EDA,CQRS 中的一些概念。

希望這篇文章能夠引發你對 Event Sourcing 的設計思想的一些思考。也期望後續我還能再完成一篇 Event Sourcing 實戰的文章。這篇文章其實還是有些遺漏的地方,比如在 Event Sourcing 架構選擇決策的缺點部分,但是考慮到實際選用 Event Sourcing 架構的情況下通常還會選用其他的設計以及架構,比如文中反覆提到的 CQRS 和 DDD,在最終決定的架構下也會引入除了本文所提的缺點之外的其他問題,但因爲我認爲這並不算是 Event Sourcing 架構本身帶來的問題故沒有在文中深究。但是如果大家真的決定選用 Event Sourcing 作爲系統設計思想的一部分的話還是需要對 Event Sourcing 的應用做更多的探索,本文還是旨在闡明 Event Sourcing 的概念,消除大家對於 Event Sourcing 的部分誤解。

參考資料:


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/If6Zuf_h0j9IQpG9N12eMw