Clean Architecture in Plain Language
Core application logic stays independent of the database, the framework, and surrounding tools.
What is Clean Architecture?
Clean Architecture is a way to build software whose main goal is to keep the core logic separate from technologies and technical details.
The application is designed so that a future change of database, framework, or supporting tools does not force a rewrite of the whole project.
The idea comes from Robert C. Martin (Uncle Bob). The classic picture is a set of concentric circles. In practice, especially in .NET, the same idea is usually implemented as four projects:
The four parts
In the simplest form, Clean Architecture can be split into four parts:
Domain
Business rules and core concepts. It knows nothing about the database or the framework.
Application
What the system can do: create, update, search, approve, log in.
Infrastructure
Technical details: EF Core, Redis, RabbitMQ, email, external APIs.
API / Presentation
The entry and exit point: controllers, endpoints, requests, and responses.
1. Domain
Domain is the heart of the application.
Anything that belongs directly to the rules and core concepts of the business lives here. For example:
- Entities
- Value Objects
- Enums
- Domain Events
- Business Rules
- Domain Exceptions
The important point is that Domain must not know which database, framework, or technology the application uses.
Domain must not depend on things like:
EF Core
SQL Server
Redis
RabbitMQ
ASP.NET Core
HTTP
2. Application
The Application layer defines what the system can do. For example:
Create
Update
Delete
Search
Approve
Reject
Login
Register
Use cases, handlers, DTOs, and interfaces usually live in this layer. For example:
CreateUserHandlerUpdateProfileHandlerLoginHandlerIUserRepositoryIEmailService
Application answers:
It usually does not go into technical details. Application might say:
await _repository.AddAsync(entity);
It does not know whether that data will be stored in SQL Server or PostgreSQL.
3. Infrastructure
Infrastructure is where the technical details live. For example:
- EF Core and
DbContext - SQL Server and PostgreSQL
- Redis
- RabbitMQ and Kafka
- MinIO
- Email and SMS services
- External APIs
Application may only declare an interface:
public interface IUserRepository
{
Task AddAsync(User user);
}
The real implementation lives in Infrastructure:
public class UserRepository : IUserRepository
{
private readonly ApplicationDbContext _context;
public async Task AddAsync(User user)
{
await _context.Users.AddAsync(user);
await _context.SaveChangesAsync();
}
}
Application says:
Infrastructure says:
4. API / Presentation
This layer is the entry and exit point of the application.
In an ASP.NET Core project it usually includes:
- Controllers
- Endpoints
- Middlewares
- Request and response
- Authentication
A controller receives the request:
[HttpPost]
public async Task<IActionResult> Create(CreateUserCommand command)
{
var result = await _handler.Handle(command);
return Ok(result);
}
The controller should not implement the core logic itself. Its job is closer to this:
How the layers relate
One of the most important rules of Clean Architecture is that dependencies point inward.
In simple form:
Infrastructure usually implements the interfaces that Application defined:
So the dependencies we usually want are:
API ----> Application
Infrastructure --> Application
Application ----> Domain
Domain ----> Infrastructure
Domain ----> API
Application ----> Infrastructure
Application ----> API
How does Application use Infrastructure?
This part can be confusing at first.
Application may contain something like this:
public interface IUserRepository
{
Task AddAsync(User user);
}
Application only knows that interface.
Infrastructure then implements it:
public class UserRepository : IUserRepository
{
// EF Core
}
Dependency injection wires them together:
builder.Services.AddScoped<IUserRepository, UserRepository>();
IUserRepository, give it the UserRepository that lives in Infrastructure.
The path of a request
A request enters the system like this:
When a database or an external service is needed:
Why this architecture helps
The biggest benefit of Clean Architecture is separating business logic from technology.
If the system uses SQL Server today and moves to PostgreSQL tomorrow, the core logic should not have to change much.
The same idea applies to other tools:
| Today | Tomorrow |
|---|---|
| SQL Server | PostgreSQL |
| RabbitMQ | Kafka |
| Redis | Another cache |
| MinIO | Another storage |
Important benefits:
- Less coupling between parts of the project
- Easier testing
- Easier technology changes
- A more readable project
- Simpler maintenance of large projects
- Business logic separated from technical details
- A better fit for the Dependency Inversion principle
A typical .NET layout
A simple Clean Architecture project can look like this:
MyProject.Domain
MyProject.Application
MyProject.Infrastructure
MyProject.Api
| Project | Responsibility |
|---|---|
| Domain | Core rules and concepts |
| Application | What the system does |
| Infrastructure | How the technical work is done |
| API | Input and output of the system |
Summary
Clean Architecture in one sentence:
The core logic must not depend on the database, the framework, or technical tools. Those tools should depend on the application logic.
| Layer | Remember it as |
|---|---|
| Domain | Logic |
| Application | Scenarios |
| Infrastructure | Technology |
| API | Input and output |
To see these layers inside a real Order service, continue with DDD and CQRS with the Order project.