Three Layer Architecture in Backend Development

リン (linh)
Goalist Blog
Published in
4 min readMay 11, 2023

I. Introduction

No matter what language you’re using, most REST API projects that you had worked or are working or will working on, probably have this 3-layer structure. The 3 layers are:

- Layer 1: Presentation.
On the first layer, handler method that matches api endpoint, will receive the request. The method, then, has to retrieve the data from the database. These handler methods are called controllers.

- Layer 2 : Business Logic
Controllers will not access database directly, so does the Business Logic layer. This is where we calculates and handle logic related to the request and response. For instance, if you want to check if the request user is authorized or not, or throwing exceptions, etc… ALthough this layer may retrieve or update some data from the application database. It doesn’t take responsibility for accessing the database; it sends requests to the next layer, the Data Access layer.

- Layer 3 : Data Access
This layer stores the data models, receives requests from the Business Logic layer and builds some queries to the database to handle these requests. Once the execution gets done, it sends the result back to the Business Logic layer.

Explicit, Easy to maintain, develop and reuse. Each layer is dedicated to a single responsibility, so it would be easy to make modifications where needed.

II. How to implement

You can jump right in the project and create folders to hold these layers, and write the code separately from the start.
In order to get used to the structure, for every request, i usually write all the logics in controller. Then, try to separate the code into 3 layers. Like people always say: “Practice makes perfect”.

I’m using Springboot for this example, but it looks pretty neat for a simple REST API example (cause the frameworks has many things run under the hood), so I use a standard MVC sample instead.

// GradeController.java

@Controller
public class GradeController {

List<Grade> studentGrades = new ArrayList<>();

@PostMapping("/handleSubmit")
public String submitForm(@Valid Grade grade, BindingResult result) {
if (result.hasErrors()) return "form";

int index = getGradeIndex(grade.getId());
if (index == Constants.NOT_FOUND) {
studentGrades.add(grade);
} else {
studentGrades.set(index, grade);
}
return "redirect:/grades";
}

@GetMapping("/grades")
public String getGrades(Model model) {
model.addAttribute("grades", studentGrades);
return "grades";
}

public int getGradeIndex(String id) {
for (int i = 0; i < studentGrades.size(); i++) {
if (studentGrades.get(i).getId().equals(id)) return i;
}
return Constants.NOT_FOUND;
}
}

The code is handling the request of grading students and getting all students grade. To split this into 3 layer, try to remind yourself:

- controller (presentation layer) is to match the request url
- repository (data access layer) is to get the data
- remaining things will be in the business logic layer

By that, some pieces of above GradeController.java (presentation layer) file will be moved to GradeRepository.java (data access layer) and GradeService.java (business logic layer).

We’ll keep the url mapping and the return response in controllers

// GradeController.java

@PostMapping("/handleSubmit")
public String submitForm(@Valid Grade grade, BindingResult result) {
if (result.hasErrors()) return "form";
gradeService.submitGrade(grade);
return "redirect:/grades";
}

@GetMapping("/grades")
public String getGrades(Model model) {
model.addAttribute("grades", gradeService.getGrades());
return "grades";
}

The logic in between that check for the grade id or creating a list grades and requesting the data access to next layer, will be in services.

// GradeService.java

public class GradeService {

GradeRepository gradeRepository = new GradeRepository();

public Grade getGrade(int index) {
return gradeRepository.getGrade(index);
}

public void addGrade(Grade grade) {
gradeRepository.addGrade(grade);
}

public void updateGrade(Grade grade, int index) {
gradeRepository.updateGrade(grade, index);
}

public List<Grade> getGrades() {
return gradeRepository.getGrades();
}

public int getGradeIndex(String id) {
for (int i = 0; i < getGrades().size(); i++) {
if (getGrade(i).getId().equals(id)) return i;
}
return Constants.NOT_FOUND;

}

public Grade getGradeById(String id) {
int index = getGradeIndex(id);
return index == Constants.NOT_FOUND ? new Grade() : getGrade(index);
}

public void submitGrade(Grade grade) {
int index = getGradeIndex(grade.getId());
if (index == Constants.NOT_FOUND) {
addGrade(grade);
} else {
updateGrade(grade, index);
}
}
}

And the data access layer will interact with database to get the data.

// GradeRepository.java

public class GradeRepository {

private List<Grade> studentGrades = new ArrayList<>();

public Grade getGrade(int index) {
return studentGrades.get(index);
}

public void addGrade(Grade grade) {
studentGrades.add(grade);
}

public void updateGrade(Grade grade, int index) {
studentGrades.set(index, grade);
}

public List<Grade> getGrades() {
return studentGrades;
}
}

The data access layer is independent from the other two. If you decided to write the code separately from the beginning, it’s easier to write the data access layer first because you already know what you want to get from the database, then move on to the controller, because you know what you want to return to the UI, finally, move on to the logic part.

To be honest, i have always focused more on the frontend side, but i’m puting my foot in the backend side now so I consider this as a note to myself, but i hope it helps you too 🙏

--

--

リン (linh)
Goalist Blog

A career-changed-non-tech-background point of view.