Building microservices through Event Driven Architecture part7: Implementing EventSourcing on Repositories

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

The previous step is about Building microservices through Event Driven Architecture part6: Implementing EventSourcing on Domain Model : https://logcorner.com/building-microservices-through-event-driven-architecture-part7-event-sourcing-core-domain/

During this journey, I will  talk about Event Sourcing implementation on Repository.

The repository is responsible for adding events to the eventstore  and also retrieving all events from the event store.

When the aggregate is saved, then all uncommitted events related to that aggregateroot  are added to the eventstore table.

The eventstore table is an append only table ( update and delete are not allowed ).

The schema of the eventstore table will look like this :

  • Id is  the primary key
  • Version is the version of the aggregate
  • AggregateId is the identifier of the aggregate
  • Name is the name of the event : 2@735f8407-16be-44b5-be96-2bab582b5298
  • TypeName is the type of the event : LogCorner.EduSync.Speech.Domain.Events.Speech.SpeechCreatedEvent, LogCorner.EduSync.Speech.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
  • OccurredOn is the event date
  • PayLoad is the event stream :

{

“Title”: { “Value”: “Introducing Azure Cosmos DB” },
“Url”: { “Value”: “https://azure.microsoft.com/en-us/resomurcejjjnns/videos/azure-friday-introducing-azurkke-cosmos-db_g5/” },
“Description”: { “Value”: “Kirill Gavrylyuk stops by Azure Friday to talk Cosmos DB with Scott Hanselman. Watch quick overview of the industry’s first globally distributed multi-model database service followed by a demo of moving an existing MongoDB app to Cosmos DB with a single config change.” },
“Type”: { “Value”: 2 },
“AggregateId”: “735f8407-16be-44b5-be96-2bab582b5298”,
“EventId”: “6eb58cb4-da5e-46d4-8325-e742a20935ab”,
“AggregateVersion”: 0,
“OcurrendOn”: “2019-09-08T10:55:48.5528117Z”
}

  • IsSync : a boolean that indicates whether the event is synchronized or not

To rebuild the current state of the aggregate, we have to read all events related to a given aggregateId and then call a function LoadFromHistory. This function belongs to the AggregateRoot class and should apply all events to the aggregate.

EVENT SOURCING INTERFACES

Let us define IEventStoreRepository  interfaces

IEventStoreRepository

  • GetByIdAsync is a function that retrieves all events related to the aggregate from the event store.
  • AppendAsync is a function that append an event to  the event store

EVENT SOURCING  IMPLEMENTATION

Let us define EventStore class

  • Id is the identifier of the event stream
  • Version is the current  aggregate version
  • AggregateId is the identifier of the aggregate
  • Name is the Name of the  event stream
  • TypeName is the full type of the event (ex : LogCorner.EduSync.Speech.Domain.Events.Speech.SpeechCreatedEvent, LogCorner.EduSync.Speech.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null)
  • OccurredOn is the date on which the event occurred
  • SerializedBody is the event serialized in Json
  • AppendAsync is a function that indicates whether the event is synchronized or not yet.

APPENDASYNC  IMPLEMENTATION

So the first test is  AppendAsync should append an event on eventstore

TEST CASE 1 : AppendAsync should append an event on eventstore :

Here I have to mock a Context, create a instance of IEventStoreRepository and call AppendAsync with an EventStore object, then the context should have a set of EventStore with a single element and that element should be equals to the eventstore object I passed in argument of  AppendAsync.

The test may looks like this :

But instead of using moq I will use entityframeworkcore inmemory database wich is a simpler way of Database Unit Testing.

So I can instanciate an inmemory context like this :

moqContext

Next step is to create a EventStoreRepository class

EventStoreRepository

Then add a   DbSet<EventStore> EventStore  property to DatabaseContex class

DataBaseContext

The code compiles but test fails because DbSet<EventStore> EventStore is null.

So let us config some mappings

OnModelCreating

EventStoreEntityTypeConfiguration class has reponsibility to map EventStore class to EventStore database table

EventStoreEntityTypeConfiguration

And the final test look like this :

FinalTest

GETBYIDASYNC IMPLEMENTATION

GetByIdAsync retrieves all events related to the aggregate from the event store.

Here I have to create an empty aggregate of type Speech, read all events from the eventstore related to it and finally apply the events.

IInvoker

 TEST CASE 2 :   CreateInstance Of AggregateRoot Should Return An Empty Aggregate

CreateInstanceOfAggregateRootShouldReturnEmptyAggregateBefore

Let us create an Invoker class with the responsability to create an empty aggregateroot

Invoker

And the test pass

 TEST CASE 3 :  GetByIdAsync With BadAggregateId Should Raise BadAggregateIdException

Given an  bad aggregateId ( ex : empty aggregateId), GetByIdAsync  should  raise an exception (BadAggregateIdException)

GetByIdAsyncWithBadAggregateIdShouldRaiseArgumentNullException

TEST CASE 4 : GetByIdAsyncWithNullInstanceOfAggregateShouldRaiseNullInstanceOfAggregateIdException

Given an  aggregateId that does not exist ( ex : empty aggregateId), GetByIdAsync  should  raise an exception (NullInstanceOfAggregateIdException)

GetByIdAsyncWithNullInstanceOfAggregateShouldRaiseNullInstanceOfAggregateIdException

GetByIdAsync

TEST CASE 5 : GetByIdAsyncWithoutEventsShouldReturnEmptyList

if there is no event related to a given aggregateId, then GetByIdAsync should return an empty list

GetByIdAsyncWithoutEventsShouldReturnEmptyList

GetByIdAsync

TEST CASE 6: GetByIdAsyncWithEventsShouldReturnTheCurrentStateOfAggregate

Here , I have to read all events related to the given aggregateId from the event store , rebuild the aggregate state and returns it.

To reach this goal, I have to deserialize the event which is in json structure according to the event type.

TEST CASE 6.1 : Deserialize Event Stream should return an event

Here I will create a function that deserialize a json string to an Event object

IEventSerializer

DeserializeEventStreamShouldReturnAnEvent

JsonEventSerializer

TEST CASE 6.2 : GetByIdAsyncWithEventsShouldReturnTheCurrentStateOfTheAggregate

Here, I will finish the implementation of my function

GetByIdAsyncWithEventsShouldReturnTheCurrentStateOfTheAggregate

GetByIdAsyncFull

codecoverage

source code of this article is available here  (Feature/Task/EventSourcingRepository)

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

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