Building Microservices Through Event-Driven Architecture, Part 6: Implementing EventSourcing on Domain Model
In part six of this popular series, Leye discusses Event Sourcing implementation on Domain Model in microservices.
Join the DZone community and get the full member experience.
Join For Free
"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 FowlerYou 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.
You may also like: Building Microservices Through Event-Driven Architecture, Part 5: Dockerization
CQRS: Separate everything about writing or changes (command) from everything about the 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 event store, and these events should be replayed to rebuild the state of the aggregate.
- A Query requests the ReadModel to get the current state.
In the DDD world, you can build a list of Domain Events: business changes that happened in the past expressed in the Ubiquitous Language: ex ThePackageHasBeenDeliveredToCustomer.
Domain Events are immutable when an event happens and it cannot be changed.
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 versioning, the user reads the current state of the aggregate, then sends commands with the version number, if the version number matches 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.
Event Sourcing Interfaces
Let us define some interfaces IDomainEvent and IEventSourcing.

EventId is the identifier of the event.

- Version is the current version of the aggregate.
- ValidateVersion is a function that should raise a concurrency exception if the provided version does not equal to the current version of the aggregate.
- ApplyEvent is a function that applies an event to the aggregate.
- 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 the IEventSourcing interface.

Some tests fail because of this change.

Let us implement my first test.
Validate Version Implementation
ValidateVersion should raise a concurrency exception if the given version does not equal to the current version of the aggregate.

The first test is: ValidateVersionWithInvalidExpectedVersionShouldRaise
ConcurrencyException
Test Case 1: ValidateVersion With Invalid Expected Version Should Raise Concurrency Exception

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.


Here is the final implementation of the test.

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

The test succeeded.

Test Case 2: ValidateVersion With Valid Expected Version Then Expected Version Should Be Equals to Aggregate Version

Then Code Coverage of ValidateVersion is 100%.

Apply Event Implementation
Test Case 3: ApplyEvent Should Populate Aggregate Properties With Event Properties
To rebuild the aggregate state, ApplyEvent will be called for each event.
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.

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 an empty speech object like this.

Let us use our speechCreatedEvent.
So I can create an Event abstract class that implement IDomainEvent and every event should inherit from it.

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

Here is the final implementation of the test.

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.

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

The test succeeded and code coverage is OK.

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 adds 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>.


Add Domain Event Implementation
Test Case 5: AddDomainEvent With Invalid Version Should Raise ConcurrencyException
AddDomainEvent belongs to the 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 implements an ExposeAddDomainEvent method to expose the AddDomainEvent method.

Here is the final implementation of the test.

I have updated the AddDomainEvent method, it takes now an event and a version.
Let us call ValidateVersion to make test succeed.

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 the correct version and use @event.BuildVersion(_version + 1); on AddDomainEvent method.

Here is the event I use for tests.

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.

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

Call of ApplyEvent.

The test fails because I need to implement ApplyEvent of type @event.

Let's implement ApplyEvent (SubEvent subEvent).

Everything passes.


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 applied to the aggregate.



Test Case 8: AddDomainEvent With Valid Version Then UncommittedEvents Should Be Single
Here I verify that when I apply an event, then the event should be the only event that belongs to the list of uncommitted events.


Clear Uncommitted 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.


Here is code coverage.

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

Code coverage is Ok (99%).

The source code of this article is available on GitHub (Feature/Task/EventSourcingCoreDomain).
Regards!
Further Reading
Building Microservices Through Event-Driven Architecture, Part 1: Application-Specific Business RulesPublished at DZone with permission of Gora LEYE, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Observability Architecture: Financial Payments Introduction
-
How to LINQ Between Java and SQL With JPAStreamer
-
Top 10 Pillars of Zero Trust Networks
-
Merge GraphQL Schemas Using Apollo Server and Koa
Comments