Building microservices through Event Driven Architecture part6: Implementing EventSourcing on Domain Model

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

The previous step is about docker using SQL Server Linux implementation : https://logcorner.com/building-microservices-through-event-driven-architecture-part5-dockerization-web-api-core-and-sql-server-linux/

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

The fundamental idea of Event Sourcing is that of ensuring every change to the state of an application is captured in an event object, and that these event objects are themselves stored in the sequence they were applied for the same lifetime as the application state itself.  

Martin Fowler  https://martinfowler.com/eaaDev/EventSourcing.html

So you have to store all business changes to the system instead of storing the current state. and you will rebuild the current state by applying all the events to the aggregate.

Eventsourcing and CQRS are architectural patterns , so you do not need a specific language or framework.
CQRS: Separate everything about writing or changes (command) from everything about reading of the state of the Domain Model (Query).
By separating these two logics, you can optimize them separately.

  • A Command writes events into an eventstore, and these events should be replayed to rebuild the state of the aggregate.
  • A Query request the ReadModel to get the current state.

In the DDD world , you can build 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, you have to create a compensation event with correct values like bank account transactions.

The aggregate record committed events and protect  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 not the transaction is committed,  if yes 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 version of the aggregate then the transaction is committed.

If the version number does not match the 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.

Optimistic concurrency control (OCC)

EVENT SOURCING INTERFACES

Let us define some interfaces  IDomainEvent and IEventSourcing

IDomainEvent

EventId is the identifier of the event

IEventSourcing

  • Version is the  current version of the aggregate
  • ValidateVersion is a function that should raise an concurrency exception if the provided version is not equals to the current version of the aggregate.
  • ApplyEvent is a function that applies an event to the aggragate .
  • GetUncommittedEvents : a function that lists the history of all the events (not yet persisted) of the aggregate
  • ClearUncommittedEvents : a function that clear the list of uncommitted events of the aggregate

EVENT SOURCING  IMPLEMENTATION

Let us use the AggregateRoot abstract class to implement IEventSourcing interface

AggregateRoot

Some tests fail because of this change

UnitTestFails

Let us implement my first test

VALIDATE VERSION IMPLEMENTATION

ValidateVersion should raise an concurrency exception if the given version is not equals to the current version of the aggregate

ValidateVersionNotImplemented

So the first test is  ValidateVersionWithInvalidExpectedVersionShouldRaiseConcurrencyException

TEST CASE 1 : ValidateVersion With Invalid Expected Version Should Raise Concurrency Exception :

ValidateVersionWithInvalidExpectedVersionShouldRaiseConcurrencyExceptionBefore

Let us define StubEventSourcing and ConcurrencyException class to fix build errors.

ValidateVersion is a method of an abstract class AggregateRoot , so to test it I can create a StubEventSourcing  class that  inherits from AggregateRoot.

SubEventSourcing

ConcurrencyException

Here is the final implementation of the test.

ValidateVersionWithInvalidExpectedVersionShouldRaiseConcurrencyExceptionTestFails

The test fail so let’s implement the method to make it succeed.

ValidateVersionImplementedEnd

The test succeeded.

ValidateVersionWithInvalidExpectedVersionShouldRaiseConcurrencyExceptionEnd

TEST CASE 2 :  ValidateVersion With Valid Expected Version Then Expected Version Should Be Equals To Aggregate Version

ValidateVersionWithValidExpectedVersionThenExpectedVersionShouldBeEqualsToAggregateVersion

Then Code Coverage of ValidateVersion is 100%.

ValidateVesrionCoverrage

APPLY EVENT IMPLEMENTATION

TEST CASE 3 :  ApplyEvent Should Populate Aggregate Properties With Event Properties

To rebuild aggregate state, ApplyEvent will be called for each event.

So I verify that if I apply an event to an empty aggregateroot, then the properties of the event should be applied to the given aggregate.

ApplyEventShouldPopulateAggregatePropertiesWithEventPropertiesBefore

I have to create an Event class with properties, Title, Url, Description ,Type and an empty aggregateroot, and verify that when applying this event, the equivalent properties of the aggregateroot are updated correctly.

The aggregateroot of our domain model is Speech Entity, so I can create en empty speech object like this.

CreateNewAggregate

Let us use our speechCreatedEvent.

So I can create a Event abstract class that implement IDomainEvent and every event should inherits from it.

Event

  • AggregateId is the identifier of the aggregateroot
  • EventId is the identifier of the event
  • OccurredOn is the the date on which the event occurred

SpeechCreatedEvent

Here is the final implementation of the test.

ApplyEventShouldPopulateAggregatePropertiesWithEventProperties_Final

I can implement ApplyEvent like this :

If the list of uncommitted events does not contains the given event, then I apply this event to aggregate

ApplyEventFinal

  • ((dynamic)this).Apply((dynamic)@event);  with raise the following method

ApplySpeechCreatedEvent

The test succeeded and codecoverage is OK.

CoverageApply

 GETUNCOMMITED EVENTS IMPLEMENTATION

TEST CASE 4 : GetUncommittedEvents Of New Aggregate Should Return List Of IDomainEvent

Here I have to verify that the GetUncommittedEvents method should return the list of uncommitted events.

For now I have not yet implement AddDomainEvent that add an event to the list of uncommitted events, so for this test I simply verify that the list of uncommitted events is empty and is of type IEnumerable<IDomainEvent>.

GetUncommittedEventsOfNewAggregateShouldReturnListOfIDomainEventBefore

GetUncommittedEvents

ADD DOMAIN EVENT IMPLEMENTATION

TEST CASE 5 : AddDomainEvent With Invalid Version Should Raise ConcurrencyException

AddDomainEvent belongs to Aggreroot class and is protected , so only aggregateroot entities can raise an event.

To test this method I can proceed as follow :

I create a  StubEventSourcing class that implement a ExposeAddDomainEvent method to expose  AddDomainEvent method.

Here is the final implementation of the test

AddDomainEventWithInvalidVesrionShouldRaiseConncurrencyExceptionBefore

I have updated the AddDomainEvent method , it takes now an event and a version.

Let us call ValidateVersion to make test succeed.

AddDomainEvent

TEST CASE 6 :  AddDomainEvent With Valid Version Then Version Of Event Should Be Equals To Current Version Of Aggregate

Here I have to verify that the version of the event equals to the current version of the aggregate, so I will create a method BuilVersion  that sets the version of the event with correct version. and use  @event.BuildVersion(_version + 1);  on AddDomainEvent method.

Here is the event I use for tests.

SubEvent

BuilVersion set the aggregateVersion property of the event with the current aggregateroot version incremented by 1.

Test already fails because AggregateVersion is -1 and EventVersion is 0.

AddDomainEventWithValidVersionThenVersionOfEventShouldBeEqualsToCurrentVerSionOfAggregate

So I call ApplyEvent using event and new event version to make it pass.

BuildVersion

Call of ApplyEvent.

AddDomainEvent

Test fais because I need to implement ApplyEvent of type @event

ErrorApplyEent

So let us implement ApplyEvent(SubEvent subEvent)

EventSourcingStubApply

And everything pass

MediaFileCreatedEventApply

AddDomainEvent

TEST CASE 7 : AddDomainEvent With Valid Version Then Event Should Be Applied To Aggregate

Here I verify that when applying Event, then the event is applyed to the aggregate

SubEvent

TEST CASE 8: AddDomainEvent With Valid Version Then UncommittedEvents Should Be Single

Here I verufy that when I apply an event , then the event should be the only event that belongs to the list of uncommitted events.

AddDomainEventWithValidVersionThenUncommittedEventsShouldBeSingle

AddUncommittedEvent

 CLEAR UNCOMMITED EVENTS IMPLEMENTATION

TEST CASE 9 : ClearUncommittedEvents Then Uncommitted Events Should Be Empty

Here I verify that when I clear the list of uncommitted events, then the list should be empty

ClearUncommittedEventsThenUncommittedEventsShouldBeEmpty

ClearUncommittedEvents

Here is codecoverage.

coverageClearUnCommittedEvent

FIX SOME TESTS ET CHECK CODE COVERAGE

I have to remove _mediaFileItems.Add(mediaFile);  from  CreateMedia(MediaFile mediaFile, int originalVersion) because it is now implemented below

ApplyEventCreateMedia

Code coverage is Ok (99%).

FInalCover

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

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

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