Multitier Software Architecture

Roman Jugai
5 min readJan 18, 2021

--

Photo by Michael D. on Unsplash

In this article we will examine an approach of layering back-end applications. The examples are given in Java and Spring Boot but the approach is language and framework agnostic. It can be applied to any backend application.

Separation of concerns

One motivation behind layering an application comes from the need to assign responsibility to specific components of the complete system that an application forms.

Intuitively, rather than viewing the entire system as a single entity, it is easier to picture a full application flow composed of individual layers, where each layer has its dedicated role.

Spring Boot

Spring ecosystem makes it easier to write enterprise grade applications using well defined design patterns. Spring Boot framework takes it further and enables developers to write enterprise grade applications with less code (via auto-configuration, annotations, IoC, and other means).

Scenario in practice

We can examine multitier architecture in a practical scenario of building an authentication service that is meant for users to authenticate with our backend.

Let’s look at the layers from a high level.

Controller (Contract layer)

Controller layer is essentially the ‘entry-point’ layer to an application. It is conventionally known as an API. From a functional standpoint, that layer defines a contract, specifically, how external or internal services interact with the application.

In Spring Boot, a “stereotype” for controller classes is known as @Controller or @RestController. The latter is a more specialized annotation to define a controller class that relies on the REST (http/https) protocol for communication.

Controller — scenario in practice

Let’s examine our Controller layer class for authenticating with the backend.

Authentication typically requires some type of login functionality, so our Controller has a contract that defines it— a function called “login” that is exposed at the endpoint/login and consumes LoginCredentials data transfer object via a POST request.

In the Controller layer we inject the Service layer class calledLoginService via @Autowired annotation. LoginService is the downstream layer that we will examine next.

Service (Business layer)

Service layer should generally not be exposed externally but rather encapsulated within the application. Controller layer indirectly exposes the Service layer by defining the correct contract.

Service layer is responsible for defining the business logic of an application. It often is the most complex layer when we look at the code, i.e. the brain of the application.

Service — scenario in practice

In the scenario of authentication we need logic for validating user’s credentials against some persisted state to ensure that a user is allowed to access our application. Details of how that (business) logic is implemented is the responsibility of the Service layer.

Our LoginService class defines a method called authenticate that validates a user’s access credentials, and upon successful validation generates an access token that is returned back to the client, or throws BadCredentialsException otherwise.

In practice, you would have LoginService use a production-grade authentication protocol library (for example, “OAauth2”) to validate credentials and generate an access token, but for the purpose of this article we simplified that logic.

In the Service layer we inject the Repository layer interface UserRepository via @Autowired annotation. UserRepository is the next downstream layer.

Repository (Persistence contract layer)

One way to think of the Repository layer is, an abstraction of how the application communicates with the persisted state (or storage).

Repository is the contract of how the Service layer interacts with the storage. We can call it a persistence contract, although, it is also known as data access layer.

Repository — scenario in practice

UserRepository interface defines a method called findOneByUsername which queries the (downstream) Persistence layer for a specific object (`User`) by the provided field (`username`).

We use Spring Data JPA repository implementation which defines “finder” methods that Spring framework automatically translates to the underlying storage backend language. This removes the need for writing boilerplate storage-specific code.

The power of such implementation is that you can apply the same abstraction to multiple storage backends (PostgreSQL, MySQL, Oracle DB, etc.).

Persistence (Domain layer)

The final layer (of the application flow) is the Persistence layer, also known as the Domain layer.

The responsibility of this layer is to define data objects of the application. Those objects can be viewed as the building blocks of the information that our application persists.

Domain layer also defines relationships (and constraints) between those objects and the types of information that those objects store.

Persistence — scenario in practice

In our authentication service example, we need to store information about the primary entity that authenticates with the application — a user.

User is the Domain layer class that stores information about a user. It stores 4 fields — id, username, password, and email. It defines a type for each of those fields, as well as certain constraints. For example, username should be unique and it cannot be null.

User object is queried in the Service layer and retrieved via the Repository layer. Service layer needs Domain objects for the business logic implementation. In our scenario, it needs to validate that the user has access to our application.

Configuration layer

Configuration layer can be viewed as a supporting layer in the application flow, and is an independent layer — meaning, it should not depend on any of the other layers. Its purpose is to store both variable and static configuration of the application. Configuration layer is implicitly or directly used in the Controller, Service, and Repository layers.

In Spring Boot, configuration is typically defined in .properties or .yaml files, and exposed to the code via @Configuration stereotype. In practice, there are multiple @Configuration classes which define the Configuration layer.

Configuration — scenario in practice

In our authentication service we define the Repository (and the Persistence) layer’s configuration via a dedicated block in application.yaml file - specifying database platform to use within our JPA repository and domain objects.

In the code we define dedicated@Configuration class specifying package paths to the domain classes (via @EntityScan annotation) and to the repository interfaces (via @EnableJpaRepositories annotation).

Source code for this article is here.

--

--

No responses yet