Today, I’d like to talk about one of the 3 pillars of the DDD trilemma: domain model purity. If you haven’t read about the DDD trilemma, I highly recommend that you check out this article: Domain model purity vs. domain model encapsulation.

1. What is domain model purity?

I gave an extensive definition in this article but in short, you can think of it as of lack of certain types of dependencies in the domain model layer:

  • Domain classes shouldn’t have explicit references to out-of-process dependencies or classes from the application services layer. In other words, domain classes should only depend on other domain classes or the framework’s built-in primitive types.

  • All inputs to the domain model should be referentially transparent. Referential transparency means that you can replace a method call or an expression with the output of that method call or expression, and it will not change the code’s behavior.

These two types of dependencies are responsible for 90% of all domain model impurities in a typical enterprise application.

A common example of the first type is a database or the file system. An example of the second type is the DateTime.Now property: it returns a different result with each invocation, hence it’s not referentially transparent.

For example, a dependency on a StudentRepository makes the Student domain class impure:

// Student domain class
public Result<Student, Error> Create(
    Email email, string name, StudentRepository repository)
{
    Student existingStudent = repository.GetByEmail(email);
    if (existingStudent != null)
        return Errors.Student.EmailIsTaken();

    return new Student(email, name, addresses);
}

2. Things that do NOT make your domain model pure

Injecting a repository into a domain class makes that domain class impure — I think this point is pretty non-controversial.

But there’s a quite common misconception here. I often see people suggesting to replace the repository class with an interface, such as IStudentRepository, which would presumably fix the impurity issue.

Here’s a common justification of this practice:

IStudentRepository is not a dependency on a database. Yes, probably in production, that abstraction will talk to some out-of-process data store. But your model doesn’t have to worry about that. That’s the whole point of using an abstraction.

I certainly understand where this sentiment comes from and I held the same opinion for a long time myself. But it’s still incorrect.

As the saying goes, a picture is worth a thousand words:

☝☝ This is how I feel when people preach the use of an interface instead of a concrete repository.

Leaving aside the claims that interfaces are abstractions (they are usually not), it doesn’t matter if you inject an interface or a concrete repository into the domain class. Both options will make that class impure.

Why is that? Because even an interface is still an explicit dependency on the database. Note the wording here: it’s not a direct dependency (an interface does introduce additional indirection), but an explicit one.

That’s because the only reason for introducing such an interface is to enable database access. You wouldn’t need that interface otherwise.

Think of it this way. To see how pure your domain model is, you need to imagine how you would design it if you didn’t have to persist any data to the database, and all you had to do is keep that data in the application memory. Then you need to compare this design to what you ended up with in your project. The difference would be the dent in domain model purity:

And the difference here is the IStudentRepository interface. The domain class simply wouldn’t need to work with this interface in a setting with no database.

A pure domain model is void of any persistence concerns. The injection of an interface whose specific purpose is to enable access to the database makes the domain model impure.

3. Domain model purity and functional programming

I see this misconception (that replacing a repository with an interface fixes domain model purity) repeated over and over again, but it’s really just that — a misconception. And it’s only prevalent in OOP programming languages, such as C# or Java, that don’t provide as strong compiler guarantees as functional programming languages.

In Haskel, for example, it’s not even a question whether an interface (or a delegate, for that matter) can fix domain model impurity. It cannot, simply because the compiler itself will make you put the fact that this interface talks to the database into that interface’s signature.

This works pretty much the same as async methods in C#. A method that calls an async method must itself become an async method (must have a Task in its return type). The same goes for the communication with out-of-process dependencies in Haskel (it’s called the IO monad).

The imperfection of the C# type system allows us to hide the impurity, but it doesn’t change the fact that such domain model starts to have unrelated responsibilities — the work with IO/out-of-process dependencies.

--Vlad


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

Consulting and training — I offer consulting and group training for organizations on Unit Testing and Domain-Driven Design. Reply to this email with the word "consult" to discuss.

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 »