Skip to main content
SEMastery
Architecturebeginner

Clean Architecture in .NET: The Benefits of Structured Software Design

A beginner-friendly guide to Clean Architecture in .NET. Learn the four layers, the dependency rule, and why structured software design keeps your code easy to change.

11 min readUpdated March 9, 2026

A kitchen with a clear order of work

Think about a busy restaurant kitchen. Right at the heart of it is the head chef, who knows the recipes and decides what a good dish must taste like. The head chef does not care which brand of stove is used or where the vegetables were bought. Those things can change any day.

Around the head chef are the cooks, who follow the recipes to actually prepare each order. Further out are the suppliers and delivery vans that bring ingredients from the outside world. And at the very front is the waiter, who takes orders from customers and brings food back.

Notice the direction of importance. The waiter depends on the cooks. The cooks depend on the head chef's recipes. But the head chef does not depend on the waiter or the van. You could change the waiter, swap the supplier, or buy a new stove, and the recipes stay exactly the same.

Clean Architecture organises software the same way. Your most precious thing, the business rules, sits safe in the centre. It knows nothing about databases, web frameworks, or screens. The outer parts depend inward toward it, never the reverse. This article explains the layers, the one golden rule, and the real benefits you get from designing software this way.

What "structured design" really means

Before we look at layers, let us be clear about the problem we are solving.

Imagine a project where everything is in one big folder. Database code, web code, and business rules are all mixed together. At first it feels fast. But after a year, every small change touches ten files. You are scared to change anything because you do not know what will break. New team members take weeks to understand it. People call this a big ball of mud.

Structured design fights this. It draws clear lines between parts of your code and decides which part is allowed to know about which other part. When the lines are clear, change becomes safe and calm.

Figure 1: Without structure, every part knows about every other part. Tangled code is hard to change safely.

In the tangled picture above, you cannot pull one box out without dragging the others with it. Clean Architecture turns this mess into neat rings.

The four layers

Clean Architecture organises your code into four layers, arranged from the inside out. In .NET, each layer is usually its own project.

Figure 2: The four layers as rings. Outer layers depend on inner ones. The arrows always point toward the centre.

Here is what each ring does:

  • Domain is the centre. It holds entities, value objects, and the rules that are always true (like "an order total can never be negative"). It references nothing else. No database code, no web code.
  • Application holds the use cases. These are the real tasks your app can do, such as "place an order" or "register a user". It uses the Domain and defines the interfaces it needs, like IOrderRepository.
  • Infrastructure holds the outside-facing code. This is where Entity Framework Core, the database, email senders, and HTTP clients live. It provides the real implementations of the interfaces the Application asked for.
  • Presentation is the entry point, usually a Web API with controllers or minimal API endpoints. It takes a request and calls the Application.

Let us see how these four layers cooperate to handle one request.

How a Request Travels Through the Layers

Presentation
Application
Domain
Infrastructure

Steps

1

Presentation

API receives the HTTP request

2

Application

Use case orchestrates the work

3

Domain

Business rules are checked

4

Infrastructure

Database saves the result

A web request flows inward to the core, then the result flows back out. Each layer does one job.

The one golden rule: dependencies point inward

There is really only one rule worth memorising. Source code dependencies must point inward only, toward the centre.

This means:

  • The Domain depends on nothing.
  • The Application depends only on the Domain.
  • Infrastructure and Presentation depend on the inner layers, never the other way round.

A class in the Domain can never reference a class in Infrastructure. The recipe does not depend on the delivery van.

The wonderful part in .NET is that the compiler enforces this for you. If the Domain project has no project reference to the Infrastructure project, you simply cannot write using MyApp.Infrastructure; inside the Domain. The code will not compile. The build itself protects your architecture, so it does not rely on everyone remembering the rule.

Here is how the project references line up:

ProjectIs allowed to referenceMust never reference
DomainnothingApplication, Infrastructure, Presentation
ApplicationDomainInfrastructure, Presentation
InfrastructureApplication, DomainPresentation
PresentationApplication, Domain(wires up Infrastructure at startup)

A small example in code

Let us make this real. Say we are building an orders system. First, the Domain holds the entity and its rules. Notice there is no database code here at all.

namespace MyApp.Domain;
 
public sealed class Order
{
    public Guid Id { get; private set; }
    public decimal Total { get; private set; }
    public bool IsPaid { get; private set; }
 
    public Order(decimal total)
    {
        if (total < 0)
            throw new ArgumentException("Total cannot be negative.");
 
        Id = Guid.NewGuid();
        Total = total;
    }
 
    public void MarkPaid() => IsPaid = true;
}

Next, the Application layer defines an interface for what it needs and a use case that uses it. The Application does not know how the order will be saved. It only knows that something will save it.

namespace MyApp.Application;
 
// The Application declares the contract it needs.
public interface IOrderRepository
{
    Task AddAsync(Order order, CancellationToken ct);
}
 
// A use case: place an order.
public sealed class PlaceOrderHandler(IOrderRepository repository)
{
    public async Task<Guid> HandleAsync(decimal total, CancellationToken ct)
    {
        var order = new Order(total);  // Domain rules run here.
        await repository.AddAsync(order, ct);
        return order.Id;
    }
}

Finally, the Infrastructure layer provides the real implementation using EF Core. This is the only place that knows about the database.

namespace MyApp.Infrastructure;
 
public sealed class OrderRepository(AppDbContext db) : IOrderRepository
{
    public async Task AddAsync(Order order, CancellationToken ct)
    {
        db.Orders.Add(order);
        await db.SaveChangesAsync(ct);
    }
}

See the trick? The Application defined IOrderRepository, and the Infrastructure implemented it. The arrow of dependency still points inward, even though the data flows outward at runtime. This idea is called Dependency Inversion, and it is the engine that makes Clean Architecture work.

Why the arrow can point inward while data flows out

This part confuses many beginners, so let us slow down. At runtime, the order data clearly flows from the use case out to the database. So why do we say the Infrastructure depends on the Application and not the reverse?

Because dependency is about source code, not about data flow. The OrderRepository class needs to know about the IOrderRepository interface to implement it. So the source code of Infrastructure points at the Application. The Application never mentions OrderRepository by name. At startup, the Presentation layer connects the two with one line of dependency injection.

Figure 3: The interface lives in the inner layer; the implementation lives outside and points inward to fulfil it.

This is the heart of structured design. The core says what it needs, and the outside provides it. The core stays clean.

The benefits you actually get

Clean Architecture asks for some extra effort up front. So what do you get back? Here are the real, practical benefits.

BenefitWhat it means for you
Easy to testThe Domain and Application have no database, so you can test them in memory with no setup.
Easy to change toolsSwap SQL Server for PostgreSQL, or email for SMS, by changing only Infrastructure.
Slower to rotClear walls stop the code from turning into a big ball of mud over the years.
Easy onboardingA new teammate can read the Domain and understand the business without web noise.
Safe refactoringThe compiler blocks illegal references, so bad changes fail the build, not production.

The biggest win is testability. Because the core has no database or web framework, a unit test is tiny and fast.

[Fact]
public void Order_With_Negative_Total_Is_Rejected()
{
    // No database, no web server, no mocks for the framework.
    var act = () => new Order(-50m);
 
    Assert.Throws<ArgumentException>(act);
}

That test runs in a millisecond and never touches a database. When most of your important logic can be tested this way, your whole team moves faster and sleeps better.

When to use it, and when not to

Clean Architecture is a tool, not a law. Use it where it pays off.

It shines on medium to large applications that many people will work on for years. The kind of project where business rules are rich and the cost of a tangled mess is high.

It can be too heavy for a tiny script, a quick prototype, or a weekend app. For those, the four projects and all the interfaces can slow you down with little reward. You can still keep the spirit (separate your business rules from your tools) while using simple folders inside one project.

Deciding Whether to Use Clean Architecture

Project size?
Small or throwaway
Long-lived or large
Use folders
Use full layers

Steps

1

Project size?

Ask how long it will live

2

Small or throwaway

Keep it simple

3

Long-lived or large

Invest in structure

4

Choose

Folders vs separate projects

Match the effort to the project. Small throwaway apps rarely need the full structure.

A useful sign you have outgrown a flat design: you feel scared to change code because you cannot predict what breaks. That fear is the signal to add structure.

Many Clean Architecture tutorials reach for a library called MediatR to send commands and queries between layers, and MassTransit for messaging. Both are excellent, but be aware that as of recent versions they have moved to a commercial license for many use cases. You do not need them to do Clean Architecture. The plain handler class shown earlier works perfectly well, and you can always add a library later if you truly need it. Structure comes from how you arrange your code, not from any single package.

On the platform side, .NET 10 is the current long-term support release, C# 14 has shipped, and C# 15 (with features like union types) is in early preview for .NET 11. None of this changes the Clean Architecture ideas. The rings and the dependency rule stay the same across versions, which is part of why this style has lasted so long.

Putting it all together

A typical .NET solution following this style looks like four projects, with references pointing inward:

// MyApp.Domain          -> (no references)
// MyApp.Application      -> Domain
// MyApp.Infrastructure   -> Application, Domain
// MyApp.WebApi           -> Application, Infrastructure (wired at startup)
 
// In Program.cs of the WebApi, you connect the dots:
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<PlaceOrderHandler>();

That single registration line is where the outer world is plugged into the clean core. The use case asked for an IOrderRepository, and here we tell .NET to hand it an OrderRepository from Infrastructure. Everything else stays unaware of the wiring.

References and further reading

Quick recap

  • Clean Architecture arranges code into four rings: Domain, Application, Infrastructure, and Presentation.
  • The one golden rule: dependencies point inward, toward the core. Inner layers never know about outer ones.
  • The Domain holds business rules and references nothing. It is the safe centre, like a head chef's recipes.
  • The Application defines the interfaces it needs; Infrastructure provides the real implementations. This is Dependency Inversion.
  • In .NET, project references let the compiler enforce the rule for you, so the build protects your design.
  • The big benefits are easy testing, easy tool swaps, slower rot, and safer change over the years.
  • Use it for long-lived, larger apps. For tiny projects, keep the spirit but use folders instead of four projects.
  • You do not need MediatR or MassTransit (now commercially licensed) to do it. A plain handler class is enough to start.

Related Posts