Layered Architecture
Intro
Layered architecture structures an application into layers with clear responsibilities and strict dependency directions. Each layer only depends on the layer directly below it (traditional) or on inner layers (onion/clean). The goal is to isolate business rules from infrastructure details — so you can swap databases, frameworks, or delivery mechanisms without touching domain logic.
Layer Responsibilities
A typical four-layer structure:
| Layer | Responsibility | Examples |
|---|---|---|
| Presentation | Handle input, render output | ASP.NET Core controllers, Razor views, Blazor components |
| Application | Orchestrate use cases, coordinate domain + infrastructure | Service classes, CQRS handlers, DTOs |
| Domain | Business rules, entities, invariants | Entities, value objects, domain events, domain services |
| Infrastructure | Technical details: persistence, messaging, external APIs | EF Core DbContext, HTTP clients, email senders |
Dependency Rule
All dependencies point inward. The Domain knows nothing about databases, frameworks, or UI. Infrastructure implements interfaces defined by inner layers.
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
OPORT --> UCTraditional 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]
endIn traditional layered architecture, changing the database affects everything above it. In Onion/Clean Architecture, the dependency is inverted: Infrastructure depends on the Domain through interfaces, so you can swap databases without touching business rules.
.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; }
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
{
private readonly IOrderRepository _orders;
private readonly 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 domain abstractions
public class EfOrderRepository : IOrderRepository
{
private readonly AppDbContext _db;
public async Task SaveAsync(Order order, CancellationToken ct)
=> await _db.Orders.AddAsync(order, ct);
}
Pitfalls
Anemic domain model
Business logic leaks into the Application layer (service classes do everything) while the Domain layer contains only data bags. The layers exist but the dependency rule is violated in spirit — the Domain has no behavior to protect.
Layer bypass
Controllers calling repositories directly, skipping the Application layer. Breaks the single-responsibility of each layer and makes the codebase harder to test.
Over-engineering small apps
Four layers with interfaces and DI for a 3-endpoint CRUD API adds ceremony without benefit. Apply layered architecture when the domain has real complexity worth protecting.
Questions
All source code dependencies must point inward — toward higher-level policies (domain). Outer layers (infrastructure, UI) depend on inner layers; inner layers never depend on outer layers. This means you can change databases, frameworks, or delivery mechanisms without touching business rules.
Cost: requires defining interfaces in inner layers and wiring implementations in outer layers — more upfront structure.
Traditional layered: UI → Business Logic → Data Access → Database. Changing the DB affects everything above. Onion/Clean: Infrastructure → Application → Domain. The Domain has zero dependencies; Infrastructure implements Domain interfaces. The key difference is dependency inversion at the data access boundary.
References
- Multitier architecture (Wikipedia) — overview of n-tier patterns, layer responsibilities, and historical context.
- The Clean Architecture (Robert C. Martin) — the canonical article defining the dependency rule and how Clean Architecture relates to Onion and Hexagonal.
- Onion Architecture (Jeffrey Palermo) — original blog post introducing Onion Architecture with the inward-dependency model.
- ASP.NET Core architecture guidance (Microsoft) — Microsoft's guidance on layered, clean, and modular architectures for ASP.NET Core applications.