l

2022年7月1日 星期五

事件溯源(4):將Aggregate儲存至Outbox Store

June 30 10:41~12:02;12:56~15:24

▲圖1:Outbox儲存方式,資料庫中包含State Sourcing與Transactional Outbox所需的資料表


前言

這一集要用傳統State Sourcing方式將Tag Aggregate儲存到PostgreSQL關聯式資料庫中,除了透過ORM工具將Tag Aggregate資料儲存到資料庫表格中,還需要套用Transactional Outbox在儲存Tag的同一個交易中一併將它身上的領域事件儲存到資料庫的領域事件表格中。我們將這種同時在同一個交易中儲存現有狀態以及領域事件的儲存方式簡稱為Outbox。

***

準備環境

如圖1所示,採用Outbox的資料庫需要有一個用來儲存領域事件的預設表格,Teddy使用Message DB這個開源軟體(https://github.com/message-db/message-db)。它已經設計好用來儲存事件的資料庫表格(表格名稱叫messages),詳細使用方法請參考它的官方網站。

圖2為ezKanban使用Message DB的資料庫畫面,其中messages表格由Message DB所建立,其它像是board、 board_content、board_member、card等表格則是ORM自動建立(ezKanban使用JPA來自動產生這些表格)。


▲圖2:ezKanban採用Outbox的資料庫筆格(部分畫面)

***

TagOutboxRepository實作

上一集<事件溯源(3):將Aggregate儲存至EventStoreDB>已經談過Repository的設計如何同時支援Event Sourcing與Outbox這兩種資料儲存方式,這一集就直接實作TagOutboxRepository類別。程式如圖3所示,它的實作方式和TagEventSourcingRepository類似,差別在於TagEventSourcingRepository將工作委託給GenericEventSourcingRepository,而TagOutboxRepository則是委託給GenericOutboxRepository

 

▲圖3:TagEventSourcingRepository程式碼

 

圖4為GenericOutboxRepository實作,首先看到11行,它接受兩個泛型參數:AggregateRootOutboxData。前者用來表示該GenericOutboxRepository是給哪一個Concreate Aggregate使用,後者則是該Concreate Aggregate透過Outbox方式儲存到資料庫所需的資料。

 

▲圖4:GenericOutboxRepository類別

 

圖5為TagData類別,基本上它身上有Tag Aggregate所有需要儲存到資料庫的屬性(第17~25行),加上@Id與@Column這些JAP的annotation。第14行的streanName與第16行的domainEventDatas這兩個屬性是用來保存領域事件的資料,最後會被儲存至messages這個資料庫表格。最後26~28行的version是用來支援樂觀鎖定所使用的屬性。

 

▲圖5:TagData類別

 

繼續看到圖4第23行的findById,在第25行它透過OutboxStore介面依據Aggregate id從資料庫中直接找出代表該Aggregate的Outbox Data物件。以Tag Aggregate,為例,這個Outbox Data物件的實作就是TagData。接著第27行將這個Outbox Data物件透過OutboxMapper轉成Aggregate並回傳;以Tag為例,將TagData轉成Tag然後回傳Tag給findById的呼叫者。

接下來看到第33行的save方法,首先在第35行將傳入的Aggregate轉成Outbox Data,然後第36行透過OutboxStore介面將Outbox Data儲存至資料庫。儲存完畢後第37行重設Aggregate版本,然後在第38行清除Aggregate身上的領域事件(因為Aggregate的狀態已經儲存到資料庫,所以要清除它身上的領域事件,否則相同Aggregate若再儲存一次會儲存重複的領域事件)。

在這裡有一個重點,就是第36行的store.save()方法。以Tag Data為例,它會先把Tag Data儲存到資料庫中的Tag Table,然後在把它身上的領域事件儲存至資料庫中的messages Table。這兩個儲存動作會被放在同一個交易中執行,以確保Tag的狀態正確。

***

PostgresOutboxStore實作

上面提到的上OutboxStore介面在ezKanban中有一個PostgresOutboxStore類別實作它,圖如6所示。可以看出來PostgresOutboxStore又把工作委託給PostgresOutboxStoreClient,真正和資料庫打交道的程式就寫在它身上。



 ▲圖6:PostgresOutboxStore類別

 

圖7為PostgresOutboxStoreClient程式碼,它透過OrmStoreClientPostgrresMessageStoreClient分別將資料儲存到ORM表格與messages表格。ezKanban底層採用SpringBoot框架,把交易處理交給SpringBoot管理(第22行與第36行的@Transactional annotation)。

第23行的save方法先呼saveAndUpdateVersion方法儲存ORM的資料,接著第25行呼叫saveDomainEventsWithoutVersion儲存領域事件。

第36行的delete方法,先呼叫deleteById刪除ORM表格中的資料,然後再呼叫saveDomainEventsWithoutVersion儲存領域事件。



▲圖7:PostgresOutboxStoreClient類別

 

最後看到OrmStoreClientPostgrresMessageStoreClient實作,如圖8與圖9。前者繼承SpringBoot的CrudRepository,程式碼很簡單Teddy就不多做說明。

 

▲圖8:OrmStoreClinet類別

 

PostgresMessageStoreClient是ezKanban幫用Message DB所撰寫Java客戶端驅動程式,Message DB其實只有設計messages Table scheam以及撰寫了幾隻PostgreSQL資料庫的functions讓客戶端程式可以用來寫入與讀取領域事件,但它並沒有提供Java的驅動程式讓Java客戶端可以直接讀寫資料庫。圖9第28行的writeMessage方法就是ezKanban幫它所撰寫的驅動程式,只要直接呼叫這個方法就可以把領域事件寫入Message DB



▲圖9:PostgresMessageStoreClient類別

 

***

執行測試案例

實作完成TagOutboxRepository之後,改寫上一集<事件溯源(3):將Aggregate儲存至EventStoreDB>的測試案例,將TagRepository注入TagOutboxRepository,如圖10所示。

 

▲圖10:將測試案例中的tagRepository換成TagOutboxRepository實作

 

 

執行完測試案例打開PostgreSQL管理畫面,看到tag Table與messages Table分別新增一筆資料,如圖11所示。



▲圖11:從PostgreSQL管理畫面看到新增的資料

 

***

下集預告

Event Sourcing與Outbox這兩種儲存方式都搞定了,但還有一個小細節沒有說明,就是在資料庫中如何儲存「領域事件型別(Event Type)」,下一集討論這個問題。

***

友藏內心獨白:很多細節要處理。

沒有留言:

張貼留言