July 9 14:20~16:20
▲NotifyBoard實做Idempotent架構圖
前言
Teddy在<事件溯源(16):分散式系統的事件語意與Idempotent>介紹過為什麼Event Handler需要具備Idempotent,這集以ezKanban系統中產生GetBoardContent的Event Handler—NotifyBoard為例(請參考<事件溯源(10):實作Projector>),介紹如何實做Idempotent。
***
記住你做過的事
實做Idempotent可以從兩個方向著手:
- 操作本身即是Idempotent:如果一個系統的操作本身就是Idempotent,哪麼Event Handler就不需要特別處理看過的事件,只要確定事件順序正確,收到事件之後閉著眼睛執行一次即可。例如,delete操作本身是Idempotent,收到CardDeleted事件(卡片被刪除)直接套用一次即可。就算重複執行相同的CardDeleted也不會造成系統狀態錯誤。
- 記憶已處理過的事件:在一般通用系統中,不太容易把所有系統操作都設計成具備Idempotent特性,因此Event Handler需要紀錄它曾經處理過的事件代號,然後每次收到事件之後要去查看該事件是否已經處理過了。如過是,則丟棄該事件;若否,才處理該事件然後把事件代號紀錄下來。這裡有兩個地方要注意,首先事件代號需要唯一,不可重複,才可判斷是否曾經處理過。其次,處理事件造成的系統狀態改變和儲存事件這兩件事,必須要在同一個交易(transaction)中完成,否則可能造成系統狀態改變但卻沒有把處理過的事件紀錄下來,這樣下次再收到相同事件變會重新執行一次,就沒有達到Idempotent。
在本文中Teddy要採用第二種方式實做Idempotent。
***
先看測試結果
鄉民們讀到「在同一個交易中儲存系統狀態改變與事件代號」這句話,是不是有種似曾相似的感覺?沒錯,這和Teddy在<事件溯源(4):將Aggregate儲存至Outbox Store>介紹過的方法是類似的。
圖1是NotifyBoardContent的測試案例,產生一個Board,一個Workflow,三個Stage,然後新增一張卡片。
▲圖1:NotifyBoardContent測試案例
圖1測試案例所投影出的BoardContentViewModel如圖2所示。
▲圖2:BoardContentViewModel(JSON檔案)
除了在資料庫投影出BoardContentViewModel,因為NotifyBoardContent支援Idempotent,所以資料庫的Idempotent表格同時也紀錄了NotifyBoardContent所處理過由測試案例所產生的七個事件,如圖3所示。這七個事件分別是:BoardCreated、BoardMemberAdded、WorkflowCreated、StageCreated、StageCreated、StageCreated、CardCreated。
▲圖3:資料庫Idempotent表格紀錄Event Handler讀過哪些事件
***
實作
NotifyBoardContent的project方法如圖4所示,57行縮起來的switch敘述是負責投影的程式邏輯,這邊要關注的是:
- 第53~54行:呼叫boardContentStateRepository的isEventHandled方法判斷領域事件是否已經處理過,如果已經處理過就直接return。
- 第241行:更新boardContentState身上的IdempotentData資料結構,它用來記錄Event Handler正在處理哪一個領域事件,如圖5所示。
- 第242行:把boardContentState儲存到資料庫。boardContentStateRepository.save方法會在同一個交易中將boardContentState儲存在board_content表格中(圖2的那個JSON檔案),以及將IdempotentData儲存在idempotent表格中,如圖6所示。
▲圖4:NotifyBoardContent的project方法
▲圖5:IdempotentData類別
▲圖6:儲存boardContentViewData與IdempotentData要在同一個交易中完成
***
好像沒有很難?
看完上面實作方法,感覺要讓Event Handler達到Idempotent好像沒有很難。但是,還是有一些實做細節需要考慮。在ezKanban中,boardContentViewData與IdempotentData都被存放在PostgreSQL資料庫,因此可以用關聯式資料庫的transaction確保這兩個操作的狀態一致性。但如果鄉民將read model儲存在NoSQL資料庫,例如document-based NoSQL資料庫,這種資料庫不一定會提供「跨document」的交易控制功能,這時候就可能需要把處理過的事件編號儲存在代表read model狀態的document身上。
***
第一季結束
這一系列寫到這裡也就差不多了,Event Sourcing與CQRS的重點還有實做細節都交代過,之後如果還有想到什麼再補充說明。
***
友藏內心獨白:感謝收看。
沒有留言:
張貼留言