← All tutorials

Clean Architecture in Plain Language

Core application logic stays independent of the database, the framework, and surrounding tools.

Clean Architecture diagram: a request enters through the API, passes Infrastructure and Application, and reaches Domain at the center. Side cards list what each layer contains.
Domain stays at the center. The outer layers handle the request, technical tools, and scenarios. Labels on this diagram are in Persian.

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:

Logic

Domain

Business rules and core concepts. It knows nothing about the database or the framework.

Scenarios

Application

What the system can do: create, update, search, approve, log in.

Technology

Infrastructure

Technical details: EF Core, Redis, RabbitMQ, email, external APIs.

In and out

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
Domain = the core logic and rules of the system

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:

  • CreateUserHandler
  • UpdateProfileHandler
  • LoginHandler
  • IUserRepository
  • IEmailService

Application answers:

What should be done?

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.

Application = the scenarios and operations of the system

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:

Store this information.
↓

Infrastructure says:

I will do that with EF Core and SQL Server.
Infrastructure = technical details and tools

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:

Take the request
↓
Call Application
↓
Return the response
API = the input and output of the system

How the layers relate

One of the most important rules of Clean Architecture is that dependencies point inward.

In simple form:

API
↓
Application
↓
Domain

Infrastructure usually implements the interfaces that Application defined:

Infrastructure
↓
Application
↓
Domain

So the dependencies we usually want are:

API            ----> Application

Infrastructure --> Application

Application    ----> Domain
These dependencies should not happen:
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>();
Whenever Application asks for IUserRepository, give it the UserRepository that lives in Infrastructure.
Application uses what Infrastructure provides, without depending on Infrastructure directly.

The path of a request

A request enters the system like this:

Client
↓
API
↓
Application
↓
Domain

When a database or an external service is needed:

Client
↓
API
↓
Application
↓
Interface
↓
Infrastructure
↓
Database / Redis / Message Broker / External API
The runtime path and the project dependency path are not the same thing. At runtime Application may use a repository, but it only knows that repository's interface.

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:

TodayTomorrow
SQL ServerPostgreSQL
RabbitMQKafka
RedisAnother cache
MinIOAnother 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
ProjectResponsibility
DomainCore rules and concepts
ApplicationWhat the system does
InfrastructureHow the technical work is done
APIInput 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.

LayerRemember it as
DomainLogic
ApplicationScenarios
InfrastructureTechnology
APIInput and output

To see these layers inside a real Order service, continue with DDD and CQRS with the Order project.