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)