July 26 15:52~16:39
▲圖1:樂觀鎖測試案例
前言
這系列文章原本是Teddy為了製作【事件溯源與命令查詢責任分離架構實作班】課程範例而撰寫,課程範例已經完成,這系列文章也已經連載結束。前幾天Teddy回頭把課程範例改成新的寫法,修改之前先跑測試,居然有一個錯誤!
仔細一看才想起來Teddy之前練習的時候注入在測試案例中InMemoryTagRepository,它沒有支援樂觀鎖,所以原本的樂觀鎖定測試案例會失敗,只要換回正常的Repository就好了,這個問題以前也遇到過。
但是這次Teddy突然想到:「為什麼InMemoryRepository不能支援樂觀鎖?」花了幾分鐘改一下Code,測試案例就通過了。今天就追加一篇,談如何讓InMemoryRepository支援樂觀鎖。
***
實作樂觀鎖
Teddy在<事件溯源(7):樂觀鎖>中介紹過如何在關聯式資料庫與事件溯源資料庫實作樂觀鎖,基本上就是要在Aggregate身上加一個Version欄位,每次儲存Aggregate的時候比對它身上Version的數值與資料庫中的數值是否相等。如果相等,就代表這個Aggregate上次從資料庫讀出之後並沒有其他人寫入,因此它目前的版本是最新的,可以直接儲存到資料庫中。反之,則代表目前Aggregate的版本比較舊,無法儲存,系統要丟出樂觀鎖定失敗例外。
請參考圖1測試案例,從tagRepository根據相同的tagId拿出tagV1與tagV2兩個相同的物件。先把tagV1改名後儲存起來,接著再儲存tagV2,此時tagV2身上的Version數值會小於tagRepository所儲存的數值,因此會丟出RepositorySaveException例外。
首先修改InMemoryTageRepository的findById方法,如圖2所示。原本InMemoryTagRepository將Tag儲存在List裡面,findById回傳的記憶體中Tag的參考(reference)。這種直接回傳記憶體參考物件無法測試樂觀鎖,因為圖1中tagV1和tagV2會參考到同一個tag,也就是說改了tagV1會同時改變tagV2的值。所以findById要改成回傳一個新的Tag物件,而不是原本Tag物件的參考。
▲圖2:修改InMemoryTagRepository的findById方法以支援樂觀鎖
其次修改InMemoryTagRepository的save方法,如圖3所示。如果要儲存的tag已經存在InMemoryTagRepository,而且它的版本不等於記憶體中的版本,則丟出RepositorySaveException。反之,先把tag從記憶體中移除(如果不移除,相同的Tag會出現在InMemoryTagRepository兩次),然後將它的版本加1,然後把它儲存起來,最後清掉tag身上的領域事件。
▲圖3:修改InMemoryTagRepository的sava方法以支援樂觀鎖
就這樣,這麼簡單。
***
結論
本集介紹如何讓InMemoryRepository也具備樂觀鎖,但在這裡Teddy實作的InMemoryTagRepository只支援State Sourcing的儲存方式,並沒有支援Event Sourcing。如果是要實作InMemoryEventSourcingRepository,基本上也不會太困難,應該只需要:
- 把資料結構由List改成Map<String, List<DomainEvent>>,Map的Key是Event Stream Name,Value是Aggregate的領域是件。至於Aggregate的版本就是List<DomainEvent>的大小。
- 在儲存Aggregate的時候,不需要更新版本號碼,因為讀取(fndById)的時候Aggregate的版本號碼就是它所屬的List<DomainEvent>的大小。
InMemoryEventSourcingRepository的實作就交給鄉民自行練習。
***
友藏內心獨白:這一集算番外篇。