Layered architecture groups code by responsibility and defines which groups may depend on which. Traditional layering points dependencies downward, usually from presentation through business logic to data access. Onion and Clean variants reverse selected edges so infrastructure depends on inner policy. The useful part is the boundary: business rules stop drifting into controllers and persistence code.

Layer Responsibilities

A common four-layer split looks like this:

LayerResponsibilityExamples
PresentationHandle input, render outputASP.NET Core controllers, Razor views, Blazor components
ApplicationOrchestrate use cases, coordinate domain + infrastructureService classes, CQRS handlers, DTOs
DomainBusiness rules, entities, invariantsEntities, value objects, domain events, domain services
InfrastructureTechnical details: persistence, messaging, external APIsEF Core DbContext, HTTP clients, email senders

Dependency Rule

In an inward-facing layered design, the Domain knows nothing about databases, frameworks, or UI. Infrastructure implements interfaces owned by inner layers. Traditional N Layer uses a different rule, shown in the next comparison: dependencies move down the stack, often including a business-layer dependency on data access.

graph TD
    subgraph OUTER[Infrastructure and Presentation - outermost]
        UI[Controllers and Views]
        DB[EF Core and SQL Server]
        EXT[HTTP clients and Email and File system]
    end

    subgraph MIDDLE[Application Layer]
        UC[Use Cases and Services]
        IPORT[[IOrderRepository]]
        OPORT[[IEmailSender]]
    end

    subgraph CORE[Domain Layer - innermost and zero dependencies]
        ENT[Entities and Value Objects]
        RULES[Business Rules]
        DEVT[Domain Events]
    end

    UI --> UC
    UC --> ENT
    UC --> RULES

    DB -.->|implements| IPORT
    EXT -.->|implements| OPORT
    IPORT --> ENT
    UC --> IPORT
    UC --> OPORT

Traditional Vs Onion/Clean

graph LR
    subgraph TRADITIONAL[Traditional Layered - dependencies go down]
        direction TB
        T_UI[UI] --> T_BL[Business Logic]
        T_BL --> T_DA[Data Access]
        T_DA --> T_DB[(Database)]
    end

    subgraph ONION[Onion and Clean - dependencies go inward]
        direction TB
        O_INFRA[Infrastructure] --> O_APP[Application]
        O_UI[Presentation] --> O_APP
        O_APP --> O_DOM[Domain]
    end

Traditional layering makes the business layer a client of the data-access layer. A change to that lower layer’s public contract can therefore ripple upward, even when the database engine itself remains hidden. Onion and Clean designs invert ownership of the boundary: an inner layer defines the persistence capability it needs, and Infrastructure implements it. Database-specific changes can then stay behind the adapter.

Layered, Hexagonal, Onion, and Clean

These names overlap, but they are not synonyms. Layered architecture is the broad structure. Hexagonal, Onion, and Clean describe stronger ways to protect policy from outer details.

  • Traditional layered (N Layer) uses a top-down chain: UI → Business → Data. It is easy to read, but the business layer remains a client of data access.
  • Hexagonal (Ports and Adapters, Alistair Cockburn) separates the application from outside actors. The application exposes or consumes ports, and adapters connect HTTP, persistence, tests, or other technologies. The IOrderRepository and IEmailSender interfaces in the diagrams are ports.
  • Onion (Jeffrey Palermo) draws dependencies as concentric rings with the domain model at the center. Outer infrastructure depends on inner interfaces.
  • Clean (Robert C. Martin) names the rings Entities, Use Cases, Interface Adapters, and Frameworks, then applies the Dependency Rule across every boundary.

The choice is less about diagram shape than the dependency contract the code actually enforces. Software Architecture/Application Architecture/Clean Architecture covers the most explicit inward rule. The same module-boundary discipline also matters inside a Software Architecture/System Architecture/Modular Monolith, where compile-time references determine whether modules remain independent.

.NET Example

// Domain layer — no dependencies on EF Core or ASP.NET
public class Order
{
    public int Id { get; private set; }
    public Money Total { get; private set; } = Money.Zero;
 
    public void AddItem(Product product, int quantity)
    {
        // Business rule: enforce invariants here
        if (quantity <= 0) throw new DomainException("Quantity must be positive");
        Total = Total.Add(product.Price.Multiply(quantity));
    }
}
 
// Application layer — depends on domain + abstractions
public class PlaceOrderHandler(IOrderRepository orders, IEmailSender email)
{
    public async Task HandleAsync(PlaceOrderCommand cmd, CancellationToken ct)
    {
        var order = new Order();
        foreach (var item in cmd.Items)
            order.AddItem(item.Product, item.Quantity);
 
        await orders.SaveAsync(order, ct);
        await email.SendConfirmationAsync(cmd.CustomerEmail, order, ct);
    }
}
 
// Infrastructure layer — implements application abstractions
public class EfOrderRepository(AppDbContext db) : IOrderRepository
{
    public async Task SaveAsync(Order order, CancellationToken ct)
        => await db.Orders.AddAsync(order, ct);
}

This sketch only stages the entity in EF Core. A complete transaction or Unit of Work must call SaveChangesAsync(ct) before sending the external confirmation. Otherwise the email can describe an order that was never committed.

Pitfalls

Anemic domain model. Service classes accumulate the rules while Domain objects become data bags. The project references may be correct, yet there is little policy at the center for the architecture to protect.

Layer bypass. A controller that calls repositories directly now coordinates application behavior at the HTTP boundary. Either route the operation through an application use case or remove the unused layer. A ceremonial boundary is worse than an honest, smaller structure.

Over-engineering small apps. Four projects and an interface for every class add ceremony to a three-endpoint CRUD API. A thin structure is enough until behavior becomes complex enough to need isolation.

Questions

References