Anemic Domain Model in Typical Spring Projects (Part 1)
Anemic Domain Model in Typical Spring Projects (Part 1)
One developer talks about how design patterns hold up in the real world as they examine layered architecture and domains models.
Join the DZone community and get the full member experience.Join For Free
When I just started my career as a Java developer, I knew only a few basic design patterns. I tried to implement them everywhere, even when I shouldn't have, and I thought that more experienced people use them on a daily basis. With time, I have changed several different teams and companies, but I have never seen real usage of “real” patterns. Of course, I am not talking about Builder, Singleton, or Abstract Fabric, but rather about more complicated and less common ones like Bridge, Observer, etc. Familiar situation, isn’t it?
For a full understanding of the problem, we should begin with the basics.
Low coupling and high cohesion tell us that every class shouldn't have a lot of dependencies from other classes (low coupling) but every class has to contain only those methods and fields that it requires and it should solve only one specific problem (high cohesion, single responsibility). If you put all your code into only one class, that is low coupling but not high cohesion.
What does a typical project written with the Spring framework look like? A classical three-tier application implements an MVC pattern. A controller has some endpoints and receives requests with them. Next, it calls a method. The method contains all of our business logic, creates domain entities, and persists it to the database (or get/delete it).
Example of layered architecture:
But MVC has only three layers, so where should Services be in this pattern? Spring encourages us to split the Model layer to Model and Service. In this variation of the pattern, the model contains information and service behavior.
Official Spring tutorials teach us that domain objects shouldn’t have any methods except getters and setters and they should be POJOs. Many authors (like Martin Fowler) consider it an antipattern and call it the Anemic Domain Model.
With Anemic Domain Design, all a program’s logic is kept in the business logic layer (classes with Service or BO suffixes). Then domain objects don’t have methods that operate with class fields, hence the object doesn’t have behavior. That breaks OOP principles, GRASP patterns, and prevent us from implementing design patterns.
Example of the anemic model:
Information Expert tells us that method should belong to an object whose fields it uses. Or fields should belong to an object that uses them.
Creator says that only a class A that uses class B or has dependencies from A should create instances of B.
If there is a method chain that passes the same object down to other methods in different objects, it violates the Creator pattern because the object should be used in the same place where it has been created. However, often we need to transfer an object between layers or call a method in a remote machine or pass data from the front end/a microservice. In that case, we use DTO, which is a violation of OOP because DTO is an object without behavior. We can’t transfer the object’s methods through the network. Still, we do this consciously since there is no other way.
A constructor is a method. Therefore Creator is a sub-case of Information expert.
Adherence to these principles leads to low coupling. Not sticking to those rules contradicts the principle of encapsulation — one piece of code should not touch another piece with its greasy fingers. Even if an object passes information by a getter, it is still information. It is WRONG to pass and process information in other places. Over time, the system will be very difficult to change.
What do we have in a typical Spring application? All logic is located in services, which are singletons. It is a variation of the Singletonism antipattern and leads to a procedural style of programming. Therefore we have a lot of duplicated code, methods with too many parameters (4+), and local variables that, in turn, makes an Extract Method impossible.
Example of Extract Method:
Protected Variations — the problem of system requirement changes is real in enterprise development. It is often possible to identify the instability points of the system that will most likely be subject to changes and modifications. The essence of the Protected Variations pattern is to eliminate the instability points by defining them as interfaces and using polymorphism to create various implementations with different behaviors of this interface.
Protected Variations solve an issue when changing one element of the system causes changes in other elements.
In practice, it looks like this: there is a method with a zillion nested IF clauses. If you change one inner clause, it causes changes in the outer clause's behavior. You can use the Replace Conditional with Polymorphism to avoid this. Instead of hundreds of IF clauses, it's best to create hundreds of polymorphic classes that encapsulate logic, which is encouraged by Information Expert. However, in an anemic model, this logic will be held in services. Therefore there is no place to put those methods and create new classes.
Many inexperienced programmers are afraid to create additional classes. You can often hear the phrase "too many classes". But if the big ones are not divided into smaller ones, it will violate the high cohesion principle.
The number of errors per line of code is a constant. For example, a programmer makes 1 error per 1000 lines. In practice, it looks like this: a 1000 line-long method always has bugs that are hard to find. You fix one but after a while, a new one appears. However, if you split the method into small ones and extract them to separate classes, there will only be a few lines of code in each, free from errors, and you won’t open it without needing to. If there was a bug, it would be easy to find and fix because we have the class with only a few lines. After the fix, we will forget about that class.
Ideally, you should work with classes only through interfaces. I would recommend borrowing an idea from Test Driven Development, i.e, to create a user-friendly interface first and then implement it. This will improve the quality of the code, assuming we make ourselves clients of our code and immediately see how convenient it is to use the new method.
Example of Protected Variations with Replace Conditional With Polymorphism:
The same rules can apply to all refactorings and patterns that use polymorphism: Bridge, State/Strategy, Replace Type Code with Subclasses, etc. I have never seen polymorphic domain objects in my practice, hence I have not seen the usage of polymorphism.
Rich Domain Model
What if we try to move our business logic directly to the model, as OOP requires? There are different variations of the Rich Model.
Example of domain model written in Rich Domain Model Extreme style:
As we can see, we introduced the model layer to DI and other auto-magic, which ruins layered architecture. In addition, there may be too many methods and fields in this object that have nothing to do with the business logic.
This is it for the first part. In the second part, we will discuss the advantages and disadvantages of both domain models, then try to find a balance between them.
Opinions expressed by DZone contributors are their own.