Layered architecture is the first architectural pattern that we are going to explore. If you haven’t read my article about software architecture, I suggest you do it before continuing with this one.
What is a layered architecture?
Simply put, the layered architecture pattern’s main idea is to group and isolate system concerns while defining strict communication direction between them.
Each group of related modules/classes we call layer. Different layers are encapsulated and depend on each other through abstraction and well-defined interfaces.
Layers in this architecture pattern are stacked. The request direction is from the upper layers to the lower layers. In the best-case scenario, a layer can request a layer that is only directly below it.
In sporadic cases, an upper layer is allowed to request a layer two-level or more levels below, but in general, it is considered bad practice.
Layered architecture is widespread. We can find it the simple client->server pattern or OSI network model. Also, many enterprise applications implement that pattern.
*In the following examples, we do not discuss cross-cutting concerns and their implementation.
What is the difference between layered architecture and N-tier architecture?
There is none significant one. By tier, we consider a physical layer (like SQL server, for example). N describes the number of layers/tiers you have.
In general, both names refer to the principles we discuss in this article.
Enterprise application example
Most of the layered architecture implementations consist of four layers (ok, three layers, and a tier).
- Presentation layer
- Business layer
- Data Access layer
- Data Storage tire
Their names are explanatory enough, but let’s discuss them in detail.
In the presentation layer, you can find user interface components.
In a standard ASP.NET Core application, these are the Views. The presentation layer, of course, is not limited to any technology. You can have anything that a user can interact with. CLI, SPA, etc.
The presentation layer should not contain business logic and should not access database data directly.
Its only task is to visualize data and dispatch the user’s input. That’s why the presentation layer requests data and sends commands from/to the business layer.
The business layer is where your core business logic lives. Here you can find modules and services (classes) specific to the business domain operations. Operations like calculating discounts, rearranging schedules, etc.
If data is needed, the business layer requests it from the data access layer.
When returning a result to the presentation layer, the business layer should not apply any data formatting. As we understood from the above paragraphs, this responsibility is entirely for the representation layer.
Data access layer
The data access layer or persistence layer’s main task is to communicate (query and persist data) from/to the data storage (SQL DB, NoSQL, flat files, etc.).
The most common data access layer translates requests to SQL queries and sends them to the SQL Server via data access objects (DAOs). In most cases, these DAOs implement a Repository pattern.
The data access layer also implements transaction capability (often through a Unit of work pattern). You can also find a Unit of work implemented in the business layer in some cases, but this is wrong.
I intentionally don’t say database because your storage could be anything—even CSV files. Of course, no sane man will do that.
You should not put any logic in this tier. Ideally, you should use it only for storage.
Structure of layered architecture
In a layered application, the structure is quite simple.
The example I am giving you is with MS stack, but technology does not change the principles we should follow.
Also, you will see that we have three logical layers and three tiers.
Consider the example set up at on-premises infrastructure, not in the cloud.
On a separate machine (tier 1) with IIS, we have a standard ASP.NET Core application. This is the presentation layer.
One level below (tier 2), we find an IIS on a separate server. On it, there is deployed a Web API application.
In the Web API application, we can see a very thin layer of REST APIs (implemented through Web API controllers), the business layer, and the data access layer.
The primary responsibility of the APIs controllers is to route a request to the business layer, so we don’t consider them important enough to classify them as a separate layer,
Controllers, business layer, and data access layers are isolated. They live in different projects and reference each other only through interfaces. We use dependency injection for instantiating services.
At the bottom (tier 3), we have a machine with an operational database managed by MS SQL Server.
In the past, most of the applications didn’t split their logical layers over different tiers (except the data storage tier). We do this to increase isolation, flexibility (to some extent), and ease of deployability.
Scale-out benefits are limited due to the monolithic nature of the architecture.
Flow of layered architecture
What happens when, for example, a user searches for a product (if we consider our application is an eCommerce website)?
The ASP.NET Core application UI requests the Web API specific REST API.
The API controller instantiates a service object from the business layer and requests the search through a specific method.
On its behalf, the business layer instantiates a data access object (or several) from the data access layer and calls a specific method/s.
The data access object translates the request to a SQL query and sends it to the SQL Server.
SQL Server returns the result. The data access object returns the result in the form of data transfer object/s to the business service.
Suppose any transformation or logic is needed (like calculation discount, as we said earlier). In that case, the business service transforms the data based on the business logic and returns it to the Web API controller.
The Web API controller returns an HTTP response to the ASP.NET Core application.
ASP.NET Core application receives the data formats it and shows it to the user.
When to use layered architecture?
Layered architecture is a great pattern, but it comes with its advantages and limitations like any other tool.
If you need a highly scalable solution, this architecture is not for you. The monolithic nature of the pattern prevents you from scaling out different components or services.
Another drawback of the pattern is its lack of agility. Even with good isolation of horizontal layers, it is a matter of time for the internal components to become “chatty” and form coupled dependencies.
And of course, this affects the deployment process. In most cases, even with different tier separation, it is all or nothing.
Or you don’t need a layered architecture at all. If most of your business layer’s services only redirect requests from the UI to the data access layer and return data, this implementation would be overkill.
Layered architecture is a very developer-friendly pattern. Even if your dev team does not have enough experience, following this pattern won’t be hard.
The pattern is also widely used and recognized among developers, so a new joining member can quickly become productive.
Separation of concerns helps for concurrent work over functionalities. It is common to see front-end developers working on the UI while back-end developers develop business services logic and data access functionalities.
Another great plus is testing. Having layers separated is a lot easier to mock them and test in isolation.
If your application does not need to scale out, and the business logic is not very complex, the layered architecture is an excellent choice for rapid development and delivery.
A long time ago, I read that layered architecture is like lasagna (with its layers), and it is better to have lasagna for lunch instead of spaghetti :). I can not agree more.
But In my next article, we are going to add the onion. Yes, the following architecture pattern is Port and adapters (Onion architecture).