Expensive Mistake That Often Plagues Layered Architectures

Lessons learned from painful experiences in working on god object-dominated projects without clear business logic.

Giedrius Kristinaitis
Level Up Coding

--

Sliced onions that illustrate layers
Photo by Wilhelm Gunkel on Unsplash

Business Logic Is Often Kept in the Service Layer

A quick Google search tells me that pretty much everyone defines the service layer a bit differently. It’s likely that everyone reading this article also would define it a bit differently. However, one thing I’ve noticed in common between people’s definitions of what the service layer is was that the service layer is supposed to encapsulate business logic.

What does it mean that the service layer is supposed to encapsulate business logic? This statement can be interpreted differently, so it tells us nothing of value.

What I see the most is the belief that the service layer is nothing more but a gateway through which the presentation layer accesses the business layer. Sounds like a terrible thing to do, but it’s common in practice. It’s what I’ve seen in a few companies (and a lot of online blogs), so I suspect it’s pretty much the same in many organizations.

What often results from such belief is the realization that there’s no point in having a separate business layer if the service layer just passes requests to it, and that’s what makes people merge the service and business layers together and state that the service layer contains business logic, which essentially leaves only the service layer and eliminates business logic as an independent part of the system.

Why Merged Layers Are A Problem

What follows from combined layers is a dysfunctional 3-layer architecture which consists of the presentation layer, the service layer, and the persistence layer. The worst one is the service layer, because in it you typically only see what I call service classes, for example, BookService, UserService, OrderService, and so on…

So what’s the problem? Notice something strange? People consider the service layer as the one that contains business logic. However, if you look at it, you only see service classes. Where’s the business domain? It’s nowhere to be seen. Essentially, you have the same class that serves as 2 things: business logic, and a part of the service layer.

The service layer often consists of god objects

Let’s define one more thing. A common anti-pattern called god object.
What is a god object? It is an object that knows and does everything. Pretty easy to understand. We all know that it’s a bad anti-pattern that should be avoided.

Why am I talking about god objects? If you look closer at the service classes, they do everything related to a domain object, like, book, user, or order. They are literal god objects. What’s supposed to be business logic is often inside god objects. It doesn’t exist on its own as an independent unit.

This creates a lot of problems. God objects are difficult to maintain, because everything’s all over the place and there’s a lot of coupling. They violate most principles of good design. For example, the single responsibility principle. If you take a look at a typical service class implementation you’ll see that it has more than one reason to change. I’m not going to expand on that, as it’s a story that deserves a whole article on its own.

You might be thinking “ok, fair point, however, I don’t use OOP in my project, so I don’t write service classes”, if so, just stay with me for a while, because the problem has nothing to do with the programming paradigm you’re using or classes in particular.

It doesn’t matter if you’re writing procedural code, you can still create a module that’s equivalent to a god object where coupling is through the roof.

The merging of the service layer and the business layer is the result of people being unaware of the difference between a service and the business layer. That’s why you have service god objects that do everything and are dependent on the outside world.

The difference between services and the business layer

What is a service actually supposed to be? A service is a set of functionality that clients can call to do something, be it performing a computation or retrieving some data. It’s an abstraction. Abstractions exist for a reason. They hide the way something’s done. What sits in a module that serves as an abstraction to something knows what’s being done behind the abstraction. Meaning, it’s not an independent unit.

What is the business layer supposed to be? It’s supposed to be a unit that’s independent of everything else that happens in your system and only knows about your business domain, in other words, your business model, what you do to provide some useful services.

What happens when you merge the dependent service layer and independent business layer into one layer?

When you merge layers you eliminate the boundary between them, and, since you see them as one, you eventually make the business layer dependent on the outside world.

From what I’ve seen people start writing the service god classes I mentioned before.

False belief

Why is that the case? Is it that people believe that their architectural choice automatically guarantees quality? Looks like it. This false belief is especially obvious when you look at a typical microservice architecture where method calls are just replaced by HTTP calls and that’s about it. That’s also a different story that deserves an article of its own.

Why else would people write god objects? When did the god object anti-pattern become a pattern most people accept by changing its name to service and arguing that it’s a part of the service layer, and because it’s layered architecture, it’s automatically good?

Having merged layers is pointless

When you start thinking of your business domain as an independent unit the service layer and the business layer separate as a result, which defeats the point of merging them in the first place.

If you think about it, what is the point of merging the service layer and the business layer? I don’t see any good reason.

Even if you encounter a situation where one layer just passes requests to another it’s not a good reason, because the thing with layered architecture is that when you encounter such situation, you allow one layer to bypass another, but you don’t just start merging layers.

Architecture Doesn’t Guarantee Anything

Architecture doesn’t guarantee quality. Just because you’re using layered architecture doesn’t mean your system is automatically good. God objects are still bad regardless of your architecture.

The 2 main benefits of using layered architecture are ease of development and testability. Now, what are the consequences of the god object anti-pattern? It makes your code difficult to maintain and practically impossible to test. So when you start writing your service + business layer using only god objects, you go against the benefits of layered architecture and render them useless in said layer.

Recap And Tips

Here are the key takeaways that resulted from my painful experience in working on projects that merge the service and business layers into one and consist of god objects:

#1. Don’t treat the service layer as the one that contains business logic

I still believe that if you do such a thing you can still get by without any trouble, but that requires a lot of care. You might understand that, however, if you’re working in a team, there might be (and often is) someone who doesn’t, that’s why it’s important to keep things that should not be together separate.

If you find your application consisting of mostly service classes, you should probably reconsider what you’re doing.

God objects are bad, and calling them services doesn’t change what they are.

#2. Don’t think of the service layer solely as a gateway to the business layer

Think of the business layer as a layer that’s independent of the outside world.

Think of the service layer as a layer that encapsulates functionality from other layers in your application and its purpose is only to serve as an abstraction, which the business layer can refer to when it needs to execute something that is not a part of it.

#3. Don’t assume that layered architecture will guarantee quality

It won’t. Architecture choice should be an informed decision. If you choose layered architecture, the layers it consists of should also be an informed decision.

#4. Allow one layer to bypass another if necessary

This is allowed in a layered architecture. However, if you find yourself needing to bypass layers very often it indicates that some layers are unnecessary or the architecture is not the best fit and you should make some alterations.

#5. Establish a clear difference between the service layer and the business layer

What that difference is should be your informed decision which suits your project best. Make sure that everyone working on your project is aware of that difference.

#6. Avoid service and manager classes without a clear purpose

When you have classes like BookService, UserService, BookManager, or UserManager, they don’t really tell you anything about what they do.

Purposeless classes imply that it’s ok to put everything related to your domain objects into one class and that results in god objects, which makes people think less, which makes the maintenance of your project very expensive.

Merging the service layer with the business layer is a big mistake that eventually leads to a pile of bad decisions that make your codebase impossible to work with and can have serious consequences.

--

--

I’m a software engineer who loves to explore ways to make development more effective and enjoyable. Love to call out bad practices and misunderstandings.