Clean Architecture šÆš§¹
Clean Architecture emphasizes the division of an applicationās concerns, promoting scalability, testability, and maintainability. The architecture is organized into distinct layers: Presentation Layer, Application Layer, Domain Layer, and Infrastructure Layer. The Presentation Layer manages user interface and interactions, the Application Layer handles business logic, the Domain Layer contains essential entities and business rules, and the Infrastructure Layer deals with technical details like databases, APIs, and external services.
While Clean Architecture offers advantages in code organization and increased testability, introducing new features to an application built with Clean Architecture can be challenging. Even minor changes may require modifications across the Domain, Application, UI, and Infrastructure layers.
The Dependency Rule in Clean Architecture dictates that dependencies should point towards the inner layers, ensuring the core business logic remains independent of external frameworks, interfaces, and agents. The innermost layer comprises entities representing the core business logic and rules. External interfaces, like third-party payment integrations, typically reside in the External Interfaces or Frameworks layer as external agents interacting with the core application. Clean Architecture underscores the separation of core business logic from external elements, safeguarding it against external changes. While beneficial for scalability and maintainability, Clean Architecture may introduce unnecessary complexity for smaller, simpler applications.
Let us look at how we can organize the code into layers in .net core application.
Core Layer or Domain Layer project
Core or Domian project is the center of the Clean Architecture design, and all other project dependencies should point toward it. Some of the examples in this project are:
- Domain Entities
- Core Exceptions
- External library or tools or functionality interfaces
- Repository interfaces
- Specification design pattern classes
The Specification design pattern encapsulates query details in separate classes as part of the domain model. This pattern can be used to solve the following problems:
1) Duplicate query logic throughout the application
2) Growing Repository implementations that keep adding custom query methods
Below is the sample project structure.
Application Layer Project
In this layer, we develop domain logic with its corresponding implementation. Interfaces within this layer guide both business requirements and implementations. To enact our business logics and perform use case operations, we utilize the Application Layer. Some of the folders or implementations in this project are:
- Application Models
- Application Specific Exceptions
- Managers/Services interfaces and implementation
- Mappers
- Validators
Note: Only project that the application layer references will be core project. Below is the sample of what the project will look like.
You might be wondering how the managers will be able to implement logic with out knowing how the repositories or external integrations are implemented. This is where the dependency injection will decouple the layers. Below is an example of manager.
public class CartManager : ICartManager
{
private readonly ICartRepository _cartRepository;
private readonly ICoreLogger _logger;
private readonly IEmailSender _emailSender;
public CartManager(ICartRepository cartRepository,
ICoreLogger logger,
IEmailSender emailSender)
{
_cartRepository = cartRepository;
_logger = logger;
_emailSender = emailSender;
}
public async Task AddItem(string userName, int productId)
{
//Get the cart using _cartRepository
//Add product to cart
//Save cart using _cartRepository
//Send email using _emailSender
}
}
Infrastructure Layer Project
In this project, we implement core interfaces using Entity Framework Core and other dependencies. Most of your applicationās reliance on external resources should be realized through classes defined in the infrastructure project. These classes are required to adhere to the interfaces outlined in Core. In instances where you have a substantial project with numerous dependencies, it might be practical to have multiple Infrastructure projects (e.g., Infrastructure.Data). However, for most projects, a single Infrastructure project with organized folders proves effective. These dependencies can include, for example, email providers, file access, web API clients, etc. At this point, the repository is solely dependent on sample data access and basic domain actions, ensuring that there are no direct links to your Core or UI projects. Below are some of the folders in the project:
- Migrations and DbContext
- Repositories
- Logging
- External Service Calls
Below is example of the what the project will look like, if you note all the dependencies are abstracted to this project(s) in that way we can swap any of those dependencies without touching other layers.
Presentation Layer Project
Implementation of UI logic is carried out in this stage, guided by interfaces that dictate both business requirements and implementations. The ASP.NET Core web project serves as the primary entry point for the application. It adheres to a classic console application structure, featuring a public static void Main method in Program.cs. Currently, it utilizes the default ASP.NET Core project template, which is based on Razor Pages templates. This setup includes the appsettings.json file along with environment variables to store configuration parameters, all configured within Startup.cs. The final layer, the UI layer, serves as the consumer of all other layers in this architectural structure. This layer is where we will define all the service configurations for dependency injection.
Below is the example of what the startup or program file can look like:
In some solutions you will see there will be test layer for consolidating all you unit, integration, and infrastructure tests. With this kind of layers, we will make our code maintainable, flexible and testable, but still, everything in our code is in a single solution, this is where scalability plays a major role. Our layers are clean, but can we scale some of them independently? May be but this where how you can scale whether it is scaling up or scaling out plays role.
Scaling
Scalability refers to an applicationās ability to handle multiple requests simultaneously or concurrently. When a system can no longer manage additional concurrent requests, it has reached its scalability limit. There are two ways to scale a system: horizontal scaling and vertical scaling. In both approaches, resources are added to the infrastructure, but the key distinction lies in their methods.
Horizontal scaling involves adding more machines or instances, while vertical scaling entails enhancing the computing power of an existing machine, such as increasing the size of instances with respect to RAM, CPU, etc. Horizontal scaling is also known as scaling out, while vertical scaling is referred to as scaling up. Scaling out may necessitate changes to your applicationās design, whereas scaling up allows the same application to run on more powerful machines without modifications.
Vertical Scaling
Vertical scaling essentially strengthens individual nodes. Enhance server capabilities by augmenting hardware, adding more resources to a single node. Optimize hardware to efficiently manage increased request loads. Vertical scaling entails bolstering existing infrastructure by injecting additional computing power. Existing code remains unaffected. Augment CPU, RAM, and DISK capacities to accommodate growing workloads. Scaling up enhances the capacity of a single machine. However, this approach has limitations known as Scalability Limits. These limits arise because hardware has inherent maximum capacity constraints. To handle millions of requests, horizontal scaling or scaling out becomes necessary.
Vertical scaling is typically used when a single node has reached its maximum capacity but still has available resources such as unused CPU or memory, it can be a good option to upgrade that node rather than adding a new one.
Horizontal Scaling
Horizontal scaling involves distributing the workload across different servers, simply adding more instances of machines without altering existing specifications. It entails sharing processing power and implementing load balancing across multiple machines, contributing to scalability and reliability. This approach is particularly favored in distributed architectures. When opting for horizontal scaling by splitting into multiple servers, considerations arise, especially if there is a state involved, such as with database servers. Managing considerations like the CAP theorem becomes necessary in such cases. Typically, horizontal scaling is employed when a system has reached its maximum capacity vertically, or when increased redundancy, availability, or scalability is required.
One thing you have to remember, just scaling application servers is not enough then database will become constraint. This is where we have to determine Sql vs NoSql.
Pain Points of Clean Architecture:
- Context switching ā While working on a feature that required engagement with various aspects like data access, domain, or application logic, we found ourselves frequently switching contexts between different software projects. The existing folder structure prompted the adoption of Domain-Driven Design (DDD) bounded contexts approaches to enhance the organization and management of our code.
- Vertical Slices ā When introducing a new feature to an application, our development involves nearly all layers of the application code. This encompasses alterations to the user interface, incorporation of new Use Case classes into the Application Layer, addition of fields to models, modifications to Data Access Codes, and more. Consequently, we find ourselves closely tied to vertically slicing our development work when implementing features.
šThanks for taking the time to read the article. If you found it helpful and would like to show support, please consider:
- šššClap for the story and bookmark for future reference
- Follow me on Chaitanya (Chey) Penmetsa for more content
- Stay connected on LinkedIn.
Wishing you a happy learning journey š, and I look forward to sharing new articles with you soon.