The Biggest Flaw of Spring Web Applications
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.
Petri, nice article
In case you have a company that has multiple lines of businesses, which operate on the same data but from different angles, you usually prefer to separate logic from data so that each line of business can have their own separate logic layer, while all can share the same data model.
This provides an abstraction, which give you great flexibility, especially to try out new use-cases and discard them if they do not apply or perform as expected without to much noise on the rest of the platform.
Sebastian, you should of course follow the path which makes the most sense to you. Also, If putting business logic to domain model classes makes things more complex, you should not do it. We should make our code simpler, not more complex. About your example: If the different businesses use same data from different angles, doesn’t using the same data model lead into compromises in the names of domain objects? What I mean is that this might lead into a situation where you have to create a generic data model which is not a perfect match for any of the… Read more »
As always, a good read Petri. But I’m having a hard time accepting the idea of moving all the business logic to the domain classes. In fact, this feels a bit like the old days where the business logic was all stored procedures in a RDBMS and I certainly don’t want to go back there. There is some logic that would make sense to live at this level, “business logic” that I would tend to think of as domain logic…. you can’t build an X without a Y being in state A. However, a lot of the business logic that… Read more »
Sean, you just found the same problem which has bothered me lately. As you probably noticed, I made a vague separation between “application logic” and “business logic”. These things are quite close to each other and maybe better names would be “business logic” and “domain logic”. We need to have something which manages the domain model objects of our application and often these responsibilities also fit under the term “business logic”. These responsibilities might include things like transactions, security, rollback behavior, integrations and so on. Putting this logic to the domain model classes is an awful idea. The reason why… Read more »
I agree with the article in general, but the same than my fellows before me here, I don’t quite like the idea of moving the business rules into the model layer. It simply doesn’t feel OK. Some business rules can involve more than one entity, and other problems arise in my mind immediately. The solution we’ve applied with good results along the years has been encapsulating the business rules into services classes (one rule per service). I’ve never done or seen the “one service class per each entity” but probably is just because we’re not following the common trend, which… Read more »