DEV Community

Ravi Vishwakarma
Ravi Vishwakarma

Posted on

Clean Architecture in .net application step by step

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

  1. Separation of Concerns: Each layer of the application has a specific responsibility and doesn't mix with others.
  2. 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.
  3. 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.
  4. 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

  1. Domain Layer (Entities)
    • Contains core business rules
    • Framework and UI agnostic
    • Pure business objects (e.g., User, Order)
  2. Application Layer (Use Cases)
    • Contains application-specific logic
    • Coordinates the flow of data
    • Interfaces with repositories
    • E.g., CreateUser, GetOrders
  3. Interface Adapters Layer
    • Adapts data between use cases and frameworks
    • Includes Controllers, Gateways, ViewModels
    • Implements interfaces from Application Layer
  4. 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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

📁 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; } } } 
Enter fullscreen mode Exit fullscreen mode

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); } } 
Enter fullscreen mode Exit fullscreen mode

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); } } 
Enter fullscreen mode Exit fullscreen mode

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); } } 
Enter fullscreen mode Exit fullscreen mode

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; } } } 
Enter fullscreen mode Exit fullscreen mode

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(); 
Enter fullscreen mode Exit fullscreen mode

Thanks
Keep Coding.

Top comments (0)