DDD & CQRS & Event Sourcing. Part 3: Test Domain Model

Before we start, I recommend checking my previous post where I described designing a domain model with events and which benefits it brings for business and engineers.

Let’s start with the basic class for domain model, let’s call it Aggregate (you could find more about this keyword in DDD here):

public abstract class Aggregate
{
public Guid Id { get; private set; }
private readonly IList<object> _events = new List<object>();

public ICollection<object> DequeueEvents()
{
    var events = _events.ToList();
    _events.Clear();
    return events;
}

private void Enqueue(object @event)
{
    _events.Add(@event);
}

protected void Process(object @event)
{
    Type thisType = GetType();
    if (thisType == null) throw new NotSupportedException($"Current this type is null!");
    
    MethodInfo methodInfo = thisType.GetMethod("Apply",  BindingFlags.NonPublic | BindingFlags.Instance, Type.DefaultBinder, new [] { @event.GetType()}, null );
    if (methodInfo == null) throw new NotSupportedException($"Missing handler for event {@event.GetType().Name}");
    
    try
    {
        methodInfo.Invoke(this, new[] { @event } );
    }
    catch(TargetInvocationException ex)
    {
        ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
    }
    Enqueue(@event);
}
}

As you could see, we have 3 main methods here:

  • Enqueue – simply add a new event to private event collection
  • Dequeue – returns the private event collection and clean the queue, so Aggregate event collection could not be changed in any way from outside.
  • Process – Try to find and call a proper method Apply (by method parameter type) and add anew event to the private event collection.

Also you could see private _events variable for storing events and Id property, which is common for every Aggregate object.

So our specific Aggregate will look like this:

public class User: Aggregate
{
public string Email { get; private set; }

public User(Guid id, string email) => Process(new Events.V1.UserCreated(userId:id, email: email ));

public void ChangeEmail(string userEmail) => Process(new Events.V1.UserEmailChanged(userId: Id, email: userEmail));

private void Apply(Events.V1.UserCreated @event)
{
    Id = @event.UserId;
    SetUserEmail(@event.Email);
}

private void Apply(Events.V1.UserEmailChanged @event) => SetUserEmail(@event.Email);

private void SetUserEmail(string email)
{
    email = email?.Trim();
    CheckNullOrEmpty(email, "Email");
    CheckMaxLength(50, email, "Email");
    CheckIsMatch(Constants.EmailTemplate, email, "Email");
    Email = email;
}
}

* For the simplicity of the example, I’ve omitted implementation details of methods CheckNullOrEmpty, CheckMaxLength, and CheckIsMatch. Anyway, if you are interested, you could find it in Github Repo here.

User domain model contains:

  • Email property (in addition to Id property from Aggregate class). Please be sure it has a private setter
  • SetUserEmail – a method for set and check if the email is correct
  • 2 private Apply methods for changing the internal state of the Domain Model
  • Public constructor
  • public ChangeEmail method for changing the email property

So when are speaking about the testing, we could check several things in the domain model:

  • It has a proper state – eg. User has not empty id and email
  • It generates a proper event – e.g. User generates UserCreated Event
  • Generated event has a proper state – UserCreated Event contains User Id and Email

Let’s see an example of such tests. I’m using Xunit, but you could use any test framework you like:

public class UserCreateTests
{
private readonly Domain.User _createdUser;
private readonly (Guid Id, string Password, string Email) _userData;

public UserCreateTests()
{
    _userData = (Id: Guid.NewGuid(), Password: "Testing123!", Email: "test@email.com");
}

[Fact]
public void ShouldBeCreatedWithCorrectData()
{
    Assert.NotNull(_createdUser);
    Assert.Equal(_userData.Id, _createdUser.Id);
    Assert.Equal(_userData.Email, _createdUser.Email);
}

[Fact]
public void ShouldGenerateUserCreatedEvent()
{
    var events = _createdUser.DequeueEvents();
    Assert.Single(events);
    Assert.Equal(typeof(Events.V1.UserCreated), events.Last().GetType());
}

[Fact]
public void UserCreatedEventShouldContainsCorrectData()
{
    var @event = (Events.V1.UserCreated) _createdUser.DequeueEvents().Last();
    Assert.Equal(_createdUser.Id, @event.UserId);
    Assert.Equal(_userData.Email, @event.Email);
}
}

Firstly, ShouldBeCreatedWithCorrectData checks if the User object is in a valid state after creation. Next, there are 2 tests for the events: ShouldGenerateUserCreatedEvent – checks if the event which has been generated has an expected type, and UserCreatedEventShouldContainsCorrectData checks if this event contains correct data.

As you could see, there is no big difference between unit testing regular objects, and those which are using Events for building their state. I hope this article helped you to understand better the philosophy of Event Sourcing. In the next post, I will describe the persistence of the aggregate using the Marten library. CU 🙂

The latest code base version of the project could be found on Github Org Page.

Always remember about IQueryable when using Expressions in LINQ!

Today I’ve faced unexpected behavior during querying data and JSON deserialization while playing with Marten library which uses PostgreSQL Database as storage. So let’s imagine we have a very dummy 2 classes, one for saving data (in JSON format) and one class for the projection (as a response of some Web API for instance):

Origin class for data storing:

public class User
{
    private User() { }

    public User(int id, string name)
    {
        Id = id;
        Name = name;
        Status = "Pending";
    }

    public int Id { get; private set; }
    public string Name { get; private set; }
    public string Status { get; private set; }
}

Projection class

public class ReadOnlyUser
{
    public string Name { get; set; }
    public string Status { get; set; }
}

So as you see, the User class has private setters and due to MSDN Deserialization behavior, JSON Serializer ignores read-only properties. This means that this code will always create a User instance with Status Pending

var stringData = "{\"Id\": \"1\", \"Status\": \"Active\",\"Name\": \"John\"}";

var stream = new MemoryStream(Encoding.ASCII.GetBytes(stringData));
var textReader = new JsonTextReader(new StreamReader(stream));

var user = new JsonSerializer().Deserialize<User>(textReader);
Result: 

Output object:
User Id: 1 
User Name: John 
User Status: Pending

So if we follow the example above, making a projection from such class, will produce the ReadOnlyUser which also will have a Status Pending:

 var stringArrayData = "[{\"Id\": \"1\", \"Status\": \"Active\",\"Name\": \"John\"}]";
 JsonSerializer serializer = new JsonSerializer();

 var stream = new MemoryStream(Encoding.ASCII.GetBytes(stringArrayData));
 var reader = new JsonTextReader(new StreamReader(stream));

 var user = serializer
   .Deserialize<IEnumerable<User>>(reader)
   .Where(usr => usr.Id == "1")
   .Select(usr => new ReadOnlyUser {Name=usr.Name, Status=usr.Status})
   .FirstOrDefault();

 Console.WriteLine($"\r\nOutput object:\r\nUser Name: {user.Name} \r\nUser Status: {user.Status}");
Result:

Output object:
User Name: John 
User Status: Pending

But what if we are not querying from a string variable, but rather from the database? Most probably instead of IEnumerable, IQueryable interface will be used.

In a nutshell, IQueryable brings 1 huge benefit – it knows how to execute expressions on special data sources. (For instance Where statement will be implemented in the database, not on the memory side)

So, coming back to the previous example, this code will also return User with Status Pending, won’t it?

return await _session.Query<User>() //Returns IQueryable<User> from db
   .Where(usr => usr.Id == "1")
   .Select(usr => new ReadOnlyUser {Name=usr.Name, Status=usr.Status})
   .FirstOrDefaultAsync();
Result:

Output object:
User Name: John 
User Status: Active
Me at that moment

So how did it happen, why Status is Active if the User has a private setter on that field?

In the first example, as we used IEnumerable, all of the operations are processed in our App memory. The order of commands was like that:

  1. Deserialize the JSON to IEnumerable list of Users (by using the only 1 public constructor which set Status to Pending without possibility to set it to Active because of private setter)
  2. Use Where statement to filter the Users with Id = “1”
  3. Use the Select projection for creating a new ReadOnlyUser

In the example with an IQueryable interface, steps 1 and 2 were “outsourced” to the database. So commands look so:

  1. Find by Object Type a proper table from which data should be fetched and create a query like “Select * from User”
  2. Add Where the statement “Where Id = ‘1’ ” to the query from step 1
  3. Execute a query in the database and return the result as a JSON object
  4. Use JsonSerializer for deserialization from JSON to ReadOnlyUser

So as you see, in the second scenario, many steps executed on the database side, but in that particular case, we gain even more profit because we could omit the limitation of Newtonsoft.Json of ignoring read-only fields during deserialization. In fact, we are not creating a User object but just go directly to the creation of ReadOnlyUser projection.

Please be aware, that every implementation of an IQueryable interface has its own rules of converting command and using expression tree. So before relying on “outsourcing processing”, just spend a few minutes to check what exactly will be executed.

DDD & CQRS & Event Sourcing. Part 2: Extending domain model by events

Continuing the topic started in the previous post, today’s subject will be related to the domain model with events and why it is important.

So first of all, let’s try to understand what is the “event”?

Domain event describes something, which is happened with the Domain Model and it is important for the domain experts. So let’s revise our domain model:

public class User: Aggregate
{
    public Guid Id { get; private set; }
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Email { get; private set; }
    
    public User(Guid id, string firstName, string lastName, string email)
    {
        if(id == Guid.Empty)
            throw new ArgumentException($"User id could not be empty")

        Id = id;
        
        ChangeName(firstName, lastName);
        ChangeEmail(email);
    }

    public void ChangeName(string firstName, string lastName)
    {
        CheckNullOrEmpty(firstName, nameof(frstName));
        CheckMaxLength(100, firstName, nameof(firstName));
        FirstName = firstName.Trim();
        
        CheckNullOrEmpty(lastName, nameof(lastName));
        CheckMaxLength(100, lastName, nameof(lastName));
        LastName = lastName.Trim();
    }
    
    public void ChangeEmail(string email)
    {
        CheckNullOrEmpty(email, nameof(email));
        CheckMaxLength(50, email, nameof(email));
        var trimmedEmail = email.Trim();
        
        if(!Regex.IsMatch(trimmedEmail, @"\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z", RegexOptions.IgnoreCase))
            throw new ArgumentException($"Email {trimmedEmail} is invalid");

        Email = trimmedEmail;
    }
}

So for now, the User domain model has 3 changes (events) which are important to business experts (and developers as well, but let’s talk about that later):

  • User Created – event which signalizes that user has been created
  • User Name Changed – the event will be raised every time when first and/or last name of the user will be changed
  • User Email Changed – the event will be raised every time when using email will be changed

First step is that the events should be created:

public static class Events
{
    public static class V1
    {
        public class UserCreated
        {
            public UserCreated(Guid userId, string firstName, string lastName, string email)
            {
                UserId = userId;
                FirstName = firstName;
                LastName = lastName;
                Email = email;
            }

            public Guid UserId { get; }
            public string FirstName { get; }
            public string LastName { get; }
            public string Email { get; }
        }

        public class UserNameChanged
        {
            public UserNameChanged(Guid userId, string firstName, string lastName)
            {
                UserId = userId;
                FirstName = firstName;
                LastName = lastName;
            }

            public Guid UserId { get; }
            public string FirstName { get; }
            public string LastName { get; }
        }

        public class UserEmailChanged
        {
            public UserEmailChanged(Guid userId, string email)
            {
                UserId = userId;
                Email = email;
            }

            public Guid UserId { get; }
            public string Email { get;}
        }
    }
}

From the code above, there are 2 things worth to mention:

  • Names of the events use past tense (for ex. UserCreated). Such style underline, that something already happened in the past
  • Properties among with the whole object are read-only. Partly it relates to the previous point. If the event is smth which has happened in the past, so it is impossible to change the state of the event. (for instance, we could not change the value of the name in the UserNameChanged event, because unfortunately, we could not change the things from the past)

So probably now you think it should look like this:

public object ChangeName(string firstName, string lastName)
{
    CheckNullOrEmpty(firstName, nameof(frstName));
    CheckMaxLength(100, firstName, nameof(firstName));
    FirstName = firstName.Trim();
    
    CheckNullOrEmpty(lastName, nameof(lastName));
    CheckMaxLength(100, lastName, nameof(lastName));
    LastName = lastName.Trim();

    return new Events.V1.UserNameChanged(userId: Id, firstName: firstName, lastName: lastName)
}

Well, in fact, we could that, but there are a couple of problems in such an approach – from the business point of few, method ChangeName should not return anything. We don’t ask here smth like “GetFullName”, so there is nothing to return after the method was executed.

So let’s fix this concern by introducing private _events property and methods for access to them:

public class User
{
    private readonly IList<object> _events = new List<object>();

    public ICollection<object> DequeueEvents()
    {
        var events = _events.ToList();
        _events.Clear();
        return events;
    }

    private void Enqueue(object @event)
    {
        _events.Add(@event);
    }

rest of the code ......

and the method will stay with the void type :

public void ChangeName(string firstName, string lastName)
{
    CheckNullOrEmpty(firstName, nameof(firstName));
    CheckMaxLength(100, firstName, nameof(firstName));
    FirstName = firstName.Trim();
    
    CheckNullOrEmpty(lastName, nameof(lastName));
    CheckMaxLength(100, lastName, nameof(lastName));
    LastName = lastName.Trim();

    Enqueue(new Events.V1.UserNameChanged(userId: Id, firstName: firstName, lastName: lastName))
}

*Small change has to be done in the constructor now, as soon as domain methods enqueue events, we could not reuse them in other methods. So we have to move the validation logic to the common part or create a Value Object (which will not be probably covered in that post series, but I highly recommend getting familiar with this concept in Domain-Driven Design).

So example of usage of the User domain now looks like that:

var user = new Domain.User(Guid.NewGuid(), "testFirstName", "TestLastName", "test@email.com");
var events = user.DequeueEvents();

or 

var user = userRepository.Get(id);
user.ChangeEmail("newTest@email.com");
var events = user.DequeueEvents();
  • UserRepository allows getting users from DB. Later in the course, we will use the Marten Framework and Event Sourcing approach for implementing the User Repository.
  • Received events could be used to notify other parts of the application about the changes in the User module (We will cover this in the future post related to Microservice Architecture) or for saving our domain model using Event Sourcing

Introduction to Event Sourcing

So now imagine, that Users in our system could be edited by multiple users, and it is crucial to have a guarantee that we have the full history of changes made in the User domain model. So probably you think that we could just have a history table and catch all events, which User generates, so we could have all the changes regarding the particular user (we will come back to that approach when will be touching the Projections in CQRS topic).

The problem with such an approach is that someone could always change accidentally the data in the database (for instance changing email from test@email.com to newTest@email.com). In that case, there will be a difference between history and actual data.

So if we are publishing domain events after each business change, why we could not use these events as a source of truth for building the domain model state?

So after the User has been created, we raise the UserCreatedEvent which contains all information about the created User. Next, we want to change the user name? No problem, event UserNameChanged has all info about this change. So in fact, we don’t need to save the User domain model state itself, it is only enough to save the current version of this User and a list of the events, which will be used for rebuilding the state.

Let’s take a look on a User model, which could be rebuild from events (without versioning for now):

public class User
{
    private readonly IList<object> _events = new List<object>();

    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Email { get; private set; }
    
    public User(Guid id, string firstName, string lastName, string email)
    {
        Process(new Events.V1.UserCreated(userId:id, firstName: firstName, lastName: lastName, email: email ));
    }

    public void ChangeName(string firstName, string lastName)
    {
        Process(new Events.V1.UserNameChanged(userId: Id, firstName: firstName, lastName: lastName));
    }
    
    public void ChangeEmail(string userEmail)
    {
        Process(new Events.V1.UserEmailChanged(userId: Id, email: userEmail));
    }

    #region Applies
    private void Apply(Events.V1.UserCreated @event)
    {
        Id = @event.UserId;
        Apply(new Events.V1.UserNameChanged(userId: this.Id, firstName:  @event.FirstName, lastName: @event.LastName));
        Apply(new Events.V1.UserEmailChanged(userId: this.Id, email: @event.Email));
        Status = UserStatus.Pending;
    }
        
    private void Apply(Events.V1.UserNameChanged @event)
    {
        CheckNullOrEmpty(@event.FirstName, nameof(@event.FirstName));
        CheckMaxLength(100, @event.FirstName, nameof(@event.FirstName));
        FirstName = @event.FirstName.Trim();
        
        CheckNullOrEmpty(@event.LastName, nameof(@event.LastName));
        CheckMaxLength(100, @event.LastName, nameof(@event.LastName));
        LastName = @event.LastName.Trim();
    }

    private void Apply(Events.V1.UserEmailChanged @event)
    {
        CheckNullOrEmpty(@event.Email, nameof(@event.Email));
        CheckMaxLength(50, @event.Email, nameof(@event.Email));
        var trimmedEmail = @event.Email.Trim();
        
        if(!Regex.IsMatch(trimmedEmail, @"\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z", RegexOptions.IgnoreCase))
            throw new ArgumentException($"Email {trimmedEmail} is invalid");

        Email = trimmedEmail;
    }

    public ICollection<object> DequeueEvents()
    {
        var events = _events.ToList();
        _events.Clear();
        return events;
    }

    private void Enqueue(object @event)
    {
        _events.Add(@event);
    }

    private void Process(object @event)
    {
        Type thisType = GetType();
        if (thisType == null) throw new NotSupportedException($"Current this type is null!");
        
        MethodInfo methodInfo = thisType.GetMethod("Apply",  BindingFlags.NonPublic | BindingFlags.Instance, Type.DefaultBinder, new [] { @event.GetType()}, null );
        if (methodInfo == null) throw new NotSupportedException($"Missing handler for event {@event.GetType().Name}");
        
        try
        {
            methodInfo.Invoke(this, new[] { @event } );
        }
        catch(TargetInvocationException ex)
        {
            ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
        }

        Enqueue(@event);
    }
}

So there are a few changes in the User class above:

  • All state changes have been moved to the Applys methods.
  • The Process method has been introduced. This method use reflection to call correct Apply method based on event type and enqueue events.
  • All public domain methods use Process method, to introduce a changes in the model.

So in the previous implementation, the first User changed the state and later raised the event. Now, the User first raises an event, and based on that event, makes changes into its own state.

The next post will describe how to test the Domain Model which is using events.

The latest code base version of the project could be found on Github Org Page.