Many developers hear about Clean Architecture but struggle with one simple question:
How should I structure my projects?
In this article we will look at a simple and practical project structure for ASP.NET Core using the ideas from Clean Architecture, introduced by software engineer Robert C. Martin (Uncle Bob).
The goal is simple:
- keep business logic independent
- keep infrastructure replaceable
- keep the system easy to maintain
Let’s break it down.
The 4 Project Structure
A very clean approach is to split the solution into four projects.
MyProject
MyProject.Domain
MyProject.Application
MyProject.Infrastructure
MyProject.API
Each project has a very specific responsibility.
1. Domain Layer (The Core of the System)
The Domain layer contains the business rules.
Nothing here should depend on frameworks, databases, or external services.
Typical folders:
Domain
├── Entities
├── ValueObjects
├── Enums
└── Exceptions
Example entity:
public class Order
{
public Guid Id { get; private set; }
public decimal Amount { get; private set; }
public void Pay()
{
// business rule
}
}
Important rule:
The Domain layer should have zero dependencies.
2. Application Layer
The Application layer orchestrates the use cases of the system.
This is where we define interfaces for external dependencies such as repositories or services.
Example structure:
Application
├── Interfaces
├── Services
├── DTOs
├── Commands
└── Queries
Example interface:
public interface IOrderRepository
{
Task<Order> GetByIdAsync(Guid id);
Task SaveAsync(Order order);
}
Application services use these interfaces without knowing how they are implemented.
public class OrderService
{
private readonly IOrderRepository _repository;
public OrderService(IOrderRepository repository)
{
_repository = repository;
}
public async Task PayOrder(Guid id)
{
var order = await _repository.GetByIdAsync(id);
order.Pay();
await _repository.SaveAsync(order);
}
}
3. Infrastructure Layer
The Infrastructure layer contains technical implementations.
Examples:
- database access
- external APIs
- email services
- file storage
Example structure:
Infrastructure
├── Persistence
├── Services
└── External
Repository implementation:
public class OrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
public OrderRepository(AppDbContext context)
{
_context = context;
}
public async Task<Order> GetByIdAsync(Guid id)
{
return await _context.Orders.FindAsync(id);
}
public async Task SaveAsync(Order order)
{
_context.Update(order);
await _context.SaveChangesAsync();
}
}
Notice something important:
The Infrastructure layer depends on the Application layer, not the other way around.
4. API Layer
The API layer is the entry point of the system.
Typical structure:
API
├── Controllers
└── Program.cs
Example controller:
[ApiController]
[Route("orders")]
public class OrdersController : ControllerBase
{
private readonly OrderService _service;
public OrdersController(OrderService service)
{
_service = service;
}
[HttpPost("{id}/pay")]
public async Task<IActionResult> Pay(Guid id)
{
await _service.PayOrder(id);
return Ok();
}
}
Dependency Direction (The Most Important Rule)
The direction of dependencies should look like this:
API
↓
Application
↓
Domain
Infrastructure → Application
This rule ensures that business logic remains independent from frameworks and databases.
Dependency Injection
The concrete implementations are wired up in the API project:
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
This allows the Application layer to stay clean and unaware of infrastructure details.
A Senior Developer Tip
As systems grow, many teams move from simple services to patterns like CQRS (Command Query Responsibility Segregation).
Instead of this:
Application
└── Services
you may see something like this:
Application
├── Commands
├── Queries
├── Handlers
└── Behaviors
This approach scales better in larger systems.
Final Thoughts
Clean Architecture is not about adding complexity.
It is about separating responsibilities so your system becomes:
- easier to test
- easier to maintain
- easier to evolve
Start simple, keep the layers clean, and your architecture will scale naturally as your application grows.
Top comments (0)