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
- 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 :
Next step is to create a EventStoreRepository class
Then add a DbSet<EventStore> EventStore property to DatabaseContex class
The code compiles but test fails because DbSet<EventStore> EventStore is null.
So let us config some mappings
EventStoreEntityTypeConfiguration class has reponsibility to map EventStore class to EventStore database table
And the final test look like this :
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.
TEST CASE 2 : CreateInstance Of AggregateRoot Should Return An Empty Aggregate
Let us create an Invoker class with the responsability to create an empty aggregateroot
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)
TEST CASE 4 : GetByIdAsyncWithNullInstanceOfAggregateShouldRaiseNullInstanceOfAggregateIdException
Given an aggregateId that does not exist ( ex : empty aggregateId), GetByIdAsync should raise an exception (NullInstanceOfAggregateIdException)
TEST CASE 5 : GetByIdAsyncWithoutEventsShouldReturnEmptyList
if there is no event related to a given aggregateId, then GetByIdAsync should return an empty list
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
TEST CASE 6.2 : GetByIdAsyncWithEventsShouldReturnTheCurrentStateOfTheAggregate
Here, I will finish the implementation of my function
source code of this article is available here (Feature/Task/EventSourcingRepository)
Regards