In the previous essay, we reflected on the way the tools we use can affect the way we reason about our domain. In this essay, we look at the high-level design principles of JPA, and how well they align with the principles of DDD.
So what are our tools in this situation? We have object modeling using UML, OO programming in Java, a relational database (RDB) in the back, and an ORM to mitigate between the OO model and the database. In my experience, all four of these tools raise certain impediments to doing DDD well. But JPA has caused me the most difficulties.
JPA was not designed as a tool to do DDD. In fact, the Hibernate project (which eventually became JPA) was started 3 years before Eric Evans’ seminal book on Domain Driven Design was published. JPA is a very flexible tool that is designed to handle a wide variety of relational databases and schemas. Its job is to map relational data into and out of Java objects, and as such, it provides some building blocks for doing DDD with RDB and Java. But it does not provide explicit DDD support.
Sometimes, the building blocks that JPA provides make it more challenging to do DDD. One major source of problems, it seems to me, lies in one of the core design principles of JPA. This is not an actual, publicly stated design principle as far as I know, and I don’t even know if the JPA authors would agree with it. But it rings true to me, and it was certainly part of the allure that got many people to choose Hibernate/JPA in the first place:
With JPA, we can create a one-to-one mapping between our domain model and our persistence model using configuration. Our domain model stays pure and free of persistence concerns, and the translation resides off to the side in XML, Java annotations, or most recently, pure Java code.Unfortunately, this design principle does not hold out in practice, and we will discuss examples of this in detail in this series. But let’s consider some clues as to why this doesn’t work out as well as promised.
It can easily be shown that a one-to-one mapping between our domain and our database schema is not possible. The database and persistence layer need to keep track of a variety of persistent state information that is not part of our domain model. The most basic example of this is the database ID. This ID is not found in our domain, but it exists in our database and our ER diagrams. And it exists in our JPA entity classes. We’ll discuss this problem in detail in the third and fourth essays in this series.
When the one-to-one mapping between persistence concerns and our domain model fails, this can have a lot of negative consequences on our system. For one thing, persistence concerns can seep directly into our domain model. In DDD, the domain model is the centerpiece of our application. When persistence logic enters our domain model, it not only negatively affects our software due to failure to encapsulate, but it also negatively affects the way we talk and think about our application.
Most Java/Spring/JPA projects have separate repository and service layers. Ostensibly, the persistence logic and persistence concerns are encapsulated in the repositories, often called data access objects (DAOs). But in every JPA project I’ve worked on, this encapsulation fails, and the service layer is intimately involved with persistence concerns. It has to be aware of fetching strategies, cascades, and special handling of proxy objects. And it has to manage flushing the persistence cache, and understand which entities need to be reloaded into the cache before continuing after a flush.
In the end, there are many situations where we end up having to make compromising decisions within our domain to get the ORM mapping to work right. This tangles software layers that should be modular, and makes it harder to maintain a DDD mindset during development. But JPA and ORM technologies are not entirely to blame. Relational databases, OO analysis, and traditional OO programming techniques, all present their own difficulties. And like any rigorous development strategy, DDD takes hard work and commitment, and we often fail to follow through as development teams, despite our best intentions.
As we consider some of these difficulties in more detail, let’s remember that our purpose here is not to criticize or complain. We want to get a good understanding of these problems from a high level, so that we are better equipped to avoid these problems when working on a DDD project with a Java/Spring/JPA tool set. We also want to use these lessons to help us do DDD well as we transition into new technologies. (One example of a new technology that helps us do DDD well is longevity, a persistence framework for Scala and NoSQL.)
In the next essay, we will take a look at the Plain Old Java Object (POJO), which we use to implement our domain entities. Are the stories we hear about the POJO true?