Clean Architecture in .NET – Layered with Purpose
When a .NET code-base starts to feel like a ball of mud—controllers talking to SQL directly, business rules leaking into UI helpers—maintenance slows to a crawl. Clean Architecture is a disciplined way to organise the mess into clear, testable layers that can grow without tripping over themselves.
Real-Life Analogy: The Onion Kitchen
Picture an onion-shaped commercial kitchen. At the very centre, a vault stores the head chef’s secret recipes. Around that are prep stations, then serving counters, and finally delivery vans parked outside. Information flows inwards only by contracts—the vault never knows (or cares) whether the final dish is plated in fine dining or boxed for takeaway.

Entities — Core Rules
Entities are the secret recipes: plain C# classes containing business logic that must never break. No frameworks, no data access, no web references.
// Domain entity
public class Invoice
{
public Guid Id { get; init; }
public Money Total { get; private set; }
public void AddLineItem(Money amount) => Total += amount;
}
Use Cases — Application Layer
Use-case classes (sometimes called Services
or
Interactors
) orchestrate entities to fulfil a specific business scenario.
They depend only on abstractions defined in the core.
// Application service
public class CreateInvoice
{
private readonly IInvoiceRepository _repo;
public CreateInvoice(IInvoiceRepository repo) => _repo = repo;
public async Task<Guid> ExecuteAsync(CreateInvoiceCommand cmd)
{
var invoice = new Invoice();
foreach (var line in cmd.Lines) invoice.AddLineItem(line.Amount);
await _repo.SaveAsync(invoice);
return invoice.Id;
}
}
Interface Adapters
Here we translate the outside world into something the inner layers understand:
- Controllers / gRPC endpoints → map HTTP or gRPC calls to use-case commands.
- Repositories → wrap EF Core, Dapper, or SQL into the
IInvoiceRepository
abstraction. - Mappers / DTOs → keep transport objects from contaminating entities.
Frameworks & Drivers
The outermost ring is where concrete tech lives: ASP.NET Core, EF Core, SQL Server, Message-Queues, UI frameworks. Swap them at will—the inner rings remain untouched.
Typical Folder Layout
src/
├─ Domain/ # Entities & value objects
├─ Application/ # Use cases & abstractions
├─ Infrastructure/ # EF Core, external APIs, File IO
├─ Web/ # ASP.NET Core controllers
└─ Tests/ # Unit & integration tests
Common Pitfalls
- Skipping Abstractions: Letting the Application project reference EF Core types breaks isolation.
- Too Many Layers: Four rings are enough—don’t introduce gratuitous “services” layers.
- God Entities: Keep entities small; aggregate roots coordinate, they don’t monopolise.
Final Thoughts
Clean Architecture is not dogma; it is a guard-rail. By honouring inward-only dependencies, you gain testability, swap-ability, and the confidence to evolve features without fear. In my experience, that trade-off easily pays for itself once a project crosses a few thousand lines.