Cargando...

Arquitectura en capas y Clean Architecture en C#

Aprende arquitectura en capas y Clean Architecture en C# para crear aplicaciones mantenibles y bien estructuradas.

A medida que los proyectos de software crecen, mantener el código organizado y sostenible se vuelve cada vez más importante. Para este propósito, se utilizan la arquitectura en capas (Layered Architecture) y su evolución moderna, la arquitectura limpia (Clean Architecture). Ambos modelos proporcionan una base sólida para gestionar dependencias, separar responsabilidades y construir sistemas comprobables y extensibles.


¿Qué es la arquitectura en capas?

La arquitectura en capas es un modelo clásico que divide una aplicación en capas con responsabilidades diferentes. Su forma más común consta de 3 o 4 capas:

La dirección de las dependencias es de arriba hacia abajo: la UI depende del negocio, el negocio depende del acceso a datos, pero no al revés.


Flujo clásico de la arquitectura en capas

El siguiente diagrama muestra un ejemplo simple:


graph TD;
  A[Capa UI] --> B[Capa de Negocio];
  B --> C[Capa de Acceso a Datos];
  C --> D[Base de Datos];

Esta estructura es suficiente para sistemas CRUD simples, pero puede volverse compleja cuando las capas están muy acopladas.


¿Qué es la Clean Architecture?

La Clean Architecture, propuesta por Robert C. Martin (Uncle Bob), orienta las dependencias hacia el centro (el dominio). Su objetivo es mantener las reglas de negocio completamente aisladas del mundo exterior.

En esta arquitectura, las dependencias van ahora de afuera hacia adentro:


graph LR;
  A[UI / API] --> B[Aplicación];
  B --> C[Dominio];
  C --> D[Infraestructura (Externa)];

La diferencia clave: la capa de Dominio nunca depende de las capas externas.


Capas y responsabilidades en la Clean Architecture


Regla de Dependencia

El principio fundamental de la Clean Architecture es que las dependencias siempre apuntan hacia el centro. Las capas externas pueden conocer las internas, pero las internas no deben conocer las externas.


// Capa Dominio (sin dependencias)
public class Order
{
    public int Id { get; set; }
    public decimal Total { get; set; }
    public bool IsApproved { get; private set; }

    public void Approve()
    {
        if (Total <= 0)
            throw new InvalidOperationException("El total debe ser mayor que 0.");
        IsApproved = true;
    }
}

// Capa Aplicación
public interface IOrderRepository
{
    Task<Order> GetByIdAsync(int id);
    Task SaveAsync(Order order);
}

public class ApproveOrderUseCase
{
    private readonly IOrderRepository _repository;
    public ApproveOrderUseCase(IOrderRepository repository)
        => _repository = repository;

    public async Task ExecuteAsync(int id)
    {
        var order = await _repository.GetByIdAsync(id);
        order.Approve();
        await _repository.SaveAsync(order);
    }
}

// Capa Infraestructura
public class EfOrderRepository : IOrderRepository
{
    private readonly AppDbContext _context;
    public EfOrderRepository(AppDbContext context) => _context = context;

    public Task<Order> GetByIdAsync(int id) =>
        _context.Orders.FirstOrDefaultAsync(o => o.Id == id);

    public Task SaveAsync(Order order)
    {
        _context.Update(order);
        return _context.SaveChangesAsync();
    }
}

// Capa Presentación (API)
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly ApproveOrderUseCase _useCase;
    public OrdersController(ApproveOrderUseCase useCase) => _useCase = useCase;

    [HttpPost("{id}/approve")]
    public async Task Approve(int id)
    {
        await _useCase.ExecuteAsync(id);
        return Ok();
    }
}

Como se puede ver, la capa de Dominio no conoce nada del código de infraestructura, y la capa de Aplicación solo trabaja con interfaces. Esto permite cambiar dependencias fácilmente (por ejemplo, sustituir EF Core por Dapper).


Gestión de dependencias entre capas

Las dependencias suelen resolverse mediante inyección de dependencias (DI) en la capa de Infraestructura o en el Composition Root:


// Program.cs o Startup.cs
builder.Services.AddScoped<IOrderRepository, EfOrderRepository>();
builder.Services.AddScoped<ApproveOrderUseCase>();

Así, el controlador solo conoce ApproveOrderUseCase sin importar qué repositorio se utilice.


Diferencias entre la Arquitectura en Capas y la Clean Architecture

Característica Arquitectura en Capas Clásica Clean Architecture
Dirección de dependencias De arriba hacia abajo De afuera hacia adentro (hacia el núcleo)
Dependencia de la base de datos La lógica de negocio depende de la base de datos El dominio es independiente del acceso a datos
Testabilidad Difícil Fácil (interfaces simuladas)
Modificabilidad Capas fuertemente acopladas Dependencias gestionadas por abstracciones
Mantenimiento a largo plazo Difícil de mantener Modular y fácil de mantener

Ejemplo real: aplicación de comercio electrónico

A continuación se muestra un ejemplo simplificado de una aplicación de Clean Architecture en un sistema de comercio electrónico:

Con esta estructura, incluso si la interfaz de usuario cambia (por ejemplo, API → WPF), las reglas de negocio (Dominio y Aplicación) permanecen intactas.


Buenas prácticas


Estructura de proyecto de ejemplo


MyApp/
├── MyApp.Domain/
│   ├── Entities/
│   ├── Interfaces/
│   └── Exceptions/
├── MyApp.Application/
│   ├── UseCases/
│   ├── Services/
│   └── DTOs/
├── MyApp.Infrastructure/
│   ├── Persistence/
│   └── Repositories/
└── MyApp.API/
    ├── Controllers/
    └── Program.cs

Esta estructura representa una solución modular en .NET conforme a los principios de Clean Architecture.


TL;DR

  • Arquitectura en capas: Modelo clásico con flujo UI → Negocio → Datos.
  • Clean Architecture: Invierte las dependencias; el núcleo es el Dominio.
  • Dominio es independiente, Aplicación gestiona el flujo, Infraestructura maneja los sistemas externos.
  • Inyección de dependencias es la clave para gestionar dependencias.
  • Cada capa debe tener una responsabilidad clara, ser comprobable y reemplazable.
  • Clean Architecture es ideal para sistemas sostenibles, mantenibles y escalables.

Artículos relacionados