When we design an application, one of the first decisions we need to make is what software architecture to implement. A software architecture describes the major components of an application, as well as how they are expected to interact with each other. In this topic, we will look at a common software architecture for web apps, known as layered architecture.

What is layered architecture?

The goal of a layered architecture is to divide our application into many layers with each of them playing a specific role in the application. We organize our layers into a vertical structure that resembles a stack of items. Each of the layers will interact only with the layers directly above or below it. In a standard layered architecture, there will be four core layers:

  1. Presentation Layer: handles the interface that is displayed to the user.
  2. Business Layer: implements rules for handling the problem your application was designed to solve.
  3. Persistence Layer: contains logic related to database storage and handles translating objects into database formats.
  4. Database Layer: contains the actual database storage system, handles tables, indices, and any other database-related operations.

To understand how a layered architecture works, let's see what happens when a request is sent, and a response is returned. When a request is sent, it starts at the top layer (the presentation layer) and moves down layer by layer until it reaches the bottom layer (the database layer). When a response is returned, it starts at the bottom layer, and moves up layer by layer until the top layer is reached.

To see why this architecture is valuable, imagine an application that has an international user base. Since the users may be based in different countries, it would be ideal to display the user interface in multiple languages, depending on the user's preference. In a layered architecture, we could implement multiple user interaction layers, each in a different language. Since the layers are autonomous, we can vary the user interaction layer without impacting the rest of the application. In these situations, the app maintenance is easier, as the changes required are minimized for each layer.

Although this architecture can make certain situations easier, there are some disadvantages to consider. Having multiple layers can make your application more difficult to maintain. For example, each change to a layer requires analysis on how it impacts the application. In addition, a layered approach can have performance impacts, since a request may have to travel through many layers before it is completed. In other words, it is important to consider if the benefits of a layered approach outweigh the potential maintenance and performance impacts.

Now that we understand what a layered architecture is, let's see how to implement one in Spring.

Implementing layered architectures

It is possible to create a Spring application without any of the layers, however, it is not recommended for real applications

Suppose we wanted to create an application that stores users in a database and retrieves them from it. We would like users to be able to access the application through REST endpoints, and we will store the data inside an H2 database. The H2 database will represent the database layer of our application. The configuration below shows how we will set up our database.

# Setup for the H2 console, used for viewing data in the database
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.h2.console.username=sa

# H2 data source setup
spring.datasource.url=jdbc:h2:file:~/test
spring.datasource.username=sa
spring.datasource.password=

# Automatically update tables when persistence objects have changed
spring.jpa.hibernate.ddl-auto=update

In order to store data in our database, we will need to implement an object that represents the data being stored. For that, we will implement our User object at the business layer of the application. Our User implementation will include the @Tableand @Column annotations, for it to be stored in the database. We will give our User a unique ID and store their username, first and last name in our database.

For each of the layers in the application, we will create a new package to contain the layer code. This will help us create isolation between the components. We will store this component in the com.example.demo2.businesslayer package.

package com.example.demo2.businesslayer;

import javax.persistence.*;

@Entity
@Table(name = "user")
public class User {

    @Id
    private long id;

    @Column(name = "username")
    private String username;

    @Column(name = "firstName")
    private String firstName;

    @Column(name = "lastName")
    private String lastName;

    public User() {

    }

    public User(long id, String username, String firstName, String lastName) {
        this.id = id;
        this.username = username;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // getters and setters
}

Next, we can build a persistence layer, which will interact with our database layer. We will implement our persistence layer using a @Repository, specifically, a CrudRepository. This will allow us to create basic CRUD queries for our database. The CrudRepository for this example will be stored in the com.example.demo2.persistence package. The code below shows how you can implement a CrudRepository .

package com.example.demo2.persistence;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.example.demo2.businesslayer.User;

@Repository
public interface UserRepository extends CrudRepository<User, Long> {
    User findUserById(Long id);
}