Building microservices through Event Driven Architecture part09: Handling updates

This tutorial is the  09th part of a series : Building microservices through Event Driven Architecture.

The previous step is about building microservices through Event Driven Architecture part8: Implementing EventSourcing on Application : https://logcorner.com/building-microservices-through-event-driven-architecture-part8-implementing-eventsourcing-on-application/

During this journey, I will  talk about how to handle updates on a Event Sourcing System.

So , in the previous steps , I have stored all business changes of the system as events instead of storing the current state.  and I  rebuild the current state by applying all the events to the aggregate.

I have builded a list of domain events :  business changes happened in the past expressed in the  Ubiquitous Language : ex ThePackageHasBeenDeliveredToCustomer.

Domain Events are immutable, when a event happen it cannot be changed.

So, to correct a mistake on an event, I have to create a compensation event with correct values like bank account transactions.

The aggregate records committed events and protects  business invariants. It is the transaction boundary.  To deal  with concurrency  I will use Optimistic Concurrency Control (OCC)   with versioning.

Without acquiring locks each transaction verifies that no other transaction has modified the data it has read. If the data have not been changed, then the transaction is committed,  if the data have been changed  by someone else, then the transaction rolls back and can be restarted.

With versionning , the user reads the current state of the aggregate,  then send commands with the version number, if the version number match the current version of the aggregate then the transaction is committed.

If the version number does not match the current  version of the aggregate, in this case it means that the data has  been updated by someone else. So the user should read again the data to get the correct version and retry.

In this tutorial, I will shwo how to update the Speech Entity. It has the following properties  : Title, Description, Url and Type.  So the update of each property is an event and should be stored on event store.

HANDLING UPDATE ON DOMAIN MODEL

HANDLING UPDATE TITLE

TEST CASE 1 : ChangeTitle  when title is null or empty should raise ArgumentNullAggregateException:

Here I will test that if the Title is NullOrEmpty then, the system should raise an exception.

ChangeTitleNullTitle

TEST CASE 2 : ChangeTitle  when expected version is not equals to aggregate version  should raise ConcurrencyException:

Here I will test that if the expectedVersion is not equals to the aggregateVersion then, the system should raise an exception.

Because I create a new speech, the aggregate version is equals to zero , so if I set expectedVersion to one, the test should raise an exception.

ChangeTitleInvalidVersion

TEST CASE 3 : ChangeTitle  with valid arguments  should apply SpeechTitleChangedEvent :

Here I will test that if no errors , then the newTitle  should be applied to the title of the speech. In other words : Speech.Title = “value of new title after updates”

Because the Apply function applies the event to the aggregate, the Title of the speech should be equals to the title of the value of SpeechTitleChangedEvent .

ChangeTitleValidArguments

 ChangeTitle  Final implementation :

The final implementation of ChangeTitle should look like this.

Very simple, I the title is not null or empty, apply a SpeechTitleChangedEvent .  The apply function sets the speech title with the value of the event SpeechTitleChangedEvent .

The code that checks the version of the aggregate was developed in the previous steps ( see the aggregateroot.cs class)

public void ValidateVersion(long expectedVersion)
{
if (Version != expectedVersion)
{
throw new ConcurrencyException($@”Invalid version specified : expectedVersion = {Version}  but originalVersion =   {expectedVersion}.”);
}
}

SpeechTitleChangedEvent

HANDLING UPDATE DESCRIPTION, URL AND TYPE

ChangeDescription, ChangeUrl and ChangeType should follow the same scenario as ChangeTitle

HANDLING UPDATE ON APPLICATION

HANDLING UPDATE TITLE

TEST CASE 1 : Handling Update when Command is null  should raise ApplicationArgumentNullException :

Here I will test that if the updateCommand is null, then the system should raise an exception.

So I should mock all external dependencies : IUnitOfWork, ISpeechRepository and IEventSourcingSubscriber

I will provide a null command and verify that a ApplicationArgumentNullException  is raised.

HandlingUpdateWhenCommandIsNullShouldRaiseApplicationArgumentNullException

TEST CASE 2 : Handling update when speech does not exist  should raise ApplicationNotFoundException:

Here I will test that if the speech to update does not exist, then the system should raise an exception (ApplicationNotFoundException).

I have to arrange my repository so that it returns a null speech with mock  :

moqEventStoreRepository.Setup(m => m.GetByIdAsync<Domain.SpeechAggregate.Speech>(command.SpeechId))
.Returns(Task.FromResult((Domain.SpeechAggregate.Speech)null));

and that’s it.

HandlingUpdateWhenSpeechDoesNotExistShouldRaiseApplicationNotFoundException

TEST CASE  3 : Handling Update when Command is not null should update speech Title :

Here I will test that if the command is not null and the speech to update exists in the database , then the title should be updated.

A way to verify that the Speech Title is modified is to check it’s value before sending it to repository, it should be equals to the value of  the new title :

moqSpeechRepository.Verify(m =>
m.UpdateAsync(It.Is<Domain.SpeechAggregate.Speech>(n =>
n.Title.Value.Equals(command.Title)
)),Times.Once);

HandlingUpdateWhenCommandIsNotNullShouldUpdateSpeechTitle_1

HandlingUpdateWhenCommandIsNotNullShouldUpdateSpeechTitle_2

TEST CASE  4 : Handling Update when Expected version is not equal to aggregate version should raise ConcurrencyException :

Here I will test that if the expectedVersion is not equals to the aggregateVersion, then the system should raise an exception.

The aggregate is equals to zero, because I instanciate a new speech , then if  expectedversion is not equals to zero, the systme should raise a ConcurrencyException.

HandlingUpdateWhenExpectedVersionIsNotEqualToAggregateVersionShouldRaiseConcurrencyException

HANDLING UPDATE ON REPOSITORY

HANDLING UPDATE 

TEST CASE 1 : Handling Update when Speech is null  should raise RepositoryArgumentNullException :

HandlingUpdateWhenSpeechIsNullShouldRaiseRepositoryArgumentNullException

TEST CASE 2 : Handling Update when the speech  does not exist should raise NotFoundRepositoryException 

HandlingUpdateWhenTheSpeechDoesNotExistShouldRaiseRepositoryNotFoundException

 TEST CASE 3 : Handling Update when the speech  is valid and exist should perform update

HandlingUpdateWhenTheSpeechIsValidAndExistShouldPerformUpdate

And the final implementation

UpdateAsyncFinal

HANDLING UPDATE ON PRESENTATION

HANDLING UPDATE 

TEST CASE 1 : Update Speech When ModelState Is Invalid Should Return BadRequest :

UpdateSpeechWhenModelStateIsInvalidReturnBadRequest

TEST CASE 2 : UpdateSpeech When An Exception Occurred Should Raise InternalServerError

Idem with register speech (ExceptionMiddleware)

TEST CASE 3 : Update Speech When ModelState Is Valid With No Errors Should Return Ok

UpdateSpeechWhenModelStateIsValidWithNoErrorsShouldReturnOk

And the final implementation

Coverage

TEST WITH POSTMAN

Hit F5 and start postman and sql server.

Let’s start sql server and see what’s going on
Here I have to table’s [dbo].[Speech] and [dbo].[EventStore] , let us run a select query , you can see that these 2 tables are empty.

EmptyResults

Let us start postman and run a post request to create a speech : http://localhost:62694/api/speech

Ok lets go

the postman scripts are here : LogCorner.EduSync.Command\src\Postman\BLOG.postman_collection.json

postspeech

Now I should have a newly created speech and a event LogCorner.EduSync.Speech.Domain.Events.SpeechCreatedEvent, LogCorner.EduSync.Speech.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Note that the version is equals to 0.
For each new speech, the version should be equal to zero.

If I inspect the payload, I must see my event

InsertedResult

{
“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”
}

To update the title of the speech, i run the following request http://localhost:62694/api/speech
it is a put request .

I grab the identifier of the newly created speech CF17D255-9991-4B7B-B08E-F65B54AA9335
Let us copy from sql and paste it on request body.

Ok, Now i can run the put query

putspeech

Come back to sql server to verify the result

SELECT * FROM [dbo].[Speech]

SELECT *  FROM [dbo].[EventStore]

I should see the updated title and a new event LogCorner.EduSync.Speech.Domain.Events.SpeechTitleChangedEvent.

The version should be 1 and the payload should be the update event

{
“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”
}

updatedResult

Cource code of this article is available here  (Feature/Task/EventSourcingApplication)

https://github.com/logcorner/LogCorner.EduSync.Speech.Command/tree/Feature/EventSourcingHandlingUpdates

Regards

Gora LEYE

I'm a microsoft most valuable professional (MVP) .NET Architect and Technical Expert skills located in Paris (FRANCE). The purpose of this blog is mainly to post general .NET tips and tricks, www.masterconduite.com Gora LEYE

Support us

BMC logoBuy me a coffee