Layers (Domain/Application/Infrastructure/Presentation), dependency rules, folder structure, real project setup
Clean Architecture is one of the most talked-about patterns in enterprise .NET development.
But the theory is often dense and the examples either too simple or too complex to apply directly.
This guide breaks it down with a practical folder structure, real code examples, and clear rules for each layer.
The Core Idea
Clean Architecture organises your code into layers with one golden rule:
Dependencies only point inward.
The inner layers know nothing about the outer layers.
[Presentation] → [Application] → [Domain]
[Infrastructure] → [Application] → [Domain]
Your business logic (Domain) has zero knowledge of databases, HTTP, or UI frameworks.
The Four Layers
1. Domain — the core
Contains your business entities, value objects, domain events, and interfaces.
No dependencies on any other layer. No NuGet packages for databases or HTTP.
// Domain/Entities/Order.cs
public class Order
{
public Guid Id { get; private set; }
public string CustomerId { get; private set; }
public List<OrderLine> Lines { get; private set; } = new();
public OrderStatus Status { get; private set; }
public void AddLine(Product product, int quantity)
{
// Pure business logic — no EF, no HTTP
if (quantity <= 0) throw new DomainException("Quantity must be positive");
Lines.Add(new OrderLine(product, quantity));
}
}
2. Application — use cases
Contains your use cases (CQRS commands and queries), interfaces for infrastructure, and application-level business logic.
Depends on Domain only.
// Application/Orders/Commands/CreateOrderCommand.cs
public record CreateOrderCommand(string CustomerId, List<OrderLineDto> Lines)
: IRequest<Guid>;
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, Guid>
{
private readonly IOrderRepository _orders;
private readonly IEmailService _email;
public CreateOrderCommandHandler(IOrderRepository orders, IEmailService email)
{
_orders = orders;
_email = email;
}
public async Task<Guid> Handle(CreateOrderCommand command, CancellationToken ct)
{
var order = Order.Create(command.CustomerId, command.Lines);
await _orders.AddAsync(order, ct);
await _email.SendOrderConfirmationAsync(order);
return order.Id;
}
}
3. Infrastructure — the outside world
Implements the interfaces defined in Application. Contains EF Core, HTTP clients, email services, file storage.
// Infrastructure/Persistence/OrderRepository.cs
public class OrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
public OrderRepository(AppDbContext context) => _context = context;
public async Task AddAsync(Order order, CancellationToken ct)
{
await _context.Orders.AddAsync(order, ct);
await _context.SaveChangesAsync(ct);
}
}
4. Presentation — the entry point
Controllers, minimal API endpoints, SignalR hubs. Receives HTTP requests and delegates to the Application layer.
// Presentation/Controllers/OrdersController.cs
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
private readonly ISender _mediator;
public OrdersController(ISender mediator) => _mediator = mediator;
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateOrderCommand command)
{
var id = await _mediator.Send(command);
return CreatedAtAction(nameof(GetById), new { id }, null);
}
}
Folder Structure
src/
├── MyApp.Domain/
│ ├── Entities/
│ ├── ValueObjects/
│ ├── Enums/
│ └── Exceptions/
├── MyApp.Application/
│ ├── Orders/
│ │ ├── Commands/
│ │ └── Queries/
│ ├── Interfaces/
│ └── Common/
├── MyApp.Infrastructure/
│ ├── Persistence/
│ ├── ExternalApis/
│ └── Services/
└── MyApp.Presentation/
├── Controllers/
└── Program.cs
The Dependency Rule in Practice
- Domain has no project references
- Application references Domain only
- Infrastructure references Application (to implement interfaces) and Domain
- Presentation references Application — never Infrastructure directly
When to Use Clean Architecture
Use it when:
- The system will grow over time
- Multiple developers work on the codebase
- Business logic is complex and must be tested independently
- You expect to swap out infrastructure (database, external APIs)
Do not use it for small CRUD applications or prototypes — the overhead is not justified.
Interview-Ready Summary
- Dependencies point inward — Domain knows nothing about Infrastructure
- Domain = entities and pure business rules, no external dependencies
- Application = use cases (CQRS commands/queries), defines interfaces
- Infrastructure = implements interfaces, owns EF Core and external services
- Presentation = controllers that delegate to Application via MediatR
- Clean Architecture makes business logic testable without a database or HTTP stack
A strong interview answer:
"Clean Architecture separates the system into layers where dependencies only point inward. The Domain layer contains pure business logic with no external dependencies. Application defines use cases and interfaces. Infrastructure implements them. Presentation handles HTTP. This makes the business logic fully testable in isolation, and allows you to swap infrastructure — like switching databases — without touching your business rules."
Top comments (0)