Using a clean architecture when developing software applications is very common. Maintainability is enhanced when distinct application layers are clearly defined by a clean architecture. Furthermore, to facilitate the transition to new technologies, the Clean Architecture approach strives to maintain the business logic’s independence from any external frameworks or libraries.
We will examine the application of clean architecture concepts in the organization of a .NET project.
The solution is split into four primary levels using the Clean Architecture approach:
- Domain (central reasoning)
- Use cases for applications
- Infrastructure (caching, data access, etc.)
- Display (open-access API)
Application of Clean Architecture
In a.NET project, how can a clean architecture be implemented?
Making a distinct project for each of the four layers depicted in the diagram is the simplest approach.
However, we will modify this strategy a little bit later in the piece.
Let us now discuss each layer separately.
Domain Layer
The domain logic of the application is specified at the domain layer. Entities, value objects, domain services, domain exceptions, domain events, repository interfaces, and other items are contained in this layer. The domain layer’s independence from all other layers is crucial. Only.NET primitive types (int, string, etc.) are necessary for it. There are two typical methods for organizing files into folders inside a domain project.
Sorting files according to their type would be the first option:
Every exception goes in the Exception folder, every entity goes in the Entities folder, and so on. Using entity folders is an alternative strategy. For instance, if the application has user and order-related domain logic, the file structure would resemble this:
Grouping by functionality by type (the first option) may be more effective for a big domain layer because it will be easier to choose a folder in which to place the file. Nonetheless, such challenges can arise with the second method, for instance, if the same value object is a part of multiple distinct entities.
Besides entities, value objects, repository interfaces, and domain exceptions, the Domain Layer should include aggregates, domain services, domain events, specifications, factories, and repositories. These components are essential for encapsulating business logic, maintaining transactional integrity, handling complex operations, and managing interactions with data storage mechanisms. By incorporating these elements, the Domain Layer becomes the focal point for expressing core business concepts and rules, thereby enhancing maintainability and flexibility in the application.
Application Layer
The use cases for an application are contained in the application layer. These use cases might be implemented as application service classes or as handlers for commands and queries. Additionally, infrastructure abstractions (such as those for emailing, caching, and other functions) are typically defined at the application layer.
This is an example of the folder structure:
The application layer only has a relationship to the domain layer in terms of references to other projects. It serves as the orchestrator of the system, coordinating interactions between the domain layer and the infrastructure layer. It ensures that business rules are applied correctly while leveraging external services and resources to fulfill the application’s requirements. By keeping this layer focused on use cases and abstractions, developers can achieve a clean separation of concerns and maintainability throughout the project’s lifecycle.
Infrastructure Layer
Application and domain layer abstractions are implemented via the infrastructure layer.
The following items may be found in the infrastructure layer:
- Migrations of databases and repositories
- The deployment of caching services
- Identity provider implementation
- Putting email providers into practice
The infrastructure layer can eventually grow too bloated if it is implemented as a single project. We can implement the infrastructure layer as a subdirectory containing different projects to get around this issue:
Apart from migrations, caching, and email services, additional components like external services integration, logging, data access implementations, file storage, security and authentication, background jobs, and message brokers can be included. These components facilitate interaction with external systems, logging application activities, accessing data, storing files, ensuring security, handling background tasks, and enabling communication between different parts of the application. This method offers a more effective division of responsibilities. To implement the abstractions defined in the domain and application projects, the infrastructure layer needs to make reference to them.
Presentation Layer
The application’s entry point is the presentation layer. It has a public API that external users and applications can connect with, such as RESTful endpoints. The Presentation layer also includes middlewares and other components required to handle incoming requests in addition to endpoints.
Furthermore, the presentation project serves as the foundation for the full solution’s development. The configuration for dependency injection is located in the composition root. Usually, the Program class’s extension methods are called in succession to do this:
builder.Services
.AddDomainServices()
.AddApplicationServices()
.AddDataAccessServices()
.AddEmailNotificationSErvices()
.AddCachingServices();
The presentation layer must reference the application layer in order to execute application use cases.
Providing a user-friendly interface and controlling interactions with the application are the main priorities of the presentation layer. This comprises parts such as views, controllers, and user interface elements. Along with handling cross-cutting issues like error management and logging, it also takes care of security and authentication. It serves as a link between users and the hidden features of the application.
Wrapping Up
In conclusion, there are several advantages to using a Clean Architecture approach in a .NET project, such as improved scalability, maintainability, and adaptation to new technologies. Developers can retain the independence of business logic from external frameworks or libraries and provide a clear separation of concerns by organizing the project into various layers, namely domain, application, infrastructure, and presentation.
From specifying the fundamental domain logic to controlling user interactions via the presentation layer, each layer has a distinct function. This project organization not only encourages testability and code reuse, but it also makes it easier for development teams to collaborate and makes upgrades and revisions easier in the future.