# Domain Implementation
The first part of an event sourcing application is your domain. This follows a similar pattern as Domain Driven Design (DDD). This example will cover building a domain implementation of a customers account.
# Define an Aggregate Root
The next step is to define an AggregateRoot
. This is an object that can change state and add new events, this can also replay events to build up current state. Let's define an AggregateRoot
for a CustomerAccount
.
using EventSourcingCustomerAccount.Models;
using Microsoft.Azure.CosmosEventSourcing.Aggregates;
using Microsoft.Azure.CosmosEventSourcing.Events;
namespace EventSourcingCustomerAccount.Aggregates;
public class CustomerAccount : AggregateRoot
{
public string Username { get; private set; } = null!;
public string Email { get; private set; } = null!;
public string FirstName { get; private set; } = null!;
public string Surname { get; private set; } = null!;
public CustomerAddress? Address { get; private set; }
public CustomerAccount(
string username,
string email,
string firstName,
string surname)
{
//TODO: Create Event
}
protected override void Apply(DomainEvent domainEvent)
{
throw new NotImplementedException();
}
}
This above example defines a class that represents a customer's account, note that this class uses the provided base class AggregateRoot
. This class provides a few useful methods that we will need to use shortly.
# Define a Domain Event
The next step is to define a DomainEvent
which represents a CustomerAccount
being created. We will name this event CustomerAccountCreated
. The implementation of this is shown below.
using Microsoft.Azure.CosmosEventSourcing.Events;
namespace EventSourcingCustomerAccount.Events;
public record CustomerAccountCreated(
string Username,
string Email,
string FirstName,
string Surname) : DomainEvent;
This event in the above example is defined as a record
this means by nature this event is immutable, this is a good fit as once an event has happened it should not be changed.
It also makes use of a provided type by the library. The DomainEvent
offers a few things:
- Gives this event a name, defaults to the name of the event, defaults to
this.GetType().Name
. - Defines a
Sequence
number which helps to keep track of the order events are applied. - Defines an
OccurredUtc
time to make when the event occurred.
Tip
DomainEvent
's are usually named in the past test as they represent something that has happened.
# Add a Domain Event
The next step is to add that event to the AggregateRoot
there is a method provided on the utilized base type that let's us do this. It takes care of adding the OccurredUtc
time stamp. It also sequentially increments the Sequence
number on the event. Going back to the CustomerAccount
example we can add this event in the constructor as below.
namespace EventSourcingCustomerAccount.Aggregates;
public class CustomerAccount : AggregateRoot
{
// Props excluded for brevity.
public CustomerAccount(
string username,
string email,
string firstName,
string surname,
CustomerAddress customerAddress)
{
AddEvent(new CustomerAccountCreated(
username,
email,
firstName,
surname));
}
// Apply method excluded for brevity.
}
Tip
In production applications this constructor should have guards, these would validate that the username was not empty for example.
# Applying a Domain Event
Once a DomainEvent
has been added the AggregateRoot
will call the Apply(...)
method that it enforces a consumer to implement. The easiest way to implement this is using a switch
statement and pattern matching. This can then delegate the applying of that method off to another method for the specific DomainEvent
that has been applied. See the Apply(...)
methods used to apply the CustomerAccountCreated
domain event.
namespace EventSourcingCustomerAccount.Aggregates;
public class CustomerAccount : AggregateRoot
{
// Props and Ctor excluded for brevity
protected override void Apply(DomainEvent domainEvent)
{
switch (domainEvent)
{
case CustomerAccountCreated created:
Apply(created);
break;
}
}
private void Apply(CustomerAccountCreated created)
{
Username = created.Username;
Email = created.Email;
FirstName = created.FirstName;
Surname = created.Surname;
}
}
Notice
It is important that an Apply(...)
method call never fails. This is because this event has happened and is a fact. The behavior method that calls AddEvent(...)
should be responsible for protecting any invalid events from being adding to the AggregateRoot
.