Saturday, February 28, 2026

Onion / Clean Architecture Deep Dive

 Both Clean Architecture and Onion Architecture follow the same core rule:

πŸ”‘ Dependencies always point inward toward the Domain.



Onion / Clean Architecture – Layer Overview

Typical layers (from inside to outside):

[ Domain ] ← pure business logic
[ Application ] ← use cases
[ Infrastructure ] ← database, external services
[ Presentation ] ← controllers, API, UI

Example: Employee Management System

We’ll implement:

“Create a new Employee and save it.”


Domain Layer (Core Business Logic)

πŸ“ Domain/

This is the center of the onion.

Where to define domain objects?

πŸ‘‰ HERE.

Domain objects like:

  • Employee

  • Order

  • Product

  • Customer

must live in Domain.

They must NOT depend on:

  • Database

  • Frameworks

  • Controllers

  • UI


Domain Entity

πŸ“ Domain/Entities/Employee.cs

namespace Domain.Entities
{
public class Employee
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public decimal Salary { get; private set; }

public Employee(string name, decimal salary)
{
if (salary <= 0)
throw new ArgumentException("Salary must be greater than zero");

Id = Guid.NewGuid();
Name = name;
Salary = salary;
}

public void IncreaseSalary(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Increase must be positive");

Salary += amount;
}
}
}

✔ Business rules live here
✔ No EF, no DB, no HTTP


Repository Interface (Contract)

πŸ“ Domain/Interfaces/IEmployeeRepository.cs

namespace Domain.Interfaces
{
public interface IEmployeeRepository
{
void Add(Employee employee);
Employee GetById(Guid id);
}
}

IMPORTANT:

The interface lives in Domain, not Infrastructure.

Why?

Because Domain defines what it needs.
Infrastructure will implement it.

This keeps dependencies inward.


Application Layer (Use Cases)

πŸ“ Application/

This layer orchestrates business operations.

Where to implement use cases?

πŸ‘‰ HERE.

Example use case:

  • CreateEmployee

  • IncreaseSalary

  • GetEmployee


Create Employee Use Case

πŸ“ Application/UseCases/CreateEmployee.cs

using Domain.Entities;
using Domain.Interfaces;

namespace Application.UseCases
{
public class CreateEmployee
{
private readonly IEmployeeRepository _repository;

public CreateEmployee(IEmployeeRepository repository)
{
_repository = repository;
}

public Guid Execute(string name, decimal salary)
{
var employee = new Employee(name, salary);
_repository.Add(employee);

return employee.Id;
}
}
}

✔ Uses Domain entity
✔ Uses Domain interface
✔ Does NOT know about database


Infrastructure Layer (External World)

πŸ“ Infrastructure/

This is where:

  • EF Core

  • SQL

  • MongoDB

  • File system

  • External APIs

live.


Where to implement repository?

πŸ‘‰ HERE.


EF Repository Implementation

πŸ“ Infrastructure/Repositories/EmployeeRepository.cs

using Domain.Entities;
using Domain.Interfaces;

namespace Infrastructure.Repositories
{
public class EmployeeRepository : IEmployeeRepository
{
private readonly AppDbContext _context;

public EmployeeRepository(AppDbContext context)
{
_context = context;
}

public void Add(Employee employee)
{
_context.Employees.Add(employee);
_context.SaveChanges();
}

public Employee GetById(Guid id)
{
return _context.Employees.Find(id);
}
}
}

✔ Implements Domain interface
✔ Depends on EF
✔ Domain does NOT depend on this

Dependency direction:

Infrastructure → Domain

Correct ✅


Presentation Layer (API / UI)

πŸ“ WebAPI/Controllers/EmployeeController.cs


Where to call the use case?

πŸ‘‰ HERE (Presentation Layer)


using Application.UseCases;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/employees")]
public class EmployeeController : ControllerBase
{
private readonly CreateEmployee _createEmployee;

public EmployeeController(CreateEmployee createEmployee)
{
_createEmployee = createEmployee;
}

[HttpPost]
public IActionResult Create(string name, decimal salary)
{
var id = _createEmployee.Execute(name, salary);
return Ok(id);
}
}

✔ Controller calls Use Case
✔ Controller does NOT create Employee directly
✔ Controller does NOT talk to DB


Summary – Clear Answers
❓ Where to define domain objects like Employee or Order?

Domain Layer
Domain/Entities/Employee.cs
Domain/Entities/Order.cs

They contain:

  • Business rules
  • Validation
  • Core behavior

They must NOT know:

  • Database
  • UI
  • Frameworks


❓ Where to implement repository?

  • Interface → Domain
  • Implementation → Infrastructure

Domain/Interfaces/IEmployeeRepository
Infrastructure/Repositories/EmployeeRepository

❓ Where to implement business logic?

  • Core rules → Domain
  • Use case orchestration → Application


❓ Where to call use cases?

From:

  • Controllers
  • API endpoints
  • UI
  • Background jobs

That’s the Presentation Layer.


Dependency Rule (Most Important)

Only this direction is allowed:

Presentation → Application → Domain
Infrastructure → Domain

🚫 Domain must never depend on outer layers.


Clean vs Onion?

Practically identical in structure.

  • Onion Architecture was popularized by Jeffrey Palermo
  • Clean Architecture was formalized by Robert C. Martin

Both enforce:

“Business rules should not depend on frameworks.”

No comments:

Post a Comment

Node | Cluster Vs Worker Threads

Cluster: Multiple processes (scale app across CPU cores) Worker Threads: Multiple threads (handle CPU-heavy work inside one process) Cluster...