← Back to Article List         
Software Architecture - Clean Architecture

Software Architecture - Clean Architecture

Published on 24 Sep 2026     7 min read Software Architecture
Clean Architecture

1. What is Layered Architecture?

Layered Architecture divides an application into layers. Each layer has a specific responsibility.

The common three-layer structure is:

Layer Responsibility Example
Presentation / API Accept requests and return responses OrdersController
Business / BLL Validate business rules and perform calculations OrderService
Data Access / DAL Read and write database data OrderRepository

Typical flow:

Controller → Service → Repository → Database

Example: Create an order

  1. Controller: Receives product and quantity.

  2. Service: Validates quantity and calculates the total.

  3. Repository: Saves the order.

  4. Controller: Returns 201 Created.


2. Why do we need layers?

  • Separate API, business, and database responsibilities.

  • Avoid putting everything inside controllers.

  • Make code easier to understand and maintain.

  • Reuse business logic across different entry points.

  • Make testing and changes easier.

Important: Creating separate folders or projects is useful only when their responsibilities are respected.


3. Common types of layering

Type Meaning
Two-layer Presentation and combined business/data code
Three-layer Presentation, Business, and Data Access
N-layer Additional layers when needed
Strict layering A layer calls only the next lower layer
Relaxed layering A layer may skip an intermediate layer

Layer versus tier:

  • Layer: Logical separation of code.

  • Tier: Physical separation of deployment.

  • Three projects can run inside one application process.


4. Traditional Layered Architecture: dependency

Typically:

API depends on Business → Business depends on Data Access

public class OrderService
{
    private readonly SqlOrderRepository _repository;

    public OrderService(SqlOrderRepository repository)
    {
        _repository = repository;
    }
}
  • OrderService knows the concrete SqlOrderRepository.

  • Business code is therefore coupled to a data-access implementation.

  • Traditional layered applications can also use interfaces to reduce this coupling.


5. What is Clean Architecture?

Clean Architecture separates responsibilities while protecting business logic from database, UI, and external-service details.

Its main rule:

Code dependencies point inward toward the business core.

A common .NET structure uses four layers:

Layer Responsibility Typical contents
Domain Business objects and rules Entities, value objects
Application Coordinate business operations Services, DTOs, interfaces
Infrastructure Implement technical operations EF Core, repositories, email
Presentation / API Handle HTTP requests and responses Controllers, middleware

Clean Architecture is also layered. Its defining feature is the dependency rule.


6. Domain Layer

Purpose: Define the business and protect its rules.

Contains:

  • Entities: Objects with identity, such as Order.

  • Value objects: Values such as Money or Address.

  • Business rules: Quantity must be positive.

  • Optional domain services and events for more complex applications.

public class Order
{
    public int Quantity { get; private set; }
    public decimal UnitPrice { get; private set; }

    public decimal Total => Quantity * UnitPrice;

    public Order(int quantity, decimal unitPrice)
    {
        if (quantity <= 0)
            throw new ArgumentException(
                "Quantity must be positive.");

        if (unitPrice <= 0)
            throw new ArgumentException(
                "Price must be positive.");

        Quantity = quantity;
        UnitPrice = unitPrice;
    }
}

Key points:

  • No controller or HTTP code.

  • No SQL or EF Core implementation code.

  • No reference to Application or Infrastructure.

  • Rules work consistently from an API, background job, or other entry point.


7. Application Layer

Purpose: Coordinate a use case.

A use case is an operation such as:

  • Create an order.

  • Cancel an order.

  • Transfer money.

  • Publish an article.

Contains:

  • Application services or handlers.

  • DTOs.

  • Interfaces for required operations.

  • Use-case validation and authorization.

Repository interface — defined in Application:

public interface IOrderRepository
{
    Task SaveAsync(Order order);
}

Application service:

public class OrderService
{
    private readonly IOrderRepository _repository;

    public OrderService(IOrderRepository repository)
    {
        _repository = repository;
    }

    public async Task CreateAsync(
        int quantity,
        decimal unitPrice)
    {
        var order = new Order(quantity, unitPrice);

        await _repository.SaveAsync(order);
    }
}

What happens here?

  1. Create an order using Domain rules.

  2. Ask the repository to save it.

  3. Remain unaware of the database implementation.

Why does Application reference Domain?

Because it uses Domain entities and business rules, such as:

var order = new Order(quantity, unitPrice);

8. Infrastructure Layer

Purpose: Implement database and external-system operations.

Contains:

  • EF Core DbContext.

  • Repository implementations.

  • Database mappings and migrations.

  • Email, file-storage, and external API integrations.

public class SqlOrderRepository : IOrderRepository
{
    private readonly AppDbContext _db;

    public SqlOrderRepository(AppDbContext db)
    {
        _db = db;
    }

    public async Task SaveAsync(Order order)
    {
        _db.Orders.Add(order);
        await _db.SaveChangesAsync();
    }
}

Key points:

  • Implements interfaces defined in the inner layers.

  • Knows how to communicate with SQL Server.

  • Depends on Application and Domain.

  • Application does not depend on this implementation.

Code snippets illustrate responsibilities; EF Core context and entity mapping are omitted.


9. Presentation / API Layer

Purpose: Convert HTTP requests into application operations.

Contains:

  • Controllers or endpoints.

  • Request models.

  • Routing and model binding.

  • Authentication integration.

  • HTTP response and error handling.

public record CreateOrderRequest(
    int Quantity,
    decimal UnitPrice);

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly OrderService _service;

    public OrdersController(OrderService service)
    {
        _service = service;
    }

    [HttpPost]
    public async Task<IActionResult> Create(
        CreateOrderRequest request)
    {
        await _service.CreateAsync(
            request.Quantity,
            request.UnitPrice);

        return StatusCode(201);
    }
}

Key points:

  • Keep controllers small.

  • Delegate business operations to Application.

  • Map errors to suitable HTTP responses through an exception handler.

  • Keep IActionResult and HttpContext out of Domain and Application.

Example simplification: A real purchase API should obtain the product price from server-side data rather than trusting the client.


10. Project dependencies

An arrow means “references / depends on.”

flowchart TD
    API["API"] --> APP["Application"]
    INF["Infrastructure"] --> APP
    APP --> DOM["Domain"]
    INF --> DOM
    API -.->|"DI registration only"| INF
Project References
Domain No other solution project
Application Domain
Infrastructure Application and Domain
API Application; Infrastructure for startup registration

Mandatory rule: Domain and Application must not depend on Infrastructure implementations.


11. How does Application call Infrastructure without referencing it?

Through an interface and Dependency Injection.

Register the implementation in Program.cs:

builder.Services.AddScoped<
    IOrderRepository, SqlOrderRepository>();

builder.Services.AddScoped<OrderService>();

At runtime:

  1. The API calls OrderService.

  2. OrderService calls IOrderRepository.

  3. DI has supplied a SqlOrderRepository.

  4. SqlOrderRepository saves the data.

Application knows the contract; DI supplies the implementation.


12. Dependency Injection versus Dependency Inversion

Concept Meaning
Dependency Injection — DI Dependencies are supplied from outside a class
Dependency Inversion — DIP Business code depends on abstractions, and technical implementations implement them
// Injection, but coupled to a concrete implementation.
OrderService(SqlOrderRepository repository)
// Injection using an Application-owned abstraction.
OrderService(IOrderRepository repository)

Interface ownership matters: Putting an interface in Infrastructure and referencing it from Application still creates an outward dependency.


13. Where should validation go?

Validation Layer
Invalid JSON or request format API
Customer must exist before placing an order Application
Quantity must be positive Domain
Unique key and foreign-key integrity Database

Domain versus Application:

  • Domain: “What business rules must always hold?”

  • Application: “What steps complete this operation?”


14. Layered versus Clean Architecture

Point Traditional Layered Clean
Main focus Separate responsibilities Separate responsibilities and protect the business core
Business dependency Often depends on Data Access Depends on inner-owned interfaces
Database implementation Lower layer Outer infrastructure
Setup Usually simpler More boundaries and contracts
Testing Depends on coupling Core can be tested without real infrastructure
Suitable example Straightforward CRUD Complex, long-lived business workflows

Both can run as one monolithic application. Neither automatically improves performance or scalability.


15. Testing and transactions

  • Domain tests: Verify business rules and calculations.

  • Application tests: Verify workflows using fake dependencies.

  • Infrastructure tests: Verify actual database queries and mappings.

  • API tests: Verify HTTP behavior, validation, and authorization.

For transactions:

  • Operations that must succeed together need one clear transaction boundary.

  • Avoid independently committing each repository call in a multi-step operation.

  • A SQL transaction cannot generally roll back an external payment or email.


16. What is optional?

Clean Architecture does not require:

  • MediatR.

  • CQRS.

  • AutoMapper.

  • A generic repository.

  • A separate Unit of Work wrapper.

  • Microservices.

  • Exactly four projects.

Use these only when they solve a real problem.


17. Key points

  • Layered Architecture separates responsibilities.

  • Clean Architecture protects business rules through inward dependencies.

  • Domain contains business entities and rules.

  • Application coordinates use cases and defines required contracts.

  • Infrastructure implements database and external-system operations.

  • API handles HTTP requests and responses.

  • Application references Domain because it uses its business model.

  • Infrastructure implements Application interfaces.

  • DI connects interfaces to implementations at startup.

  • Runtime execution flow differs from project dependency direction.

  • Keep business logic out of controllers.

  • Keep database implementation details out of the business core.

  • Choose the simplest structure that supports the application’s complexity.