Eric Evans has formulated what domain-driven design (DDD) is. Martin Fowler is a great supporter and advocate of DDD. These are remarkable names and it is almost certain they are supporting something worth. And I’m not here to argue with that. Maybe I’m trying to justify the way I’ve been writing software, or maybe I’m trying just to clear things and be constructive. Let’s see.
What is at the core of domain-driven design – the abstract notions of the domain, the domain model, the ubiquitous language. I’ll not go into details with that – for those interested there is wikipedia (with lots of references to read in the footer). This is all very good in theory, and the domain-driven way of building software should appeal to everyone – after all that software is being built for the benefit of that domain, not for the benefit of architects, developers or QAs.
But now comes the practical part – how to implement DDD? I’ll answer that question in its contemporary context – that is, using frameworks like spring and hibernate. And I’ll justify their usage. Spring is a non-invasive dependency-injection framework. DI is also strongly supported by Fowler, and is considered a good way to implement DDD. Hibernate is one way to use objects when working with a relational database. Another way is to use JDBC and construct the objects manually, but that is tedious. So hibernate doesn’t influence the architecture part – it is an utility (very powerful one, of course).
Throughout this post I’ll use hibernate and spring as “given”, although they may be changed with any DI framework and any ORM or other persistence mechanism that relies on objects.
The accepted way to implement DDD with spring and hibernate is:
@Configurableto make the domain objects eligible for dependency injection (they are not instantiated by spring, so they need this special approach)
- inject repository objects in the domain objects in order to allow the domain objects to do persistence-related operations
- use a thin, stateless, transactional service layer (a facade) to coordinate the domain objects
The alternative to this approach is the anemic domain model. It is considered an anti-pattern, but at the same time is very common and often used. The features of the anemic data model are simple - the domain objects have no business logic inside them - they are only data holders. Instead, the business logic is placed in services.
The reason this is considered an anti-pattern is because, first, this seems like a procedural approach. It is breaking the encapsulation, because the internal state of the object is, well, not internal at all. Second, as the domain object is the center of the design, it is harder to change it if its operations don't belong to it, but to a number of stateless service classes instead. And domain-driven design is aimed at medium-to-large scale applications, which change a lot and need an easy way to make changes fast, without breaking other functionality. Thus it is important to have all the functionality of the object within the object itself. This also makes sure that there is less duplication of code.
So, instead of having a service calculate the price:
ProductServiceImpl.calculatePrice(complexProduct) we should simply have
ComplexProduct.calculatePrice(). And so whenever the domain experts say that the price calculation mechanism changes, the place to change it is exactly one and is the most straightforward one.
When simple operations are regarded, this looks easy. However, when one domain objects needs another domain object to do its job, it becomes more complicated. With the anemic data model this is achieved by simply injecting another Service into the current one and calling its methods. With the proposed DDD it is achieved by passing domain objects as arguments.
In my view, the domain object, which is also the hibernate entity, has its dependencies already set. But not by spring, because spring can't know which exactly domain object to inject. They are "injected" by hibernate, because it knows exactly which (identified by primary key) domain object should be placed in another domain object. So, a little odd example - if a
Product has rotten and has to dispense smell in the warehouse, it has to call, for example,
warehouse.increaseSmellLevel(getSmellCoeficient()). And it has its pricise
Warehouse without any interference from spring.
Now, here comes another point where I disagree. Most sources (including the two linked above) state that repositories / DAOs should be injected in the domain objects. No, they shouldn't. Simply calling "save" or "update" doesn't require knowledge of the internal state of the object. Hibernate knows everything anyway. So we are just passing the whole object to the repository.
Let's split this in two - business logic and infrastructure logic. The domain object should not know anything of the infrastructure. That might mean that it should not know it is being saved somewhere. Does a product care of how it is stored in the warehouse? No - it's the warehouse that's "interested". And here are the practical disadvantages:
- CRUD is implemented by simply wrapping repository calls in all domain objects - duplication of code
- the domain object is transitively dependent on persistence - i.e. it is not a pure domain object, and if repositories change, it has to be changed as well. And in theory it should be changed only when the domain rules and attributes change
- people will be tempted to include transaction, caching and other logic inside the domain object
I'll open a bracket here about a proposed solution in one of the above articles for making duplication of code, and boilerplate code easier to handle. Code generation is suggested. And I think code-generation is a sin. It moves the inability to get rid of duplicated or very similar code and abstract it, to tools. The most striking example is generating ProductDAO, CategoryDAO, WarehouseDAO, etc, etc. Generated code is hard to manage, cannot be extended and relies heavily on external metadata, which is definitely not an object-oriented approach.
Speaking of the repository, in the proposed examples each domain object should have a repository, which in turn will call the persistence mechanism. What do we get then:
User presses "save" in the UI > UI calls save on the service (in order to have transaction support) > Service calls save on the domain object > domain object calls save on the repository > the repository calls save on the persistence mechanism > the persistence mechanism saves the object.
Is it just me, or calling the domain object is redundant here. It is a pass-through method that adds nothing. And since a lot of functionality is related to CRUD (yes, even in big business applications), this looks quite bad to me.
And finally, I find the
@Configurable approach a hack. It does some magic in the background, which isn't anything of the common language features (and is not a design pattern), and in order to understand how it happens you need a great deal of experience.
So, to summarize the big mess above
- domain objects should not be spring (IoC) managed, they should not have DAOs or anything related to infrastructure injected in them
- domain objects have the domain objects they depend on set by hibernate (or the persistence mechanism)
- domain objects perform the business logic, as the core idea of DDD is, but these do not include database queries or CRUD - only operations on the internal state of the object
- there is rarely need of DTOs - the domain objects are the DTOs themselves in most cases (which saves some boilerplate code)
- services perform CRUD operations, send emails, coordinate the domain objects, generate reports based on multiple domain objects, executing queries, etc.
- the service (application) layer isn't that thin, but doesn't include business rules that are intrinsic to the domain objects
- code generation should be avoided, and abstraction, design patterns and DI should be use instead, to overcome the need of code generation