As applications grow in complexity, one of the ways to manage the application is to break it up based on various responsibilities or concerns. This separation of concerns principle helps us to organize our codebase. This will help us to organize our code into different layers. You might be familiar with this design approach by the following names.
- Or Layers architecture in general.
A few of the benefits of layering approaches are below
- It supports reuse of common low-level functionalities.
- Standardize on implementations.
- Enforce restrictions on which layer can communicate with other layers.
- It helps us to achieve encapsulation.
Though our intentions with layering approach is good, I have often seen violation of principles. Any time I introduce a change, let us say a database table, it affects the entire application or at least more one layer.
In this tutorial, let us review a simple scenario with an example. We will be discussing
- What are we doing wrong?
- Finally, we will review the golden rule (Dependency Inversion Principle) that we can leverage when separating your code into different layers.
Without much delay, lets jump onto the GUI, and create a simple project.
For this tutorial, I’m using the below tools
- Visual Studio Community Edition 2022 Version 17.3.0 Preview 2.0
- .NET 6.0
- Web API
Create a simple ASP.NET Core API project with default template.
Let us go ahead and execute this project
We will have only one endpoint as below
Go ahead and run the end point and we will be getting the below response.
If we go back and look at the solution explorer, we will see, all pages and classes have added a single project.
Now let us see how this project can be refactored into different layers. The typical layers are below
- User Interface (UI)
- Business Logic Layer (BLL)
- Data Access Layer (DAL)
In this approach,
- UI will interact with BLL
- BLL will interact with DAL
Now let us try to simulate these layers into our project.
As a first step, add a new project library and name it as WeatherApi.BLL, later add another project library called WeatherApi.DAL. The overall solution looks like below
The next step would be to set up the dependencies between these projects.
Add WeatherApi.BLL reference into WeatherAPI and Add WeatherApi.DAL into WeatherAPI.BLL.
The below picture will help you to understand the dependencies.
Now will start the real game. To start with, let us move the WeatherForcest.cs class from WeatherAPI to WeatherApi.BLL layer. As we are dealing with Weather data, it is obvious that this Model class can be part of BLL layer. This is also called Data Transfer Object.
As we are only discussing about Layering, I’m not going to discuss various terminologies such as Entities, DTOs, POCO models etc. We will be focusing only on Layering.
For WeatherAPI application, to interact with BLL layer, we need to have a class that help us to read weather data. Now let us go ahead and add a new class into BLL library called WeatherService. The class looks like below
To implement the “Get” method, we need to have DAL class which will configure some dummy weather data. Let us go ahead and a new class in DAL layer called “FakeWeatherDataRepository.cs”. In this class we need to add a method which return a dummy collection of Weatherforcecast data. As DAL don’t have the Dependencies with BLL layer- we need to follow either of the below approaches.
- Extract the WeatherForecast class and put it in a new project library and name it as something like WeatherApi.Model and add this as a dependency into BLL and DAL projects.
- Add a WeatherForecast model into DAL library itself and have the Mapping class in BLL. This might be because the DAL has a structure that maps to our database or an external service.
We are going to follow the approach # 2. Let us go ahead and add a new class under DAL and name is as WeatherForecastDto.cs. The class looks like below
Here I have removed the Property “TemperatureF” as it has been calculated based on the TemperatureC property
Now the time to implement the “Get” method DAL layer. The method looks like below
As a next step, we will use this repository method in BLL layer. To avoid direct dependency on this Fake Repository class, let us extract an interface from this. To extract an interface, follow the below steps.
1. Select the class FakeWeatherDataRepository
2. Go to Edit -> Refactor-> Extract Interface
A pop-up will be displayed as below
Just click on “OK”, a new interface class will be created in DAL layer.
The interface looks like below
Now the time to inject this interface into BLL layer. Post Implementation, the WeatherService class looks like below
Now, the time to refactor the interface from BLL, follow the same approach we did for DAL and create an interface.
Interface looks like this
Now let us go ahead and inject this IWeatherService into Controller
The updated controller looks like this.
Note: Ensure that you are deleting the WeatherForecast.cs class from WeatherAPI project.
Let us build and ensure that the build is successful. Since we have multiple interfaces injecting into the classes, we need to register these interfaces into the dependency container.
As we are using .NET 6.0, there is no Startup.cs class and dependency registration has been to be incorporated into Program.cs
Let us go ahead and execute the program. The below swagger screen will be displayed
Provide the number of days and execute.
We got the same response. The response is based on the number of days which we are passing.
If we look at the request flow, it will be top to bottom, ie, the User Interface (Controller) call the BLL (Business Service), and the BLL calls DAL (Fake Data Repository). The dependency also flows the same direction which means, the UI depends on BLL and the BLL depends on DAL. In other words, I would say, the higher layer depends on the implementation details of lower layer because it is referring dll from the lower-level layer.
Though we have extracted the interfaces for decoupling these dependencies, there is a physical dependency on these DLLs. Let us look at this in action.
Assume that we have a change in WeatherForecastDto class to replace a property name “TemperatureC” with “TemperatureInCelsius” due to the change in database.
Now let us go ahead and build the application and see how this behaves. We can see the build got failed and it has the below two errors
One error raised in BLL layer and another one in DAL layer. BusinessService.cs belongs to BLL library and why did this break when we had a change in DAL library. This leads us to recognize that we were unable to achieve the encapsulation benefit which we discussed at the beginning of this tutorial. This is happening due to the top-down dependencies of these different layers. In other words, the higher layer is depending on the lower layer.
Now we are going to see how this can be fixed.
To resolve this, we will apply a design principle called “Dependency Inversion Principle”. This principle says that higher level layer should not depends on the lower layer, and both should depend on abstractions.
For argument’s sake, we would have clarified that we are not depending on the actual implementations, but we have added the interface. If we go back to code and see the BAL layer- there we can the DAL library has been referenced. This reference has been added only to consume the IFakeWeatherDataRepository interface. Now, there may be a question, whether the interface is at the right place?
As per the Dependency Inversion Principle, the ownership of interface should belong to client. In our case, the BLL is the client. Let us see how this can be achieved.
To begin with,
- Go ahead and remove the DAL dependency from BLL library.
- Move the IFakeWeatherDataRepository interface to BLL layer.
- Update the namespace and Return type of the method “Get”
4. Make changes in the WeatherService .cs to update the “Get” method
5. Go to DAL Repository class and fix the dependency issue. Let us go ahead and add the Dependency BLL into DAL layer.
6. Go to FakeWeatherDataRepository and make the changes as mentioned below
7. Add the DAL dependency into User Interface Layer
If you look at the BLL layer, there is no dependency on DAL Layer. The dependency has been inversed.
If we execute the application, based on the number of days which we are passing, we will be getting the response.
Assume that – there is another change in database to update the column Name. We are ended to replace the property “TemperatureInCelsius” with “Temperature”. Make the necessary changes and build the application. we will have the below two errors raised only in DAL Layer.
This is because we have inverted the dependencies between BLL and DAL layer.
Now if we look at the architecture, the User Interface is dependent on Business Layer and the Data Access layer is also dependent on Business layer. In both cases, the dependencies are flowing inversed. Whereas the request flow remains the same.
This kind of architecture is referred to as a Clean Architecture. It comes with different names which is
- Clean Architecture.
- Hexagonal Architecture
- Onion Architecture
- Ports and Adapters
The fundamentals behind all the approaches are same.
There are cases where client may not be taking interface ownership. In our case, we haven’t moved the IWeatherService interface into User Interface (is a client). Because API is just one of the clients, we could have Web App, Console app or mobile app which can consume this weather data. In such scenario we need to have a standard Interface like what we have IWeatherService in Business Layer in this example.
Hope this article helps you to understand the Dependency Inversion Principle and the overview of Clean Architecture. Thank you for reading this article and please leave your feedback in the comment box below.