DDD & CQRS & Event Sourcing. Part 1: Creating a basic domain model

This is the very first post in a series about using DDD (Domain Driven Design), CQRS (Command Query Responsibility Segregation), and Event Sourcing. I’m going to create a small project, which will use the above-mentioned approaches for solving some real-life scenarios. Before we start, I want to underline, that this posts series among the code project will be created for presentational purposes only. That means, that the business challenges which will be handled here could be solved without any above described practices. At this point, I’m not sure how far and deep this will go, so I will start with very simple and naive examples. It is always better to go in direction of complication, rather than simplification.

I’ve not decided so far what problem this project will solve, but I’m sure that we will need registered users for it. So let’s start with a User Service.

First of all, we have to create a User Entity class with some basic properties

Entity – an object that is not defined by its attributes, but rather by a thread of continuity and its identity.

    public class User
    {
        public string FirstName { get; private set; }
        public string LastName { get; private set; }
        public string Email { get; private set; }
        public string Status { get; private set; }
        
        public User(Guid id, string firstName, string lastName, string email)
        {
            FirstName = firstName;
            Id = id;
            LastName = lastName;
            Email = email;
            Status = UserStatus.Awaiting;
        }

        public static class UserStatus
        {
            public const string Awaiting = "AWAITING";
            public const string Confirmed = "CONFIRMED";
        }
    }

As you could see above, all properties have a private setter. In such a way, we could guarantee, that entity will be created with all necessary information and will not be in a non-valid state (for example User without FirstName).

We could check if all necessary parameters are valid in the constructor.

   public class User
    {
        public string FirstName { get; private set; }
        public string LastName { get; private set; }
        public string Email { get; private set; }
        public string Status { get; set; }
        
        public User(Guid id, string firstName, string lastName, string email)
        {
            CheckNullOrEmpty(firstName, nameof(firstName));
            FirstName = firstName;

            CheckNullOrEmpty(lastName, nameof(lastName));
            LastName = lastName;

            CheckNullOrEmpty(id, nameof(id));
            Id = id;

            CheckNullOrEmpty(email, nameof(email));
            Email = email;

            Status = UserStatus.Awaiting;
        }

        private void CheckNullOrEmpty(string paramValue, string paramName)
        {
            if (string.IsNullOrWhiteSpace(paramValue))
                throw new ArgumentException($"{paramName} could not be null or empty");
        }

...

I believe that now you have a question “So if properties have private setters, how could change their value?”. Very reasonable one, the answer is that we are going to create methods, which will describe the business perspective of that operations. For example, we are going to create a method, which will change the first and last names. This method will check if it is not empty and also that it does not break the business requirement of max length 100 characters:

...

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

    CheckNullOrEmpty(lastName, nameof(lastName));
    CheckMaxLength(100, lastName, nameof(lastName))
    LastName = lastName;
}

private void CheckMaxLength(int maxLength, string paramValue, string paramName)
{
    if (paramValue.Length > maxLength)
        throw new ArgumentException($"{paramName} could not be longer the {maxLength} characters");
}

...

After that, we could reuse this method in a constructor, to be sure, that our Entity has a valid state during the creation and change of the name properties:

...

public User(Guid id, string firstName, string lastName, string email)
{
    ChangeName(firstName, lastName);

    CheckNullOrEmpty(id, nameof(id));
    Id = id;

    CheckNullOrEmpty(email, nameof(email));
    Email = email;

    Status = UserStatus.Awaiting;
}

...

The ChangeEmail method also should be implemented (with some email regex etc), but I’m going to omit that for simplicity for now. The important thing is that the User Entity will not have a ChangeId method, because it would break the business requirement and the User just could not change its own identification number. So in a such way, we guarantee that Entity will always be in a valid state.

Below you could find a potential example of usage of such Entity:

...

public void ExampleOfUsage()
{
    var user = new User(Guid.NewGuid(), "John", "Doe", "john.doe@email.com");
    
    //We also could change user name or email if we want
    //But there is not way to change the Id of the user, 
    //because Id has private setter and entity doesn't have ChangeId method
    user.ChangeName("Barbra", "Streisand");
    user.ChangeEmail("barbra.streisand@email.com");

    // Also we could check the user status, which has been set to Awaiting,
    // as the Entity has been created in a valid s
    Console.WriteLine(user.Status)
}

...

So now we have a basic Domain Entity, which ensures a valid state and encapsulates business logic inside.

The last but not least, if you an interested to go deeper into the topic of DDD, which we touched on today (in a very very superficial way) I would like to recommend a few resources, which could be useful for a deeper understanding of key concepts like Value Objects, Bounded Context, Aggregates, etc. :

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

In the next post, we will get familiar with the Events, and how we could use them in Entities.