Domain Driven Design: Entities, Value Objects, Aggregates and Roots with JPA (Part 4)
Don’t abuse the `public` keyword in Java. The demo source code has very few public classes or methods. This is unusual for Java projects. Typically Java projects have package layouts that model the solution; “this package has all the entities, that package has all database related code, that package is all the services code.” That approach forces you to make almost everything public. There is no way the compiler can enforce boundaries that align to the business domain. In the long term, on a big project, many brittle connections will be made across business responsibility boundaries. Bit rot creeps into the design and your application becomes a big ball of mud. You should Tell Don’t Ask across domain boundaries in your application and align your package structure to the to the domains of your application and not any technical layers.
With DDD you model the problem space not the solution; “this package is everything to support contracts, that package holds everything for products, that package is all about customers.” Then you can be very strict and only expose the service classes, root entities, and core business concepts, and force client code to go via a narrow public API. This helps keep bugs at bay and allows you to add or refactor logic with confidence. It also makes it easy to add sophisticated and professional features like locking patterns, audit trails, and “restore to date” just inside of the narrow public API.
Java didn’t make `public` the default it made “package private” the default. Tragically that excellent hint as to how to write good OO/DDD code is ignored by the vast majority of Java developers. This is an epic fail. Package private is awesome as you can put your test code into the same package to be able to setup and verify fine grained unit tests while only exposing a minimal public API to code outside of the package. The unit tests in this sample code demonstrate this approach. If you have two many method calls across business boundaries you have end up with a big ball of mud. You should use the Ask-Dont-Tell principle avoid entanglement across business boundaries. You should enforce that this is done by not making anything public for an aggregate root that is not the “tell” contract. If you make everything public there will be no enforcement of the business boundaries. Only “convention” will prevent disorder in the code. Yet time and time again developers make a package for each layer (ui, service, data, common) and make everything public in each layer. This simply does not scale very well to many developers working on a big system over many years.
When Java coders look at the code though they will likely scowl at the contract package. It has package private entities, package repositories, and services that demarcate the database transaction boundaries. Classic Java says you have a service layer and a data layer that are separated things leading to all the entities being public. Then again a lot of class Java projects stick all the logic in the service layer, make everything public, and don’t really do DDD or even much OO at all. This is known as the anaemic domain model anti-pattern. So we should question whether the classic layout of service layers and DOA layers is anything other than a comfort blanket from the turn of the century J2EE days. The optimal package layout for DDD is one where the compiler enforces boundaries that separate core concepts in the business domain. Making as much as possible package private and only exposing a minimal public API is a great tool to achieve this. If we put each root entity into it’s own package, with a service to load it, along with a package private repository to persist it, we can have the compiler enforce that code outside of the root entities package must go via a narrow public API.
One could easily argue that the service class in the demo isn’t a classic service class as it is only about storing and retrieving root entities. In a classical application layering the service layer would be higher up in the application stack one step closer to the user. Does that really work out in practice? To quote the scabl blog someone else with the same sort of experiences:
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.
Do we have to have such persistence complexity bubble up in the business logic service? I considered renaming the ContractService to be “ContractStore” to highlight that it is all about hiding the root entity persistence details. Then the sample code is all about encapsulating the object to relational impedance mismatch into one package that exposes a Store class to find, load and save root entities.