Clean Architecture is a software design philosophy introduced by Robert C. Martin (Uncle Bob). Its goal is to create systems that are:
- Independent of frameworks
- Testable
- Independent of UI or database
- Flexible and maintainable
Key Principles
- Separation of Concerns: Each layer of the application has a specific responsibility and doesn't mix with others.
- Dependency Rule: Code dependencies must point inward, from outer layers (UI, DB) to inner layers (Use Cases, Entities). Inner layers know nothing about the outer layers.
- Inversion of Control: High-level policies (business rules) shouldn't depend on low-level details (DB, APIs). Instead, interfaces should be used to invert control.
-
Independence:
- Framework Independent: Can change from ASP.NET to another without affecting core logic.
- Database Independent: Can switch from SQL Server to MongoDB.
- UI Independent: Can change from Web to Mobile easily.
The Layers
-
Domain Layer (Entities)
- Contains core business rules
- Framework and UI agnostic
- Pure business objects (e.g., User, Order)
-
Application Layer (Use Cases)
- Contains application-specific logic
- Coordinates the flow of data
- Interfaces with repositories
- E.g., CreateUser, GetOrders
-
Interface Adapters Layer
- Adapts data between use cases and frameworks
- Includes Controllers, Gateways, ViewModels
- Implements interfaces from Application Layer
-
Frameworks & Drivers
- External tools and frameworks (e.g., ASP.NET Core, EF Core)
- Dependency injection and infrastructure
- Least stable and most replaceable
Flow of Control
UI (Controller) → Use Case → Domain Logic → Output/Result
- Controllers receive the input (e.g., HTTP request)
- Pass data to the Use Case
- Use Case performs business logic using the Domain
- Returns results (e.g., DTOs or ViewModels)
⚖️ Advantages
- High Testability – Business logic can be tested without UI or DB.
- Easy to Maintain – Changes in UI/DB won’t affect core logic.
- Scalability– Modular design helps teams work independently.
- Reusability– Domain and Use Cases are reusable in different apps.
⚠️ Common Mistakes
- Violating the dependency rule (e.g., use case calling Entity Framework directly)
- Mixing business logic with framework code
- Skipping interfaces (tight coupling)
🧱 Project Structure
Solution: CleanArchitectureDemo.sln
Projects:
├── CleanArchitectureDemo.Domain // Entities
├── CleanArchitectureDemo.Application // Use Cases
├── CleanArchitectureDemo.Infrastructure // DB / Repositories
├── CleanArchitectureDemo.API // Web API (Controllers)
🔧 Create Projects
dotnet new sln -n CleanArchitectureDemo
dotnet new classlib -n CleanArchitectureDemo.Domain
dotnet new classlib -n CleanArchitectureDemo.Application
dotnet new classlib -n CleanArchitectureDemo.Infrastructure
dotnet new webapi -n CleanArchitectureDemo.API
Add them to the solution:
dotnet sln add CleanArchitectureDemo.Domain
dotnet sln add CleanArchitectureDemo.Application
dotnet sln add CleanArchitectureDemo.Infrastructure
dotnet sln add CleanArchitectureDemo.API
Add project references:
dotnet add CleanArchitectureDemo.Application reference CleanArchitectureDemo.Domain
dotnet add CleanArchitectureDemo.Infrastructure reference CleanArchitectureDemo.Application
dotnet add CleanArchitectureDemo.API reference CleanArchitectureDemo.Application
dotnet add CleanArchitectureDemo.API reference CleanArchitectureDemo.Infrastructure
📁 Layer Implementations
1. Domain/Entities/User.cs
namespace CleanArchitectureDemo.Domain.Entities
{
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
}
2. Application/Interfaces/IUserRepository.cs
using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;
namespace CleanArchitectureDemo.Application.Interfaces
{
public interface IUserRepository
{
IEnumerable<User> GetAll();
User GetById(int id);
}
}
3. Application/UseCases/UserService.cs
using CleanArchitectureDemo.Application.Interfaces;
using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;
namespace CleanArchitectureDemo.Application.UseCases
{
public class UserService
{
private readonly IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public IEnumerable<User> GetUsers() => _userRepository.GetAll();
public User GetUser(int id) => _userRepository.GetById(id);
}
}
4. Infrastructure/Repositories/InMemoryUserRepository.cs
using CleanArchitectureDemo.Application.Interfaces;
using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;
using System.Linq;
namespace CleanArchitectureDemo.Infrastructure.Repositories
{
public class InMemoryUserRepository : IUserRepository
{
private readonly List<User> _users = new()
{
new User { Id = 1, Name = "Alice", Email = "alice@example.com" },
new User { Id = 2, Name = "Bob", Email = "bob@example.com" }
};
public IEnumerable<User> GetAll() => _users;
public User GetById(int id) => _users.FirstOrDefault(u => u.Id == id);
}
}
5. API/Controllers/UserController.cs
using Microsoft.AspNetCore.Mvc;
using CleanArchitectureDemo.Application.UseCases;
using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;
namespace CleanArchitectureDemo.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
private readonly UserService _userService;
public UserController(UserService userService)
{
_userService = userService;
}
[HttpGet]
public IEnumerable<User> Get() => _userService.GetUsers();
[HttpGet("{id}")]
public ActionResult<User> Get(int id)
{
var user = _userService.GetUser(id);
if (user == null)
return NotFound();
return user;
}
}
}
6. API/Program.cs – Register Dependencies
using CleanArchitectureDemo.Application.Interfaces;
using CleanArchitectureDemo.Application.UseCases;
using CleanArchitectureDemo.Infrastructure.Repositories;
var builder = WebApplication.CreateBuilder(args);
// Dependency Injection
builder.Services.AddScoped<IUserRepository, InMemoryUserRepository>();
builder.Services.AddScoped<UserService>();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();
Thanks
Keep Coding.
Top comments (0)