l

2022年7月8日 星期五

事件溯源(11):撰寫JavaScript在EventStoreDB中產生自訂投影

July 04 18:38~19:46

▲圖1:EventStoreDB的使用者自訂投影程式

 

前言

上一集介紹ezKanbana如何實作Projector以便在PostgreSQL資料庫中產生Read Model所需的資料,這一集回頭使用EventStoreDB的客製化Projection功能,建立另一種形式的Read Model。

 

***

透過JavaScript程式產生事件投影

Teddy在<讀取事件溯源(6):透過Projection查詢Event Store>介紹過EventStoreDB三種內建的Projection:$all、$ce-以及$et-。除了內建的Projection以外,EventStoreDB也支援使用者撰寫JavaScript程式來產生Projection。什麼,JavaScript程式?!沒錯,Teddy沒寫錯,鄉民們也沒看錯,就是JavaScript程式。EventStoreDB可以執行JavaScript程式,投影出使用者想要的event stream,再利用這個event stream產生讀取資料。

依據EventStoreDB官方文件的講法,它是一個為了寫入所開發的事件溯源資料庫,也是一個可以做為讀取模型的資料庫。EventStoreDB就是透過自訂Projection使得它可以做為取模型的資料庫。

 

圖1為ezKanban用來投影所有Board事件的JavaScript程式,第1行~第5行的fromStreams函數表示將這些event stream當成輸入來源。第11行~第13行則把所有接受到的事件,投影到另一個ReplayEvents-By-Board-[board id]的event stream裡面。這個使用者自行定義的event stream,可以做為ezKanban的replay功能的讀取模型。也可以拿這個event stream當成資料來源,傳給上一集<事件溯源(10):實作Projector>裡面的NotifyBoardContent程式,讓它即時投影出GetBoardContent所需的讀取模型。

 

***

 

採用EventStoreDB當成讀取資料庫的架構如圖2所示,以GetBoardContent查詢為例,它會直接從ReplayEvents-By-Board-[board id] event stream裡面讀出屬於某一個Board的所有事件,然後再呼叫NotifyBoardContent讓它即時重播這些事件並投影出目前狀態。


▲圖2:將EventStoreDB當成Read Database

 

ezKanban的GetBoardContent查詢並沒有使用這種方式當作讀取資料來源,但是團隊有用測試案例來驗證這種作法的效率,如圖3所示。

  ▲圖3:使用測試案例模擬將EventStoreDB做為GetBoardContent的讀取模型資料庫

 

***

效能比較

ezKanban團隊成員杜奕萱在她的碩士論文《套用命令與查詢責任分離以簡化聚合依賴:以 ezKanban 為例》比較使用PostgreSQL資料庫以及在上一集<事件溯源(10):實作Projector>產生JSON讀取模型的效能比較,如圖4所示。可以看出來,當卡片數量越多,套用CQRS的效益越大。

 

▲圖4:杜奕萱碩士論文所做的ezKanban在套用CQRS前後,GetBoardContent查詢的效能比較

 

在杜奕萱的碩士論文中並沒有比較這一集所介紹的採用EventStoreDB建立GetBoardContent讀取模型的方法,但ezKanban團隊在前幾天以圖3測試案例的形式做了非正式的比較,其結果如下:

  • 在8000張卡片的情況之下,花費的時間大約2179 ms。這個速度還是比沒有建立讀取資料要來得快很多(原本需要16841 ms)。
  • 團隊進一步模擬以1000筆領域事件建一次Snapshot為例,則產生GetBoardContent的時間可以縮短成87ms,非常接近在PostgreSQL資料庫中用JSON格式產生讀取模型的73ms時間。

***

EventStoreDB自訂投影的好處

若採用上一集<事件溯源(10):實作Projector>所介紹的架構來套用CQRS,則讀取端的Projector需要具備idempotent的特性,而且若讀取資料庫損毀,也需要想辦法讀取原本的領域事件再次投影出讀取模型(replay)。使用EventStoreDB的自訂投影,只要撰寫好JavaScript程式,EventStoreDB會負責產生投影,使用者不用煩惱idempotent的問題。至於replay也很簡單,因為所有的領域事件都保存在EventStoreDB所投影出的stream裡面,所以只要重頭讀取一次該stream的所有事件再逐一apply即可。雖然這種方式的效能沒有直接用JSON儲存View Model來得快,但如果有需要只要加上Snapshot,便可以大幅改善EventStoreDB做為讀取資料庫的效能。

至於CQRS的讀取資料庫要採用哪種方式,就要看鄉民們各自專案需求與技術能力而定。

***

下集預告

Event Sourcing與CQRS的核心觀念與技術介紹的差不多了,下一集之後談幾個比較進階一點的議題,先從幫Aggregate或是Read Model建立Snapshot(快照)談起。

***

友藏內心獨白:Write DB也可以是Read DB。

沒有留言:

張貼留言