Layered architecture

The layered architecture pattern is a common technique that's used to break complicated systems into separate layers, where each layer focuses on its own concern. The usual separation is to use layers, that is, the presentation layer, business layer, and data access layer. A higher-layer depends on a lower-layer, while a lower-layer isn't aware of the existence of the high-layer that depends on it. In a strictly layered architecture, a higher-layer can only depend on a direct lower-layer. In a relaxed layered architecture, a higher-layer can depend on any layer underneath it. For example, the presentation layer can only depend on the business layer in a strict layered architecture, while it can depend on the data access layer in a relaxed layered architecture.

Sometimes, the business layer is called the application layer, service layer, or domain layer. Or, it might be divided into the application layer and service layer, which creates a four-layered architecture. No matter what the name of this layer is called, it contains the business logic of the application:

Figure 6.11: Layered architecture

The preceding diagram shows an example of this architecture. The User sends a request to get tasks. The Presentation Layer receives this request and converts it into an internal call to the API that's provided by the Business Layer, which performs a series of business logic and eventually asks the Data Access Layer for the data. The Data Access Layer generates an SQL query and sends it to the database. Once the database returns the result, the Data Access Layer will convert the row-based data into objects which will be sent back all the way to the Presentation Layer, where the result is converted into strings in JSON format and then sent back to the user.

As you can see, the layered architecture is straightforward and easy to understand. And for applications that do not have complex business logic, this architecture works well. It has a tendency to shift the focus from business logic to data manipulation, also known as CRUD. At the bottom of this architecture is the database. For applications that have only a few business rules where most of the requests are about reading data out of the database or saving the data back to the database, this architecture fits well. In these applications, domain models, such as entities, are all POJOs with a bag of getters and setters. They don't contain domain logic. There are often design rules saying that there should be no domain logic in models. Instead, the domain logic should be put into service objects in the business layer. These domain models are usually called anemic domain models.

One of the symptoms of this data-centric design is that, as the complexity of the application grows, it is easy to see domain logic leak into other layers. Let's see a simple example of activating a free trial user, as shown in the following code:

public class UserController {
@PostMapping("/users/{userId}/activate")
public void activate(@PathVariable(value="userId") int userId){
// get the user and calculate free trial end date
user.setActive(true);
user.setInFreeTrial(true)
user.setFreeTrialEndDate(trialEndDate);
user.setLastModifiedDate(now);
userService.save(user);
sendFreeTrialStartedEmail(user);
}
}

As you can see, in the activate() method, we use the setters to modify the user entity and then ask userService to save the changes, followed by sending a free trial started email to the user. A better implementation is to add an activate() method in the UserService API and move the setters and send email logic into that method, and then change UserController to the following:

public class UserController {
@PostMapping("/users/{userId}/activate")
public void activate(@PathVariable(value="userId") int userId){
// get the user
userService.activate(user);
}
}

There are still potential and serious issues with this design. As you can see, the User entity has two separate fields, active and inFreeTrial. When a user starts the free trial, both need to be set as true. Imagine this: one day, another developer, Frank, who isn't familiar with this logic, needs to create a new API to allow mobile app users to register, without reading the functional specification; well, he is probably in an Agile team, so there most likely won't be comprehensive specifications, but just user stories. Anyway, Frank doesn't know that when a newly registered user is activated, the inFreeTrial field needs to be set as true. In this way, a serious bug is introduced. Hopefully, the QA team will catch this bug before release.

It is true that, in the example, using layered architecture isn't the cause of this kind of issue. But the point is that layered architecture tends to keep developers looking at the system from the data perspective instead of the domain model perspective.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.183.138