l

2022年7月3日 星期日

事件溯源(6):透過Projection查詢Event Store

June 30 22:15~23:38

▲圖1:EventStoreDB的Projections畫面

 

前言

在領域驅動設計中套用Event Sourcing,一個Aggregate instance的資料透過Repository儲存至Event Store的event stream,讀取的時候也是以單一個Aggregate instance為基本單位。但這樣顯然不足以應付大多數系統需要查詢資料的需求,在進一步套用CQRS解決查詢問題之前,這一集先介紹如何將原本主要用來負責寫入的Event Store也拿來當作查詢資料庫使用。

***

Projection (投影)

接下來Teddy將以EventStoreDB為例,說明如何使用Event Store來查詢資料。之前Teddy提到一般使用Aggregate-ID當作event stream的名稱把Aggregate instance的領域事件存入EventStoreDB。例如一個Tag Aggregate instance,它的id等於4cc11cc7-707b-476a-801c-1b6a22a69169,那麼它的領域事件將儲存在EventStoreDB裡面名稱為Tag-4cc11cc7-707b-476a-801c-1b6a22a69169的event stream。

EventStoreDB中,除了這種代表Aggregate instance的event stream,系統還有一些特殊的event stream:

  • $all:所有系統所產生的事件都可以在$all stream找到。
  • System projections:系統依據某些內建特定條件自動「投影」產生的stream,比較常用的有:
    • By Category($ce-):Category就是Aggregate Type,EventStoreDB會將不同Aggregate Tyee的事件投影至 $ce-[Aggregate] stream中。例如,可以從$ce-Tag stream讀到所有Tag instance的事件。
    • By Event Type($et-):將每一種event type投影成一個event stream,例如可以從 $et-TagEvents$TagCreated stream讀到所有的TagEvents$TagCreated領域事件。

 

***

使用Projection查詢資料

以下用查詢某一個Board有多少個Tag當成範例說明如何使用projection來查詢資料。

在ezKanban中Tag與Board靠著Tag身上的board id維持單向依賴,Board並不知道它身上一共有多少個Tag,所以無法從BoardRepository獲得Board身上有多少個Tag。而基本的TagRepository也只能依據tag id找到某一個Tag,無法找出屬於某一個Board的全部Tag。

參考圖2,要找出某個Board有多少個Tag,只要:

  1. $et-TagEvents$TagCreated stream讀取所有的事件,然後依據事件身上的board id當作過濾條件,就可以找到一個List<TagCreated>用來代表某個Board身上所有 Tag 的id。
  2. 跑一個for each迴圈,依據步驟1找到的List<TagCreated>呼叫TagRepository就可以找出所有這個Board裡面的所有Tag。

 

▲圖2:getTagsByBoardId程式碼

 

***

 

EventStoreDB Projection的優缺點

EventStoreDB的Projection是一個很強大的功能,它讓原本主要用來支援Write Model的資料庫,也可以同時做為Read Model資料庫。當然這種View Model本質上還是保有Event Sourcing的特性,也就是說要得到領域物件的「目前狀態」還是需要讀出該Read Model裡面所有的領域事件並replay它們。速度可能沒有像是採用CQRS之後,用NoSQL資料庫當作Read Model只要下一個查詢條件就可以直接把整個Read Model讀到記憶體裡面那麼快。

但是,所謂「速度比較慢(或很慢)」也許沒有人類想像中的那麼慢,還是要依據應用程式的實際狀況來判斷,在很多情況之下這種速度已經可以被接受。畢竟直接使用EventStoreDB的Projection可以很方便的產生Read Model,而套用CQRS還需要另外撰寫Projector用以在讀取端資料庫投影出Read Model,這可是一個不小的工作負擔。

另外還有一點要注意,EventStoreDB是採用非同步的方式去投影出這些Projection,也就是說這些Projection與原始寫入的領域事件之間的狀態同步是最終一致性。因此你可能會發現,為什麼有時候領域事件已經寫入資料庫中,但在是Projection裡面還讀不到相關領域事件。

***

下集預告

介紹完Event Sourcing的基本寫入以及讀取操作之後,下一集談在多人同時讀取與寫入資料的情況下,如何透過樂觀鎖定來避免資料衝突。

***

友藏內心獨白:不能下Select查詢資料一開始會不太習慣。

沒有留言:

張貼留言