Persistence and Domain layers separation within a Rich Domain Model application

425 Views Asked by At

There is a concept that talks about the separation of the persistent layer from the domain layer to make the domain layer more robust - it would not be dependent on the actual implementation of the repository in persistence layer, but only on the repository interface.

It means that we have:

IPersonRepository {...} // in domain layer
PersonCassandraRepository implements IPersonRepository {...}  // in persistence layer
Person (Aggregate Root) {...}

Now, what about Person?

In anemic-domain-model we can have:

IPerson {...} // in domain layer
Person implements IPerson {...} // in persistence layer

Why put Person in persistence layer?
Because it contains implementation-specific code.
For example, it may contain JPA-related annotations, and the same as with repository, we don't want data-store specific implementation in our domain layer.

We can do the above with anemic-domain-model, because Person does not contain any domain logic, which means we can put Person in persistene layer.
In anemic-domain-model data is separated from behavior, so Person's behavior is done by separated services, and not written in Person itself.

We cannot do this layers separation with rich-domain-model, because in this case, Person does contain domain-specific logic.

How would you do this layers separation within a rich-domain-model application?
Or maybe you are thinking that it is not needed.

2

There are 2 best solutions below

0
On

The class(es) that get(s) persisted don't necessarily need to be the classes (or even implement the same interfaces) that are in the domain model.

So you could have a Person class in the persistence layer that is designed to just work with your persistence layer and has no real behavior and probably doesn't enforce domain invariants. In your domain layer, you have a Person class that does enforce invariants and has no idea about persistence.

The repository's responsibility in this approach would then be to translate between the representation of Person in the persistence layer and the representation of Person in the domain layer.

0
On

How would you do this layers separation within a rich-domain-model application? Or maybe you are thinking that it is not needed.

This is the point in the conversation where the established literature tends to say "Oh, gosh, I'm afraid that's all we have time for today...."


Design is what we do to get more of what we want than we would get by just doing it. -- Ruth Malan

As a rule, your business logic doesn't care about the details of the plumbing; the business policies for shipping containers (or whatever) are independent of whether we store the information on the file system, or in a relational database, or a document store, or an event store....

So we "should" have separation between the two layers, that we can trivially swap out one for the other.

But the design work required to make this possible isn't free. If you end up in a situation where changes are both frequent and expensive, then you aren't going to have a good time.

Expressed another way: unnecessary coupling between the persistence layer and the domain layer is a form of technical debt, but taking on technical debt can be the right decision.


IPerson {...} // in domain layer
Person implements IPerson {...} // in persistence layer

If you want "separation" between domain and persistence, this isn't quite right; the interface isn't really part of the domain layer, but is instead part of a contract which the domain layer depends on.

A "rich domain model" has-an anemic data model. It might be implicit rather than explicit, or hidden inside a bunch of wrappers, but dig deep enough and you are almost certain to find an int, or a double, or bytes, or some other general purpose data structure that doesn't actually implement domain logic on its own.

When you separate the domain and persistence, you can decide that the domain layer will use the anemic data model provided by persistence, or you can decide that you just want to pass information (values) back and forth across the boundary. Either can work, there are of course different trade offs.


Recommended