I was tweeting something about having separate "DDD" and "ORM" entities in a project in a project, and that I don't understand this. There were some great comments and questions, thanks a lot for that! To be honest, I understand more about it now. In this article I'll try to provide some more information about this.
I'm glad many developers want to use input from the book "Domain-Driven Design" by Eric Evans to improve their domain model. I recommend reading this book and getting your information from the source, because unfortunately the internet, tweets, e-books, including my own books, aren't able to reflect a full, nor a correct view of everything there is to find out about this topic. All too often DDD is completely misinterpreted to be "an elitist, dogmatic approach to programming, where we use DTOs, layers, and CQRS"...
I think some programmers, myself included, want to escape from the framework-coupled, hype-inspired mess that web application development often seems to be. Unfortunately, while trying to move in the opposite direction, they end up with a lot of code that is written for the sake of decoupling. Or maybe it happens because they want to try something new and not-boring, something that can't be generated from the command-line with make:entity
.
Back in the days when Symfony 2 adopted annotations for routing, and Doctrine ORM did the same for its mapping configuration, I was one of the first to be against that, pointing out that it's "framework coupling". I mean, "Are you coupled to the framework?!"
Ever since I've been thinking, experimenting, and writing a lot about this topic (e.g. my book about application architecture, and an older article about ORMless). In the end I think our applications need framework coupling in certain areas, and in other areas, like the domain model, they are better off being decoupled.
But full decoupling is usually not the best choice. Rather, 80% decoupling is fine ;) As an example, it seems that some would like to have DDD entities that are fully decoupled from the ORM. They interpret this to mean that you can't have ORM annotations in your entity, or that you can't use specialized collection classes like Doctrine ORM forces you to have. This coupling is by no means dangerous, and certainly doesn't get in the way, as long as:
- We can instantiate and modify these entities without a runtime dependency on the database.
- We can retrieve data from these entities without a runtime dependency on the database.
Once this is the case, you can even test their behavior in a unit test, because these are the only requirements for that. It does mean that most entities-in-the-wild aren't good entities, because calling methods on them is most likely to trigger database queries (e.g. lazy-loading for child entities or referenced entities). If you use an Active Record implementation like Eloquent, many more methods will trigger database queries.
And this is where DDD comes in, with its aggregate design rules. I often refer to an excellent series of articles on this topic, by Vaughn Vernon. In summary:
- An aggregate should guarantee its own consistency, establishing relevant domain invariants (i.e. it should prevent itself from being in an invalid or incomplete state).
- Reference other aggregates by their ID (i.e. don't pass related entities as objects, or load them lazily).
- Aggregates should be small (i.e. don't let it manage child entities or related entities).
We can learn a lot from these DDD patterns to make our models better, and we can in most cases accomplish this while using our current ORM and its entities or model objects. We just upgrade these existing objects with insights from the DDD book; adding intention-revealing methods, making state changes in meaningful chunks instead of on a per-field basis, throwing exceptions whenever a consistent state is in danger, etc. There may be some things we have to do to please the ORM, but nothing that messes up much with our domain model. A good rule of thumb is to focus on the public methods of the entity, and ignore what goes on behind the scenes (good old encapsulation).
There is one situation where upgrading existing ORM entities to DDD entities isn't going to work: when you want to evolve the design of your aggregates, without also evolving the database schema. I don't have experience with this. Whenever I worked with a team that introduced DDD practices to their entity design, they still had to stick to the old database structure and even had to do some work to protect its broken state because parts of the application were relying on it.
Not having experience with this, doesn't mean it could be a valid use case. Looking at some of your tweets, you seem to be doing this. Great! However, the cases I've heard where a project had separate DDD and ORM entities did not have this need to evolve the domain model. In fact, the ORM entities were pretty much 1:1 copies of the DDD entities, only keeping the data, but no behavior. Pretty much DTOs. It was done to achieve an - in my opinion - expensive and unnecessary form of decoupling. It leads to a lot of extra work, because these two entities often change together and have to remain in sync somehow. You also need some way to get the state out of the DDD entity and put it into the ORM entity. At that point the question becomes relevant: do we even want to use the ORM for persisting our DDD entities? And indeed, nowadays I prefer manual mapping over automated/magic mapping by the ORM.
In summary, roughly speaking, this could be a decision diagram for "should I have separate DDD entities and ORM entities"?
If you have other relevant experiences with this topic, please share them as a comment below!