Clean Architecture in .NET: Building Evolution-Ready Systems
1. Introduction
As applications grow, they often become “Big Balls of Mud” where changing the database schema requires updating the UI, and business logic is scattered across controllers, stored procedures, and services.
Clean Architecture (popularized by Robert C. Martin) is a design pattern that organizes code into concentric layers. The core rule is the Dependency Rule: Dependencies can only point inwards toward the business logic.
2. The Layers of Clean Architecture
1. Domain (The Core)
The innermost circle. It contains entities, value objects, and domain exceptions.
- Rule: It has zero dependencies on other layers or external libraries (like EF Core).
2. Application
Contains “Use Cases” (e.g., CreateOrderCommand). It defines interfaces for external services (like IEmailService or IOrderRepository).
- Rule: It depends only on the Domain layer.
3. Infrastructure
Contains the implementation of the interfaces defined in the Application layer. This is where EF Core, File System access, or external API clients live.
- Rule: It depends on Application and Domain.
4. Web / Presentation
The entry point (Web API, Blazor, or MVC). It handles HTTP requests and translates them into Application commands.
- Rule: It depends on Application and Infrastructure (only for DI registration).
3. Benefits of this Approach
- Independent of Database: You can swap SQL Server for MongoDB without touching your business logic.
- Independent of Framework: Your core logic doesn’t care if you’re using ASP.NET Core or a Console App.
- Highly Testable: Since the Domain and Application layers are decoupled from external concerns, you can unit test them without mocks for databases or web servers.
4. Project Structure Example
Solution/
├── Domain/
│ └── Entities/
├── Application/
│ ├── Interfaces/
│ └── UseCases/
├── Infrastructure/
│ ├── Data/ (EF Core)
│ └── Services/
└── WebAPI/
├── Controllers/
└── Program.cs
5. Implementation Tip: The Mediator Pattern
Clean Architecture often uses the MediatR library to decouple the Web API from the Application layer. Instead of injecting a service with 20 methods into a controller, you “send” a command:
[HttpPost]
public async Task<IActionResult> Create(CreateOrderCommand command)
{
var id = await _mediator.Send(command);
return Ok(id);
}
6. Conclusion
Clean Architecture is about deferring decisions. By keeping your core logic “clean,” you don’t have to commit to a specific database or UI framework on day one. It requires more boilerplate initially, but it pays off massively as your project evolves and complexity increases.
Start by moving your Entities into a separate Domain project today!
Leave a comment