Simplified Clean Architecture - A Practical Approach
A practical guide to implementing a simplified version of Clean Architecture in .NET, reducing unnecessary complexity while maintaining structure and scalability.
Simplified Clean Architecture: A Practical Approach
Introduction
Clean Architecture is a powerful design pattern that ensures maintainability, scalability, and separation of concerns in software projects. However, many developers find traditional Clean Architecture implementations overly complex, with numerous layers and abstractions that may not be necessary for small to medium-sized applications.
This article presents a simplified approach to Clean Architecture that retains the core principles while reducing unnecessary complexity. The goal is to provide a structured, easy-to-follow framework that balances architecture best practices with simplicity.
Understanding the Key Layers
A Clean Architecture-based application is typically divided into four main layers:
1. Core Layer (Domain)
This layer represents the heart of the application, containing business logic, entities, and domain rules. It is completely independent of external dependencies.
Key Components:
- Entities: Core business objects such as
User,Post, orOrder. - Value Objects: Immutable objects that represent domain concepts like
EmailorMoney. - Interfaces: Contracts for repositories and domain services.
Example:
public class User
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Email { get; private set; }
public User(string name, string email)
{
Id = Guid.NewGuid();
Name = name;
Email = email;
}
}
2. Application Layer
This layer orchestrates business logic and acts as an intermediary between the Core and Infrastructure layers. It contains use cases, service implementations, and application-specific logic.
Key Components:
- Services: Contain use case logic.
- Interfaces: Define contracts for application services and repositories.
Example:
public interface IUserService
{
Task<Guid> CreateUserAsync(string name, string email);
Task<User> GetUserByIdAsync(Guid id);
}
3. Infrastructure Layer
This layer provides implementations for external dependencies such as databases, external APIs, and file systems. It depends on both the Core and Application layers.
Key Components:
- Repositories: Implement data persistence logic.
- Services: Handle external operations like sending emails or processing payments.
Example:
public class UserRepository : IUserRepository
{
private readonly AppDbContext _context;
public UserRepository(AppDbContext context)
{
_context = context;
}
public async Task AddAsync(User user)
{
await _context.Users.AddAsync(user);
await _context.SaveChangesAsync();
}
}
4. Web API (Presentation) Layer
This layer exposes the application to external clients via HTTP endpoints. It depends on the Application layer and acts as a communication bridge.
Key Components:
- Controllers: Handle HTTP requests and responses.
- DTOs (Data Transfer Objects): Shape data for API consumption.
Example:
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] CreateUserDto dto)
{
var userId = await _userService.CreateUserAsync(dto.Name, dto.Email);
return Ok(new { Id = userId });
}
}
Project File Structure
A well-organized project structure is crucial for maintainability. Below is the suggested file structure:
/src
├── Core
│ ├── Domain
│ │ ├── Entities
│ │ ├── Interfaces
├── Application
│ ├── Services
│ ├── Interfaces
├── Infrastructure
│ ├── Data
│ ├── Repositories
├── WebAPI
│ ├── Controllers
│ ├── Models
/tests
├── UnitTests
Running the Project
To set up the project, follow these steps:
-
Install dependencies:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.AspNetCore.Mvc -
Configure Dependency Injection in
Program.cs:builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); -
Run the application:
dotnet run --project WebAPI
Final Thoughts
Clean Architecture does not have to be complex. By following this simplified approach, you can still reap the benefits of a maintainable and scalable application without unnecessary complexity. Start with a minimal structure, and expand only when needed.
Would you like to see a sample project with this structure? Let me know in the comments!
Written by Emil Bolet · Cloud Operations Manager · More about me →
0 responses
Keep reading
Do You Really Need Kubernetes in a Modern Azure Environment?
AKS vs Azure Container Apps vs App Service - A story-driven framework to pick the least platform that ships now without painting yourself into a cluster.
No Silver Bullets, No Golden Hammers - Build What You Need, When You Need It
Skip the cargo cult. Start with a clean monolith, design seams, and split only when the data says so, with tools, checklists, and a safe extraction plan.
Unleashing the Power of MCP with .NET and Azure - A Fun and Practical Guide
Dive into Model Context Protocol (MCP) and discover how to build real-time, strongly typed, and reactive APIs with .NET and Azure. This energetic guide compares MCP to REST, walks through building an MCP server in C#, and shows how to secure it using Azure API Management.
One thoughtful article, every month.
No fluff, no recaps. Just deep technical writing, delivered to your inbox.