The developers who are using Spring Framework in their applications are good to talk about the benefits of dependency injection. Unfortunately, they are not so good to leverage its benefits such as the single responsible principle and separation of concerns in their applications. If we take a look at any Spring powered web application, the odds are that the application is implemented by using these common and equally erroneous design principles:
- The domain model objects are used only to store the data of the application. In other words, the domain model follows the anemic domain model anti-pattern.
- The business logic lies in the service layer which manages the data of the domain objects.
- The service layer has one service class per each entity of the application.
The question is: If this is so common, how can it be wrong? Let’s find out.
Old Habits Die Hard
The reason why Spring web applications look this way is that this is the way things have always been done, and old habits die hard, especially if they are enforced by senior developers or software architects. The problem is that these people are very good at defending their opinions. One of their favourite arguments is that our application follows the separation of concerns principle because it has been divided into several layers and each layer has specific responsibilities.
A typical Spring web application has the following layers:
- The web layer which is responsible of processing user’s input and returning the correct response back to the user. The web layer communicates only with the service layer.
- The service layer which acts as a transaction boundary. It is also responsible of authorization and contains the business logic of our application. The service layer manages the domain model objects and communicates with other services and the repository layer.
- The repository / data access layer which is responsible of communicating with the used data storage.
The separation of concerns principle is defined as follows:
Separation of concerns (Soc) is a design principle for separation a computer program into distinct sections, such that each section addresses a separate concern. Although it is true that a typical Spring web application follows this principle in some level, the reality is that the application has a monolithic service layer which has too many responsibilities. To be more specific, the service layer has two major problems:
First, the business logic of the application is found from the the service layer.
This is a problem because the business logic is scattered around the service layer. If we need to check how a certain business rule is implemented, we have to find it first. This might not be easy. Also, if the same business rule is needed in multiple service classes, the odds are that the rule is simply copied from one service to another. This leads into a maintenance nightmare.
Second, the service layer has one service class per each domain model class.
This violates the single responsibility principle which is defined as follows: The single responsibility principle states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.
The service classes have a lot of dependencies and a lot of circular dependencies. The service layer of a typical Spring web application does not consist of loosely coupled services which have only one responsibility. It is a more like a net of tightly coupled and monolithic services. This makes is it hard to understand, maintain and reuse. This might sound a bit harsh but the service layer is often the most problematic part of a Spring web application. Luckily for us, all hope is not lost.
The current situation is bad, but it is not totally hopeless. Let’s find out how we can break free from old habits.
First, we have to move the business logic of our application from the service layer to the domain model classes.
The reason why this make sense should be clear to us if we think of the following example:
Let’s assume that I am a service class and you are a domain model object. If a tell you to jump off from a roof, would you prefer to have a veto right to my decision?
Moving the business logic from the service layer to the domain model classes gives us three advantages:
- The responsibilities of our code are divided in a logical way. The service layer takes care of the application logic and our domain model classes takes care of the business logic.
- The business logic of our application is found from a single place. If we need to verify how a specific business rule is implemented, we always know where to look for.
- The source code of the service layer is cleaner and does not contain any copy paste code.
Second, we have to divide the entity specific services into smaller services which serves only a single purpose.
For example, if our application has a single service class which provides CRUD operations for persons and operations related to user accounts, we should divide it into two separate service classes:
- The first service provides CRUD operations for persons.
- The second service provides operations related to user accounts.
This gives us three big advantages:
- Each service class has a logical set of responsibilities.
- Each service class has less dependencies which means that they are no longer tightly coupled giants. They are smaller and loosely coupled components.
- The service classes are easier to understand, maintain and reuse.
These two simple steps will help us to clean up the architecture of our application, and increase the productivity and happiness of our fellow developers.
Now, we might be wondering if all this is really necessary and if so, when it is critical to address these issues?
Sometimes Life Is Black and White
I have often heard an argument which states that we should not pay much attention to the “architecture” because our application is small and simple. Although this argument has some truth in it, we must remember that a project which starts small can grow into something much bigger.
If we don’t take this into account when it happens, we are screwed.
Sailing in uncharted waters might sound like a bad idea but we must remember that Titanic was sailing in a familiar route when it was hit by an iceberg which sank it. This same thing might be happening to our application right now.
We must have the courage to yell STOP when things are getting out of control.
P.S. If you are ready to take the red pill, I recommend that you read Whoops! Where did my architecture go by Olivier Gierke (or watch his SpringOne2GX presentation about the same subject). But beware, the rabbit hole goes much deeper than you think.