Promotes Database-Driven Design

Learn how layers can promote database-driven design in our software architecture.

Introduction

Chances are that you have developed a layered (web) application in the past. You might even be doing it in your current project right now.

Thinking in layers has been drilled into us in computer science classes, tutorials, and best practices. It has even been taught in booksSoftware Architecture Patterns by Mark Richards, O’Reilly, 2015.

The 3 layers

The figure above shows an overview of the very common three-layer architecture. We have:

  • A web layer that receives requests and routes them to a service in the domain or “business” layer.
  • The service does some business magic and calls components from the persistence layer to query for or modify the current state of our domain entities.

Layers are a solid architecture pattern. If we get them right, we can build domain logic that is independent of the web and persistence layers. We can switch the web or persistence technologies without affecting our domain logic if we feel like it. We can also add new features without affecting existing features.

With a good layered architecture, we are keeping our options open and can quickly adapt to changing requirements and external factors. And if we believe Uncle Bob, this is exactly what architectureClean Architecture by Robert C. Martin, Prentice Hall, 2017, Chapter 15 is all about.

So, what’s wrong with layers?

In my experience, layered architecture has too many open flanks that allow bad habits to creep in and make the software increasingly harder to change over time.

In the following lessons, this will be explained in more detail.

Foundation of a layered architecture

By its very definition, the foundation of a conventional layered architecture is the database. The web layer depends on the domain layer, which in turn depends on the persistence layer and thus, the database. Everything builds on top of the persistence layer. This is problematic due to several reasons.

Let’s take a step back and think about what we’re trying to achieve with almost any application we’re building. We’re typically trying to create a model of the rules or “policies” that govern the business in order to make it easier for the users to interact with them.

We’re primarily trying to model behavior and not the state. Yes, the state is an important part of any application, but the behavior is what changes the state and thus drives the business!

So why are we making the database (and not the domain logic) the foundation of our architecture?

Think back to the last use cases you have implemented in any application. Have you started with implementing the domain logic or the persistence layer? You have most likely thought about what the database structure would look like and have only then moved on to implementing the domain logic on top of it.

This makes sense in a conventional layered architecture since we’re going with the natural flow of dependencies. But it makes absolutely no sense from a business point of view! We should build the domain logic before doing anything else! Only then can we find out if we have understood it correctly. And only once we know we’re building the right domain logic should we move on to build a persistence and web layer around it.

A driving force in such a database-centric architecture is the use of ORMObject-Relational Mapping frameworks. Don’t get me wrong, I love those frameworks and I’m working with JPAJava Persistence API and Hibernate on a daily basis. But if we combine an ORMObject-Relational Mapping framework with a layered architecture, we’re easily tempted to mix business rules with persistence aspects.

Database-driven development

Usually, we have ORMObject-Relational Mapping-managed entities as part of the persistence layer as shown in the figure. Since layers may access the layers below them, the domain layer is allowed to access those entities. And if it is allowed to use them, they will be used.

This creates a strong coupling between the persistence layer and the domain layer. Our services use the persistence model as their business model, and they not only have to deal with the domain logic, but also with eager vs. lazy loading, database transactions, flushing caches, and similar housekeeping tasks.

The persistence code is virtually fused into the domain code, and thus, it is hard to change one without the other. This is the opposite of being flexible and keeping options open, which should be the goal of our architecture.