Layered Architecture and Clean Architecture in C#
Learn layered architecture and Clean Architecture in C# to build maintainable, testable, and well-structured applications.
As software projects grow, maintaining organized and sustainable code becomes increasingly important. For this purpose, the Layered Architecture and its modern evolution, the Clean Architecture approach, are used. Both models provide a solid foundation for managing dependencies, separating responsibilities, and building testable, extensible systems.
What Is Layered Architecture?
Layered architecture is a classic software architecture pattern that divides an application into layers with different responsibilities. The most common form consists of 3 or 4 layers:
- Presentation (UI) – The user interface layer (e.g., Razor Pages, WPF, API Controllers).
- Business (Service / Application) – Contains business rules and logic.
- Data Access (Repository / Infrastructure) – Manages database or external data interactions.
- Core / Domain – Defines base models, entities, and interfaces (optional layer).
In terms of dependency direction, layers are dependent from top to bottom. That is, the UI can access the Business layer, and Business can access the Data Access layer, but not the other way around.
Classic Layered Architecture Flow
The following diagram illustrates a simple example:
graph TD;
A[UI Layer] --> B[Business Layer];
B --> C[Data Access Layer];
C --> D[Database];
This structure is sufficient for simple CRUD-based systems, but as layers become tightly coupled, it tends to grow complex over time.
What Is Clean Architecture?
Clean Architecture, introduced by Robert C. Martin (Uncle Bob), is an architectural approach that directs dependencies toward the center (the domain). Its goal is to keep the application’s business rules completely isolated from the outside world.
In this architecture, layers are dependent from the outside in:
graph LR;
A[UI / API] --> B[Application];
B --> C[Domain];
C --> D[Infrastructure (External)];
- Domain – The core: Entities, Value Objects, and interfaces.
- Application – Use Cases, services, and application-specific implementations of interfaces.
- Infrastructure – External dependencies such as databases, file systems, or email services.
- Presentation – API, web, or desktop interface layer.
The key difference here: The Domain layer is never dependent on any external layer.
Layers and Responsibilities in Clean Architecture
-
Domain: The core of the application.
Defines
Entities,Value Objects, andInterfaces. Contains the business rules. - Application: Manages workflows (Use Cases) using domain interfaces. Example: “Create Order” or “Approve Payment”.
- Infrastructure: Implements the interfaces defined in the Application layer. (e.g., EF Core, SMTP, file storage, API calls)
- Presentation: The user-facing layer, such as Web API, MVC, or WPF UI.
Dependency Rule
The most fundamental principle of Clean Architecture: Dependencies always point inward. That is, outer layers depend on inner layers, but inner layers are unaware of the outer ones.
// Domain Layer (depends on nothing)
public class Order
{
public int Id { get; set; }
public decimal Total { get; set; }
public bool IsApproved { get; private set; }
public void Approve()
{
if (Total <= 0)
throw new InvalidOperationException("Total must be greater than 0.");
IsApproved = true;
}
}
// Application Layer
public interface IOrderRepository
{
Task<Order> GetByIdAsync(int id);
Task SaveAsync(Order order);
}
public class ApproveOrderUseCase
{
private readonly IOrderRepository _repository;
public ApproveOrderUseCase(IOrderRepository repository)
=> _repository = repository;
public async Task ExecuteAsync(int id)
{
var order = await _repository.GetByIdAsync(id);
order.Approve();
await _repository.SaveAsync(order);
}
}
// Infrastructure Layer
public class EfOrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
public EfOrderRepository(AppDbContext context) => _context = context;
public Task<Order> GetByIdAsync(int id) =>
_context.Orders.FirstOrDefaultAsync(o => o.Id == id);
public Task SaveAsync(Order order)
{
_context.Update(order);
return _context.SaveChangesAsync();
}
}
// Presentation Layer (API)
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
private readonly ApproveOrderUseCase _useCase;
public OrdersController(ApproveOrderUseCase useCase) => _useCase = useCase;
[HttpPost("{id}/approve")]
public async Task Approve(int id)
{
await _useCase.ExecuteAsync(id);
return Ok();
}
}
As you can see, the Domain layer knows nothing about infrastructure code; the Application layer works only with interfaces. This allows dependencies to be swapped easily (for example, replacing EF Core with Dapper).
Dependency Management Between Layers
Dependencies are typically resolved through Dependency Injection (DI) in the Infrastructure or Composition Root layer:
// Program.cs or Startup.cs
builder.Services.AddScoped<IOrderRepository, EfOrderRepository>();
builder.Services.AddScoped<ApproveOrderUseCase>();
This way, the Controller only depends on ApproveOrderUseCase and doesn’t need to know which repository implementation is being used.
Differences Between Layered and Clean Architecture
| Feature | Classic Layered Architecture | Clean Architecture |
|---|---|---|
| Dependency Direction | Top to bottom | Outside to inside (toward the core) |
| Database Dependency | Business logic depends on the database | Domain is independent of data access |
| Testability | Difficult | Easy (mock interfaces) |
| Modifiability | Tightly coupled layers | Dependencies managed through abstractions |
| Long-term Maintenance | Becomes difficult over time | Easy to maintain, modular structure |
Real-World Example: E-Commerce Application
Below is a simplified example of how Clean Architecture might be structured in an e-commerce system:
- Domain: Entities and interfaces such as Order, Product, Customer.
- Application: Use Cases like “OrderService” or “PlaceOrderUseCase”.
- Infrastructure: EF Core-based repository “EfOrderRepository”.
- Presentation: ASP.NET Core Web API layer.
With this design, even if the UI changes (e.g., from API to WPF), the business rules (Domain & Application) remain unaffected.
Best Practices
- Manage inter-layer dependencies using interfaces.
- The Domain layer should not depend on any external NuGet packages (only .NET Standard).
- Use Use Case-based services (e.g.,
CreateOrderUseCase). - Entities should contain business rules, not just data.
- Use mapping tools like Mapster or AutoMapper for DTO/model conversions between layers.
- Organizing each layer as a separate project (assembly) is a good practice.
Example Project Structure
MyApp/
├── MyApp.Domain/
│ ├── Entities/
│ ├── Interfaces/
│ └── Exceptions/
├── MyApp.Application/
│ ├── UseCases/
│ ├── Services/
│ └── DTOs/
├── MyApp.Infrastructure/
│ ├── Persistence/
│ └── Repositories/
└── MyApp.API/
├── Controllers/
└── Program.cs
This structure represents a Clean Architecture–compliant, modular .NET solution layout.
TL;DR
- Layered Architecture: A traditional model flowing from UI → Business → Data.
- Clean Architecture: Reverses dependencies — the core is the Domain.
- Domain is independent, Application manages flow, and Infrastructure handles external systems.
- Dependency Injection is the key to managing dependencies.
- Each layer should have a clear responsibility, be testable, and replaceable.
- Clean Architecture is ideal for long-term, maintainable, and scalable systems.
Related Articles
Clean Code Principles with C#
Learn clean code principles with C# to write readable, maintainable, and scalable applications with practical examples.
Dependency Injection Basics in C#
Learn the basics of Dependency Injection in C#, including managing dependencies, loose coupling, and improving testability.
Design Patterns in C# (Factory, Singleton, Repository, Observer)
Learn design patterns in C#, including Factory, Singleton, Repository, and Observer, to build flexible and maintainable applications.
SOLID Principles with C#
Applying SOLID principles in C# with examples: building flexible, maintainable, and testable code.