Eduard Keilholz

Hi, my name is Eduard Keilholz. I'm a Microsoft developer working at 4DotNet in The Netherlands. I like to speak at conferences about all and nothing, mostly Azure (or other cloud) related topics.
LinkedIn | Twitter | Mastodon | Bsky

I received the Microsoft MVP Award for Azure

Eduard Keilholz
HexMaster's Blog
Some thoughts about software development, cloud, azure, ASP.NET Core and maybe a little bit more...

Introduction to DDD

So here’s some thought about DDD. I really love the thought and principles of DDD (Domain Driven Design) and I really recommend looking into it. That’s why it is time for a new blog. Let’s call it a practical introduction to DDD for C# developers.

This is the first post of a series. This post is an introduction to DDD and how to build a domain model.

So, what is DDD? You probably know the meaning of the abbreviation by know, but what does it really mean? The answer to that question is easy yet complicated. DDD is a huge thing and has a whole lot involved, but basically you’re just dividing functionalities of your system in separate domains. In the classic example of a web shop, the catalogue, the basket and the order process would all live in a separate domain. This may also be the reason why DDD and Microservices are such a good marriage, however leveraging the power of DDD doesn’t necessarily mean your technical architecture must be Microservices. You can enjoy the advantages of DDD in a huge monolith as well.

All functionalities that you pack in a domain is called the bounded context. When starting a Microservices architecture you probably may want each bounded context in a separate Microservices although this isn’t true for all situations, be sure to evaluate your decisions. Now in this world of DDD, there’s also someone called the domain expert. This guy is the smartest in class of a given domain and can tell you everything about it. Compared to agile/scrum you may identify the domain expert as a Product Owner, but for a specific domain. Some domains have an expert that is actually the same person. It is this crucial piece of information where you can get confused. Having different experts for different domains may also introduce a difference in terminology. In DDD, we think that’s fine… For example, an entity may be called a User in the first domain, but a Customer in the second domain, although they originated from the very same entity.

Bounded contexts, as the word says, have a huge boundary around them. This means that all functionalities and infrastructure involved with a domain are separated from other domains. Different domains for example, should not share the same data store. This may become a little challenging when dealing with the situation that a certain entity should live in multiple domains, for example the user and the customer. A messaging system must be configured to synchronize the changes between domains. Of course in case a data store is separated, which is (again) recommended. Eventual consistency is very important. Be sure to have a good solution in place. If a user changes his email address in the user service and places an order, you don’t want the order service to send a confirmation email to the old address. The new email address should be synchronized to the order service so it ‘knows’ the new address. One important rule of DDD is that only one domain can change a certain entity. So if an email address belongs to a user, only the user service can change it. All domains may use the email field of a user, but only one can change it.

So what’s in it for me?

So in a couple of brief paragraphs, I summed of a couple of fundamentals of DDD. These help you along the way making decisions as you go. A lot of rules to keep in mind, there must be a benefit somewhere… And oh yes there is… Why would you be writing the DDD way, what’s the advantage why why why?

Well, the answer to that is basically given in the previous paragraphs. Lets point them out…

There are not many company processes known by a single person. No C-Level manager of a huge online web store like Amazon knows the details of the packaging process. The packing process manager does. So making this guy the domain expert of the packing process in your software makes sense. Also, the packing software will then probably contain terminology and names known by the ‘packaging process guys’. There are no translations between the domain expert and the software solution. Centralizing knowledge is key, because with that the business is capable of ensuring that understanding the software is not locked in ‘tribal knowledge’. This means that the information about what the software does is open and everyone can contribute. The developer is not (anymore) the only one who knows the entire process of a business.

And finally I think a well designed piece of software that uses the principles of DDD is way more easy to maintain compared to traditional techniques. And I experienced less ‘fixing one moving part, breaks another’ moments. All the moving parts are still in place, but not dependant of each other anymore.

Your first domain model

So the basics are easy. I want to create a domain model for a user. The user has an ID, name, email address and a password. Then I also want to track a created and expiration date. So I start with a couple of properties:

public Guid Id { get; private set; }
public string DisplayName { get; private set; }
public string EmailAddress { get; private set; }
public Password Password { get; private set; }
public DateTimeOffset CreatedOn { get; private set; }
public DateTimeOffset ExpiresOn { get; private set; }

Note that the setters of all properties are private. This is to prevent external systems from changing the values of the properties. You may want the EmailAddress property to be validated for example. If the EmailAddress property is public (and thus settable for other classes) you cannot guarantee that value of the property is always correct, and therefore you can not guarantee the valid state of the domain model. So instead, all setters are private so nobody can harm the correct state of our domain model. Now to change the email address, we need to add a method that does so.

public void SetEmailAddress(string value)
    if (string.IsNullOrWhiteSpace(value))
        throw new ArgumentNullException(nameof(value));
    if (!Equals(EmailAddress, value))
        if (value.IsValidEmailAddress())
            EmailAddress = value;
            throw new ArgumentException($"The value {value} is not a valid email address");

You can see that all validations for an email address (or the validations required for this system) are done inside the SetEmailAddress() method and the valid of the EmailAddress property only changes when the new email address is valid according to the business rules. These business rules by the way, are defined by the domain expert.

I think a domain model has two kinds of constructors, one constructor is to create a new object, for example a user. The second one is to re-produce an existing object from (for example) a data store. The difference is (in my opinion) than when creating a new user, you pass the minimal required fields to a constructor to create a new valid domain model. In this example, the email address of the user is mandatory. Let’s say it’s the only mandatory field. Then the constructor of a new user will accept only one parameter, the email address. The constructor will create a new instance of the domain model class, call the SetEmailAddress() method to set the passed email address and return the new created object. This way, all validations on the email address are validated so when everything runs fine, we end up with a model only containing an email address, but it’s a valid domain model.

public User(string emailAddress)
    Id = Guid.NewGuid();
    CreatedOn = DateTimeOffset.UtcNow;;

Now if you have more information available about the user, let’s say his display name and a password, you create additional Set methods like the SetEmailAddress() method, validate the passed information and then change the property value as soon as everything is fine. You also see that I add some default values as well.

Now you can pass that user to a repository somewhere in order to store is somewhere safe. Now in case you want to change information of a certain user, you fetch that information from the data store and reproduce the domain model.

This procedure uses the second constructor. The second constructor accepts all fields in the domain model and will instantly create it.

public User(Guid id, 
    string emailAddress, 
    string displayName,
    Password pwd, 
    DateTimeOffset created,
    DateTimeOffset expires)
    Id = id;
    EmailAddress = emailAddress;
    DisplayName = displayName;
    Password = pwd;
    CreatedOn = created;
    ExpiresOn = expires;

So I hope you’re now thinking and maybe can already see the benefit of DDD. You see this mysterious Password data type. In DDD terms, that’s called a Value Object. Next post is about value objects.