Building Domain Driven Architecture in .NET – Part 2 (Domain Modeling)

In part 1, I gave an overview of the DDD architecture as I implemented for my expense tracking application. In this post, I will be going over the domain modeling in a bit more detail in terms of its design and show my thought process and the decisions I made and why I made them. This post is not about how to build an expense tracking application so anyone looking for some help in that regard will be sorely disappointed!

Domain modeling is an essential part of any DDD endeavour and can be quite involved if its to realise its true potential. This is the part where I as a developer try to turn real life business concepts into executable code. A domain model lives in what’s called a Bounded Context which represents, as Vaughn Vernon states in his book – Implementing Domain Driven Design (p62):

“…a Bounded Context is principally a linguistic boundary.”

Basically, what this means is all the terms within this realm will have a consistent meaning. So in expense tracking context, terms like “Expense”, “Period”, “Pot”, “Saving” etc will all have consistent meaning and within the context, no term will have a synonym. For e.g. within the context, I either use the word “Budget” or “Allocation” but not both or interchangeably. I will pick the most commonly used word, understand its meaning and use that throughout in the modeling process. This is called creating a ubiquitous language dictionary and the classes that I write in the code will have the same names. This has knock-on benefit on the readability and clarity of the code as well.

So, I identified the following major concepts/objects after spending a lot of time thinking and simplifying (all these objects form part of a single aggregate that the Period object is at the root of, no Period => no Pot, no Pot => no Expense):

  1. Period – represents a user selected date range that they want to track expenses for. This is the root of the aggregate and will control access to its child entities and their behaviour.
  2. Pot – represents a chunk of money allocated for a specific purpose for e.g. groceries, rent etc. Each period will usually have one or more of these created.
  3. Expense – represents the thing we are interested in tracking. An expense is recorded against a Pot for a given Period.
Domain model
Structure of the Domain Assemblies

I thought a lot about the concept of Budget and to me it seemed more of an abstract concept, a bunch of Pots with some money allocated to each is my Budget. I could model that in the code but a Budget in this case doesn’t really have any domain logic of its own to warrant a separate class of its own. May be some time in the future as the app evolves, I may very well need to model the Budget but for now that’s not clear so I am not going to model it.

With domain modeling, I am trying to capitalise on what I know upfront or can make a reasonable assertion about (either by talking to the domain experts or just by common sense). I won’t cater for the cases that I (or the domain experts) cannot justify in concrete terms.

I prefer all the domain model objects that I might need to query directly or identify uniquely, to have an Id property. This necessity comes also from the fact that most ORMs haven’t matured to the point of being DDD friendly.  To achieve this in a more contractual and DRY way, I create an IDomainObject interface in a shared assembly, where I declare Id property of type Guid. All the domain entities will then implement this interface and provide the Id property. This will have a positive side effect while designing repositories as I will discuss in the next post.

public interface IDomainObject
{
Guid Id { get; }
}

Time to model the Period class that implements the above interface, I would recommend reading the comments in the code because they show my reasoning (and DDD reasoning in general) for designing the class in this way:

public class Period : IDomainObject
{
//..other member variables
private List pots = new List();
private Guid id;

// Entity Framework ORM relies on a parameter less ctor to hydrate the
// entities from the database as a result of a query. This can even
// be a private ctor so that we don't risk exposing unbounded and
// unconstrained entity instantiation to any client code. This ctor
// was only put once the need to persist these object arose and
// I decided to use Entity Framework Core as the ORM of choice.
// All domain entities have this private ctor.
private Period()
{
}

// Public parameterised ctors is the only way client code can
// instantiate entities in Domain Driven Design. This makes sure
// the object created is valid and consistent right from the very
// start because any invalid values or consistency problems can be
// detected centrally and the construction rejected as a consequence
// before it goes any further. Some developers
// just cannot get their heads around this simple idea! Shocking!
public Period(DateTime periodStartDate, DateTime periodEndDate)
{
// each entity assigns its own Id as soon as its created
// and I don't allow the id to be changed once allocated.
// In some cases, ORM can generate the Id but since I want to
// be able to unit test my model without the ORM involved, I choose
// manually assign the id at the time of object creation.
this.Id = Guid.NewGuid();
//..initialise the other props
}

// The Pots collection is exposed to the outside code as a read only
// collection because the conditions under which a Pot can be added
// to a Period cannot be enforced outside of the aggregate root
// and this will lead to potentially inconsistent aggregates. The
// outside code can enumerate the Pots but it cannot directly manipulate
// this collection. I have a method in this class to do that in a controlled
// fashion.
public IEnumerable Pots { get => this.pots.AsEnumerable();
private set => this.pots = value.ToList(); }

// Id assignment is also internal to each entity and that's
// why its marked with a private setter. This is very important
// for consistency and identity of an object in the database.
public Guid Id { get => id; private set => id = value; }

//..more props

// Creating child entities is also the root's responsibility. The
// calling code just passes the relevant information to the
// corresponding method which the root uses to instantiate a
// consistent object and adds it to the collection.
public void CreatePot(string name, decimal allocation)
{
Pot pot = new Pot(name, allocation);
this.pots.Add(pot);
}

// Object Orientation means that the data + behaviour relevant to an
// object must live within that object. This method is one of the
// many behaviours of the Period class, it takes in all the values
// required and creates an Expense record from it and executes
// other business logic against all concerned entities to make sure
// the aggregate as a whole is consistent everytime an expense is
// recorded. Some developers find it difficult to
// understand this too! Can you believe it?
public void MakeExpenseAgainstPot(decimal amount,
string description,
DateTime date, Guid potId)
{
Expense expense;

// The Period class acts as an entry point for recording expenses
// it does its share of business logic checks and then delegates
// the actual act of adding an expense to the pot under which the
// the expense is going to live. This ensures loose coupling &
// separation of concerns at the right levels.
if (this.ExpenseDateWithinPeriodDateRange(date))
{
var potToUpdate = this.GetPot(potId);

if (potToUpdate != null)
{
expense = potToUpdate.RecordExpense(amount,
description, date, potId);
}
else
{
throw new ArgumentOutOfRangeException("This pot doesn't
exist in the current period");
}
}
else
{
throw new ArgumentOutOfRangeException("Expense must be within
the current period's date range!");
}

return expense;
}

//...more behaviour methods that ensure transactional consistency
}

I don’t expose any property in any domain entity to be publicly modifiable and that’s why I mark them all with private setters. This is to control the conditions i.e. use cases under which a property i.e. state can be altered. This also helps with writing consistent, repeatable and reliable unit tests which I would lose if I open up my properties to be modifiable by the outside code. In some cases, opening up the properties may be fine but such instances should be as few as possible and still be testable.

Since Period is my root entity class that references within itself the Pot entity, I don’t expose Pot’s constructor to outside code because a Pot can only be instantiated by a Period because it only makes sense within a Period. So the Pot ended up looking like this:

public class Pot : IDomainObject
{
// ..other member variables
private Guid id;
private List expenses = new List();

private Pot()
{
}

// The responsibility of creating a new Pot lies with the aggregate
// root, in this case, the Period class that's why this ctor is
// internal. I don't want dangling instances of Pot hanging around
// not associated with any Period. I can expose the internals to
// the unit tests so I don't lose testability.
internal Pot(string name, decimal allocation)
{
this.Id = Guid.NewGuid();
// ..other assignments
}

public Guid Id { get => this.id; private set => this.id = value; }

// I throw an exception as soon as I get an invalid value or any
// related business constraint fails. This way an object can never
// be created in a potentially inconsistent state by outside code.
// Also, the setter is marked private which will close the object to
// direct modifications from outside the aggregate.
public decimal Allocation
{
get => this.allocation;

private set
{
if (value <= 0)
{
throw new ArgumentException(
"Pot allocation MUST be greater than zero!");
}

this.allocation = value;
}
}

public IEnumerable Expenses
{
get => this.expenses.AsEnumerable();

private set => this.expenses = value.ToList();
}

// ..other properties

// the Period class proxies the call to the appropriate Pot object to
// record an Expense entry and update any other amounts that need
// updating to make sure the aggregate as a whole is always
// consistent. This is one of the biggest strengths of
// Domain Driven Design, its all Object Oriented.
internal Expense RecordExpense(decimal amount,
string description,
DateTime date, Guid potId)
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(
"Expense amount MUST be greater than zero!",
new Exception());
}

Expense expense = new Expense(date, description, amount);
this.RecordExpenseAndUpdateAllocation(amount, expense);

return expense;
}
}

Domain Driven Design is all about object orientation which results in rich domain objects i.e. objects that have state and behaviour encapsulated within. Its this encapsulation that makes them highly testable and reusable. Lot has been written/said about this by some very smart people, so I will just leave it at that.

Finally, the Expense class which is actually rather simple and so far, hasn’t got any behaviour because Expense is immutable (atleast in my interpretation of Accounting concepts) so any state mutating behaviour won’t make sense. For e.g. if you make a payment of the wrong amount to your bank, they don’t edit that transaction to correct it, they create a new transaction which is the reversal of the wrong one. This leads to an inherently Event Sourced system where the state of a system can be regenerated by replaying the events that have occurred up to any point in time. This is the basic premise of Event Sourced architecture, but I digress.

I will just show the constructor of the Expense class because the rest of the class is very simple and the one that almost everyone has experience in creating.

internal Expense(DateTime expenseDate, string description, decimal spend)
{
if (spend == 0)
{
throw new ArgumentException(
"An expense must have a non-zero spend value!");
}

this.Id = Guid.NewGuid();
// ..assign other properties
}

As you can see, the ctor is internal meaning only the Pot class which contains Expenses can create an Expense object using the values passed in from the outside world.

This is pretty much it. Also, all along the domain modeling process I simultaneously write and execute unit tests so I can be sure that the behaviour that I am building is correct. This Test Driven approach helps build confidence in the application in the long run as the application starts becoming more and more complex. It also helps with refactoring because tests will let me know if my refactoring has broken any existing behaviour.

There are other concepts in domain modeling such domain services and domain events that I haven’t yet had a chance to use in my application but my high level interpretation of these concepts is as follows:

Domain Services: these are business services that domain objects can use to execute a part of their behaviour. For a completely cooked up e.g. whether or not an insurance policy has lapsed might be a piece of functionality that doesn’t really belong in any specific domain object so this can be created in a separate assembly as a separate class that exposes a function called something like HasInsurancePolicyLapsed(…).

Then a domain object could take a dependency on this domain service and call into its function to find out whether or not an insurance policy has lapsed.

Domain Events: these are used to communicate state changes in one aggregate to other aggregates within the same bounded context. For communicating cross-context, something a little more decoupled might be used for e.g. message queues where a domain service puts a message in a queue which the domain objects of another bounded contexts can pick up and do their process based on the information packed in the event. For e.g.  if the Human Resources bounded context needs to let the IT Operations context know that an employee has left so the logon credentials of the ex-employee can be disabled, they can use this latent form of domain events by exchanging information via queues if the contexts are implemented as physically separate systems. If, however, a new Product has been added to the inventory, it can raise a C# event so that the Product Catalog can update its list and display the latest information to the user.

I could be dead wrong in these interpretations because I haven’t used them so far so I would recommend checking out this, this and this for domain events and this and this for domain services. I prefer to watch videos on these subjects as well so here is a quick LGTFY.

I hope this has been some help in the next post I will deal with Repository Design.

3 Replies to “Building Domain Driven Architecture in .NET – Part 2 (Domain Modeling)”

  1. Thank you for your post. I am a newbie willing to learn DDD in ASP.NET Core. I wish this could be a complete course. Please do one on UDemy

    1. Thanks for your comment Patrick, glad my post helped you out.

      The subject of DDD is a fairly complex one so while I appreciate that you think I could write a course on this, but I am almost certain there are plenty of courses on DDD on Udemy, Pluralsight etc that are far better than what I could muster.

      May be some day but thanks for the suggestion.

Leave a reply to Patrick Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.