l

2022年6月28日 星期二

事件溯源(1):Event Sourcing的好處

June 28 06:30~09:30

▲採用Event Sourcing的系統狀態由重播(重新套用)事件所計算而來 

 

前言

今年下半年Teddy準備開新課程—事件溯源與命令查詢責任分離架構實作班,開課之前先把教材內容整理成文章。這系列文章分成Part 1與Part 2,先介紹Event Sourcing在介紹CQRS。

 

***

 

兩種常見儲存狀態的方式

Event Sourcing,翻譯成「事件溯源」或「事件來源」,是一種儲存狀態的方式。在Event Sourcing流行之前,大部分的開發人員儲存系統狀態的方式稱為State Sourcing(狀態來源)或Domain Sourcing(領域來源)。如圖1所示,State Sourcing採用將系統目前狀態儲存至資料庫中。這種儲存狀態的重點是資料庫中只儲存目前狀態,系統的狀態是直接從資料庫中取得,至於所使用的資料庫是關聯式資料庫或是NoSQL資料庫都可以。

在State Sourcing系統中,狀態改變會直接覆寫物件現有的狀態,例如圖1中Teddy的email如果改成ted@gmail.com則在資料庫中id=001的這一筆資料,其email欄位的值就被改成ted@gmail.com,舊有的值則被覆蓋掉。


▲圖1:以State Sourcing保存系統狀態 

 

***

 

如圖2所示,Event Sourcing在資料庫中保存的不是系統的目前狀態,而是儲存曾經造成系統狀態改變的所有事件。至於系統的目前狀態,則是透過把這些事件從頭到尾重新執行過一次(稱為replay events)所計算出來。儲存事件的資料庫一般稱為Event Store,它可以是關聯式資料庫,例如message-db (https://github.com/message-db/message-db)、NoSQL資料庫,或是專門為Event Sourcing所設計的特殊用途資料庫,例如EventStoreDB (https://eventstore.com)。

在Event Sourcing系統中,基本上採取append only的方式來儲存事件。事件只可寫入Event Store,不可刪除或修改。例如圖2中Teddy的email如果改成ted@gmail.com,則在資料庫中代表Teddy帳戶的event stream會被寫入一筆新的事件:EmailChanged { id=001, email=ted@gmail.com}


▲圖2:以Event Sourcing保存系統狀態 

 

***

稽核

Event Sourcing並不是新的技術,像是銀行的存款帳戶儲存客戶存款金額的方式就是採用Event Sourcing,存摺上面一筆、一筆的交易紀錄(transaction log)就是系統所儲存的事件。.

由於所有針對系統所造成的狀態改變皆以事件的形式儲存起來,因此可以達到稽核的效果。以銀行存款為例,如果銀行採用State Sourcing的方式儲存你的存款餘額,你認為自己在銀行存款餘額還有一百萬,但是銀行的資料紀錄你只剩下一百塊,怎麼辦,以誰的紀錄為準?為了稽核,銀行必須儲存每筆交易紀錄,既使是錯誤的交易紀錄也不能直接刪除,而是要用另外一筆紀錄去沖銷。例如,銀行帳務系統錯誤,不小心轉了五萬到你的戶頭。銀行不能直接從你的戶頭扣掉五萬然後刪除這筆交易紀錄,而是要新增一筆負五萬的交易紀錄來抵銷原本錯誤的交易。

 

***

 

狀態同步

既然Event Sourcing不是新技術,在特定領域中也應用了很長一段時間,為什麼近幾年來這個名詞變得越來越流行?它流行的原因和DDD(領域驅動設計)流行的原因相同,主要受惠於微服務架構的熱潮。

在DDD中,Aggregate狀態改變會產生領域事件(Domain Event),透過領域事件可以在不同Aggregate之間達到狀態最終一致性,這個特性正好可以應用在分散式或微服務架構中作為狀態同步之用。

如圖3所示,如果DDD的Aggregate採用State Sourcing,在儲存物件狀態的時候,除了原本物件的目前狀態以外,還需要儲存領域事件,這兩個動作必須要在同一個交易(transaction)中一起完成,系統狀態才不會錯誤。在微服務物架構中Transactional Outbox設計模式就是用來解決這個問題。

 

▲圖3:State Sourcing保存系統狀態與領域事件必須在同一個交易中 

 

如圖4所示,採用Event Sourcing的系統在儲存領域事件的同時就等於儲存系統狀態,也就避免圖3的問題。

 

▲圖4:Event Sourcing保存領域事件也就同時保存系統狀態 

 

在這裡有一點要注意,不管是採用Transactional Outbox或是Event Sourcing,在這種情況下Event Store同時也扮演簡易Message Bus或Message Broker的功能。也就是Event Store需要支援客戶端去資料庫中讀取領域事件,然後再將領域事件轉發給其他「下游」的Event Handler或微服務,如圖五。

 

▲圖5:Event Store也是Event Bus/Event Broker 

***

 

偏好寫入

Event Sourcing還有寫入快速、簡單、方便的好處。在DDD的情境下採用Event Sourcing,每一個Aggregate的instance在Event Store中會有一個專屬的event stream用來儲存它的領域事件 ,這個event stream通常以Aggregate Type-Aggregate ID的格式來命名,例如一個Account Aggregate instance,它的id等於3104ca15-df4a-4878-9342-b6d6d650b4cc,那麼在Event Store中就會新增一個Account-3104ca15-df4a-4878-9342-b6d6d650b4cc的event stream,用來儲存該Account instance的領域事件,如圖6所示。

 

▲圖6:EventStoreDB的Stream Browser畫面

 

相較於使用關聯式資料庫需要將物件結構轉成關聯式表格(Object Relational Mapping;ORM),Event Sourcing只需儲存領域事件,省去ORM繁瑣的設定。另外,由於寫入資料一次只針對一個Aggregate,所以也不會有採用關聯式資料庫在寫入時可能需要鎖定多個表格的現象,因此「理論上」Event Sourcing的寫入效能會比較高。

 

***

下集預告

關於Event Sourcing的基本觀念先介紹到這裡,下集就要開始寫程式,以ezKanban的Tag Aggregate為例子,說明撰寫Event Sourced Aggregate以及其相對應Repository的步驟。

 

***

友藏內心獨白:終於又開工了。

沒有留言:

張貼留言