In this article, we're going to talk about Clean Architecture and its many benefits.
- Knowledge of C# and .NET
- Basic understanding of the Dependency Inversion Principle
Let's get right to it.
The smallest possible number of projects for an application architecture in .NET is one. Here, the entirety of the application, the business, presentation, and data access logic is contained in a single project, compiled to a single assembly, and deployed as a single unit. This type of all-in-one architecture might serve small applications well, but as applications get bigger this becomes unsustainable.
Folders relating to each aspect of the project can keep growing and get scattered across with no apparent cohesiveness between related folders, for example, user interface logic can reside in different folders (models, views, controllers), same with business and data access logic. Along with this, there is no clear indication of which classes in which folders should depend on which others. This lack of organization at the project level frequently leads to what is called spaghetti code.
To mitigate the issues mentioned above, applications often evolve into multi-project solutions, where each project represents a particular layer of the solution. Layers represent logical separation within the application. They are a way to manage complexity by breaking an application into different parts according to its responsibilities or concerns.
The most common organization of application logic into layers is shown below.
This is the entry point of the application. It is the part of the application that the client/user interracts with.
This part is where the main implementation of an application is carried out. This part is responsible for solving whatever problem the application has set out to solve.
This is the part of the application that provides easy and simplified access to data stored in persistent storage, eg data stored in an SQL database.
Using this architecture, user can communicate with the UIL, which in turn communicates with the BLL, which communicates with the DAL. The UIL should not speak to the DAL directly, or implement persistence by any other means. This way, each layer has its own well-defined responsibility.
However, this traditional layering approach also has a disadvantage, because dependencies seem to flow downward; the UIL depends on the BLL, and the BLL on the DAL. The business logic layer, which usually holds the most important logic of the application, depending on the data access details poses a problem, for example, tesing the BLL can be difficult because we now require a test database. Making changes to your data access details can also result in changes to the BLL.
Clean architecture puts the business logic at the center of the application. This is achieved mainly by following the Dependency Inversion Principle (DIP). If you're unfamiliar with this topic, you can check out a brief overview here.
Instead of having the application core (which contains the business logic) depend on data access and other infrastructure details, this dependency is inverted; infrastructure and implementation details depend on the application core. This is achieved by defining abstractions (interfaces) in the Application Core, which will be implemented by types defined in the Infrastructure layer. Below is a diagram depicting a common visualization of Clean Architecture.
As you can see from the figure above, dependencies in Clean Architecture flow inward, and the Application Core has no dependencies on any other layers. The entities and interfaces are at the very center. Outside this, but still in the Application Core, we have the domain services, which typically implement the interfaces defined in the inner circle.
The Infrastructure and UI layers both depend on the Application Core, but not necessarily on each other. Since the Application Core doesn’t depend on the Infrastructure, it’s very easy to write automated unit tests for this layer. Also, since the UI layer doesn't have any direct dependency on the Infrastrucure layer, it is also easy to swap out implementations, either for testing purposes or due to changing technical requirements (for example, switching the UI layer from a MVC project to a Web API).
Layers in applications that follow Clean Architecture have specific, well-defined responsibilities. As such, certain types belong in each project, and developers can easily find where certain functionality is implemented.
The Application Core contains the main business logic of the application; the entities, interfaces, and the domain services, along with few other types central to the business logic.
- Data Transfer Objects
- Custom Exceptions and Handlers, etc
The Infrastructure project typically includes data access implementations. In a project developed using Entity Framework, the Infrastructure layer would also contain the DbContext implementation, and any migration objects that have been created. The Infrastructure layer is also where you would find infrastructure-specific services (eg mailing services). These services would implement interfaces defined in the Application Core
- EF Core DbContext
- Infrastructure-specific services
This layer is the entry point of the application. It is also in this layer that the application is configured and implementation types are provided for interfaces defined in the Application Core.
- Custom middlewares/filters
This has been a brief overview of Clean Architecture and its benefits. Microsoft has provided a sample ASP.NET Core 6.0 reference application implementing Clean Architecture. You can view it here to get a more practical understanding of this topic.
Microsoft Documentation - https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures
EShopOnWeb - https://github.com/dotnet-architecture/eShopOnWeb