基于事件驱动架构构建微服务第9部分:处理更新
原文鏈接:https://logcorner.com/building-microservices-through-event-driven-architecture-part10-handling-updates-and-deletes/
在本文中,我將討論如何處理事件溯源系統上的更新。
在前面的步驟中,我將系統的所有業務變化存儲為事件,而不是存儲當前狀態。我通過將所有事件應用于聚合來重建當前狀態。
我已經建立了一個領域事件列表:過去發生的業務變化以通用語言表達:例如ThePackageHasBeenDeliveredToCustomer。
領域事件是不可變的,當事件發生時它不能改變。
因此,要糾正事件中的錯誤,我必須創建一個具有正確值的補償事件,例如銀行帳戶交易。
聚合記錄已提交的事件并保護業務不變量。這是事務邊界。為了處理并發,我將使用帶有版本控制的樂觀并發控制 (OCC)。
在不獲取鎖的情況下,每個事務都會驗證沒有其他事務修改了它所讀取的數據。如果數據沒有改變,則提交事務,如果數據被其他人改變,則事務回滾并可以重新啟動。
使用版本控制,用戶讀取聚合的當前狀態,然后發送帶有版本號的命令,如果版本號與聚合的當前版本匹配,則提交事務。
如果版本號與聚合的當前版本不匹配,在這種情況下,這意味著數據已被其他人更新。所以用戶應該再次讀取數據以獲得正確的版本并重試。
在本教程中,我將展示如何更新語音實體。它具有以下屬性:標題、描述、網址和類型。所以每個屬性的更新都是一個事件,應該存儲在事件存儲中。
處理領域模型的更新
處理更新標題
測試用例1:當title為null或為空時,ChangeTitle應引發ArgumentNullAggregateException:
在這里,我將測試如果Title為NullOrEmpty,則系統應該引發異常。
測試用例2:當預期版本不等于聚合版本時的ChangeTitle應該引發ConcurrencyException:
在這里我將測試如果預期版本不等于聚合版本,則系統應該引發異常。
因為我創建了一個新語音,聚合版本等于0,所以如果我將expectedVersion設置為1,測試應該會引發異常。
測試用例3:具有有效參數的ChangeTitle應應用SpeechTitleChangedEvent:
在這里我將測試,如果沒有錯誤,則應將newTitle應用于演講的標題。換句話說:Speech.Title = “更新后新標題的值”
由于Apply函數將事件應用于聚合,因此語音的標題應等于SpeechTitleChangedEvent值的標題。
ChangeTitle最終實現:
ChangeTitle的最終實現應該是這樣的。
很簡單,我的標題不為空或為空,應用SpeechTitleChangedEvent。apply函數使用事件SpeechTitleChangedEvent的值設置演講標題。
檢查聚合版本的代碼是在前面的步驟中開發的(參見aggregateroot.cs類)
public?void?ValidateVersion(long?expectedVersion) { if?(Version?!=?expectedVersion) { throw?new?ConcurrencyException($@”Invalid?version?specified?:?expectedVersion?=?{Version}??but?originalVersion?=???{expectedVersion}.”); } }處理更新DESCRIPTION、URL和TYPE
ChangeDescription、ChangeUrl和ChangeType應遵循與ChangeTitle相同的場景
處理申請更新
處理更新標題
測試用例1:當Command為空時處理更新應該引發ApplicationArgumentNullException :
在這里我將測試如果updateCommand為空,那么系統應該引發異常。
所以我應該模擬所有外部依賴項:IUnitOfWork、ISpeechRepository和IEventSourcingSubscriber
我將提供一個空命令并驗證是否引發了ApplicationArgumentNullException。
測試用例2:當語音不存在時處理更新應該引發ApplicationNotFoundException:
這里我將測試如果要更新的語音不存在,那么系統應該引發異常(ApplicationNotFoundException)。
我必須安排我的存儲庫,以便它返回帶有模擬的空語音:
moqEventStoreRepository.Setup(m?=>?m.GetByIdAsync<Domain.SpeechAggregate.Speech>(command.SpeechId)) .Returns(Task.FromResult((Domain.SpeechAggregate.Speech)null));就像這樣。
測試用例3:當命令不為空時處理更新應更新語音標題:
這里我測試一下,如果命令不為空,并且數據庫中存在要更新的語音,則應該更新標題。
驗證語音標題是否被修改的一種方法是在將其發送到存儲庫之前檢查它的值,它應該等于新標題的值:
moqSpeechRepository.Verify(m?=> m.UpdateAsync(It.Is<Domain.SpeechAggregate.Speech>(n?=> n.Title.Value.Equals(command.Title) )),Times.Once);測試用例4:當預期版本不等于聚合版本時處理更新應該引發ConcurrencyException:
在這里我將測試如果預期版本不等于聚合版本,那么系統應該引發異常。
聚合等于零,因為我實例化了一個新的語音,然后如果expectedversion不等于零,則系統應該引發ConcurrencyException。
處理倉儲更新
處理更新
測試用例1:當Speech為空時處理更新應該引發RepositoryArgumentNullException :
測試用例2:當語音不存在時處理更新應該引發NotFoundRepositoryException
測試用例3:當語音有效且存在時處理更新應執行更新
以及最終的實現
處理PRESENTATION的更新
處理更新
測試用例1:當ModelState無效時更新語音應返回BadRequest:
測試用例2:發生異常時的UpdateSpeech應引發InternalServerError
同上注冊語音(ExceptionMiddleware)
測試用例3:當ModelState有效且沒有錯誤時更新語音應該返回Ok
以及最終的實現
用POSTMAN測試
按F5并啟動postman和sql server。
讓我們啟動sql server看看發生了什么 讓我們運行一個select查詢,你可以看到[dbo].[Speech]和[dbo].[EventStore]這兩個表是空的。
讓我們啟動postman并運行一個post請求來創建一個演講:http://localhost:62694/api/speech
postman腳本在這里:LogCorner.EduSync.Command\src\Postman\BLOG.postman_collection.json
現在我應該有一個新創建的演講和一個事件LogCorner.EduSync.Speech.Domain.Events.SpeechCreatedEvent, LogCorner.EduSync.Speech.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 請注意,版本等于 0。對于每個新語音,版本應為零。
如果我檢查有效載荷,我必須看到我的事件
{ “Title”:?{ “Value”:?“Le?Lorem?Ipsum?est?simplement?du?faux?texte” }, “Url”:?{ “Value”:?“http://www.yahoo_1.fr” }, “Description”:?{ “Value”:?“Le?Lorem?Ipsum?est?simplement?du?faux?texte?employé?dans?la?composition?et?la?mise?en?page?avant?impression.?Le?Lorem?Ipsum?est?le?faux?texte?standard?de?l’imprimerie?depuis?les?années?1500,?quand?un?imprimeur?anonyme?assembla?ensemble?des?morceaux?de?texte?pour?réaliser?un?livre?spécimen?de?polices?de?texte” }, “Type”:?{ “Value”:?3 }, “AggregateId”:?“7c8ea8a0-1900-4616-9739-7cb008d37f74”, “EventId”:?“a688cc8a-ed56-4662-bbad-81e66ed917a0”, “AggregateVersion”:?0, “OcurrendOn”:?“2020-01-19T15:49:59.3913833Z” }為了更新演講的標題,我運行以下請求 http://localhost:62694/api/speech 這是一個PUT請求。
我拿到了新建語音的標識符CF17D255-9991-4B7B-B08E-F65B54AA9335 讓我們從sql復制并將其粘貼到請求正文中。
好的,現在我可以運行put查詢
回到sql server驗證結果
SELECT * FROM [dbo].[Speech]
SELECT * FROM [dbo].[EventStore]
我應該看到更新的標題和一個新事件LogCorner.EduSync.Speech.Domain.Events.SpeechTitleChangedEvent。
版本應為 1,有效負載應為更新事件
{ “Title”:?“UPDATE_1__Le?Lorem?Ipsum?est?simplement?du?faux?texte”, “AggregateId”:?“7c8ea8a0-1900-4616-9739-7cb008d37f74”, “EventId”:?“de253f69-ea89-4a54-8927-e09553cc43c7”, “AggregateVersion”:?1, “OcurrendOn”:?“2020-01-19T15:55:14.1734365Z” }本文的源代碼可在此處獲得 (Feature/Task/EventSourcingApplication)
https://github.com/logcorner/LogCorner.EduSync.Speech.Command/tree/Feature/EventSourcingHandlingUpdates
總結
以上是生活随笔為你收集整理的基于事件驱动架构构建微服务第9部分:处理更新的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# 使用Timer控件设置时间间隔
- 下一篇: Envoy实现.NET架构的网关(四)集