In this post, I’d like to write about a pretty common discussion in DDD circles: should one have the domain model separated from the persistence model? In other words, should you map your domain objects to the DB tables directly using an ORM or would it be better to use a separate set of Data Access Objects (DAOs) instead?
Should a domain model serve as a persistence model?
This question often arises when you try to build a rich domain model while working with a relational data store. To answer it, let’s first look at why one would even consider creating a separate set of DAOs.
It’s true that building a rich domain model that adheres to the DDD principles is not an easy task. To make your code base maintainable in the long term, you need to have it separated from all responsibilities other than holding the domain knowledge. It means that all persistence concerns must be extracted out of the domain classes.
At the same time, the use of ORMs might damage this separation as they impose limitations on how you design the domain model. Some sophisticated design techniques may be hard to implement when an ORM chimes in.
Creation of separate DAO classes is one of the solutions to this problem. With them, it is easier to keep the domain model pure. A typical solution with a persistence model involved looks like this:
DAOs usually have a 1-to-1 correspondence with the underlying data store. The mapping between them and the DB is performed via an ORM; the mapping between the domain model and the persistence model is performed manually in repositories.
So having that said, should you create such a persistence model in order to build a rich and isolated domain model? The quick answer is: no, you don’t have to. Now let’s see why it is so.
A common belief here is that if your application is not too complex, you can trade some purity for the speed of development, and just have your ORM of choice map the domain objects directly to the database. Not a pretty solution, but it works and doesn’t take much time to implement. On the other hand, if you are willing to invest in your code base, you can put in some effort and extract a separate persistence model in order to keep the domain clean.
This belief can be diagrammed as follows:
As the diagram shows, the more work you put, the better results you will have.
In reality, however, it is not the case. The outcome doesn’t grow linearly with the amount of effort you put into implementing your own persistence model. At first, you may have some impressive results as you are able to pretty quickly set up basic mappings between your domain and persistence models.
For example, it’s quite easy to map a single entity to a single table, even if that entity contains lots of Value Objects. However, the further you go into it, the more difficult the task becomes.
One-to-many relationships between domain classes (when a class contains a collection of domain objects) aren’t as easy, especially if you want to somehow distinguish between changed and unchanged objects in the collection. Many-to-many relationships (when both classes have collections of references to each other) are even more difficult to map. Heck, even a single reference from one class to another can pose serious problems.
Eventually, you have a choice to either re-implement a fair share of the functionality ORMs already have or give up on the ideal purity and start to cut corners. And I never saw anyone choosing the first option. In the reality, the amount of work needed to implement a pure domain model with a separate persistence model looks like this:
The real amount of work to reach a complete purity is always more substantial than we perceive it. At the same time, with a partial persistence model, we just trade one sort of purity for another, netting to zero or even a loss comparing to the approach without such a model.
Why? Mainly because ORMs already do a lot of work for you. For example, with your own persistence model, you are not able to benefit from the built-in change tracking functionality. And that means you will not be able to implement reliable domain events – the ones which get fired only when a transaction gets committed to the database. You either need to build a change tracker yourself or give up on such implementation of domain events.
Why is the approach with a separate Persistence Model popular?
It’s interesting to discuss why the approach with a separate persistence model has become so popular. Aside from the point I made earlier regarding underestimating its complexity, I think there are two reasons for that.
First of all, in large organizations, it is still quite common to have a team of developers who work on an application code base separately from a DBA team which manages the underlying database. In this situation, the application code base usually evolves in a much faster pace than the database structure. And it becomes too difficult to manage the mapping between them with an ORM alone. Introduction of a persistence model is inevitable in this case if you want to have a more or less isolated domain model.
However, such situation is a sign of a bigger problem. The problem here is that the application’s database shouldn’t be treated separately from the “regular” code base and must evolve with that code base hand-by-hand. To make it work, the application and its database should be managed by a single team. Check out this article if you want to learn more on how to do that.
The second reason is Entity Framework. Because of the support from Microsoft, this ORM has become the default choice for many (probably most) of the .NET developers nowadays. This is a disappointing trend because, in spite of being developed for many years, it still has major shortcomings in terms of purity. These shortcomings push the developers who seek better isolation for their domain models towards custom persistence model implementations.
If you are one of such developers, just switch to NHibernate. Really. This ORM has everything EF has (except for async DB operations) and much more. You won’t even have to re-learn too much because all modern ORMs are very similar to each other and your experience with EF will be very well applicable to NH (and vise versa).
I’ve been working with both of them for many years and I would say that Entity Framework is still not even close to NHibernate in terms of domain model isolation. Sure, you will still have to change your domain model in order to work with NHibernate but the impact of those changes is minuscule comparing to the benefits you’ll get out of it. Here’s the full list of such changes:
- You’ll need to mark all non-private methods and properties as virtual in your entities (but not in value objects).
- You will not be able to mark your entities as sealed (you will be able to do that with value objects, however).
- You will need to introduce a parameter-less constructor for entities and value objects (which can be made private or protected).
- You will have to be cautious when you deal with the GetType method. For entities, it returns the type of an NHibernate’s runtime proxy instead of the entity itself.
They aren’t pleasant but only the last change actually mean a leakage of an ORM concern to the domain model. Encapsulation of your domain classes is not violated at all.
All DDD practices can be implemented with NHibernate directly, without involving a separate persistence model. And it scales. With NHibernate, it is possible to build large systems whose domain model is easy to understand, unit test and refactor. For an example of a project that uses NHibernate, you can watch my DDD in Practice course on Pluralsight or just examine the source code of the sample project on Github.
Overall, the Complexity/Purity diagram looks more like this:
A fully-fledged persistence model is too costly to implement, whereas with a partial one, you merely trade one kind of purity for another with an additional cost of building and maintaining your own mapping.
NHibernate provides the best set of trade-offs between the implementation complexity and the overall purity. There still will be ORM concerns leaking into your domain model, however. But I think it’s a low price for all the benefits you’ll get out of it: speed of development, rich functionality, and separation of concerns.