Some time ago, I wrote an article about a trilemma in domain modeling. It resonated with a lot of people, and I’d like to elaborate on this topic with this email.

In short, the trilemma states that you can’t have all 3 of the following attributes:

You have 3 options here, but each of them only gives you 2 out of the 3 attributes:

Here’s the trilemma in one picture:

[Enable Images]

In the article, I mentioned that in most applications, you can’t concede performance, and so the decision comes down to the choice between injecting out-of-process dependencies into the domain model (which gives performance and domain model completeness at the expense of purity) and splitting the decision-making process between the domain layer and controllers (performance + domain model purity).

Recently, I received an interesting question which made me reconsider this statement about performance.

We can and often do concede performance, particularly when working with 1-to-many relationships.

For example, let’s say that we have a User class, and we need to register all its login sessions in our application. A user relates to login sessions as 1-to-many (one user may have multiple sessions).

One way to implement this relationship is make the LoginSession class part of the User aggregate. This would allow us to put session-related logic directly into the User domain class:

public class User
{
    public LoginSession[] LoginSessions { get; }

    public void RegisterSession(DateTime now)
    {
        LoginSession session = LoginSessions.Last();
        if (session.HappenedRecently(now))
            session.Update(now);
        else
            LoginSessions.Add(new LoginSession(this, now));
    }
}

public class UserController
{
    public UserDto RegisterSession(int userId)
    {
        User user = _repository.GetById(userId);
        user.RegisterSession(_dateTimeServer.Now);
        _repository.Save(user);
    }
}

The downside here is that we can run into performance issues if the number of sessions becomes too large, because we have to load them all from the database even though we need only the last session.

The alternative implementation would be to put LoginSession into an aggregate of its own and handle the session-related logic in the controller, like this:

public class User
{
}

public class UserController
{
    public UserDto RegisterSession(int userId)
    {
        User user = _repository.GetById(userId);
        LoginSession session = _sessionRepository.GetLast(user);
        if (session.HappenedRecently(now))
        {
            session.Update(now);
            _sessionRepository.Update(session);
        }
        else
            _sessionRepository.Add(new LoginSession(user, now));
    }
}

What are the pros and cons of both solutions?

Both options give you domain model purity, because neither of them refer to the database directly. The difference here is that:

In fact, this is a common dilemma in DDD, which I mentioned in my DDD in Practice course. The dilemma is about the size of aggregates in your domain model.

The bigger the aggregates, the simpler the code, because it becomes easier to manage transactional boundaries and enforce domain model invariants. On the other hand, the smaller the aggregates, the more performant your application, since each business operation works with a smaller amount of data.

I recommend going with the first approach, as long as the performance impact is not noticeable or not critical for the application. You can always split the aggregates down the road when the performance becomes a problem.

It’s interesting how this performance-simplicity dilemma can be reduced to a more generic performance-completeness-purity trilemma.

--Vlad

https://enterprisecraftsmanship.com



Enjoy this message? Here are more things you might like:

Unit Testing Principles, Patterns and Practices — A book for people who already have some experience with unit testing and want to bring their skills to the next level.
Learn more »

My Pluralsight courses — The topics include Unit Testing, Domain-Driven Design, and more.
Learn more »