Skip to content
SP StackPractices
intermediate Por StackPractices

Arquitectura Onion — Inversión de Dependencias en la Práctica

Guía práctica de Arquitectura Onion: organizar código alrededor del modelo de dominio, forzar dirección de dependencias hacia adentro y aislar infraestructura de la lógica de negocio.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Overview

La Arquitectura Onion, popularizada por Jeffrey Palermo, estructura aplicaciones como capas concéntricas con el modelo de dominio en el centro. A diferencia de la arquitectura tradicional por capas donde las dependencias apuntan hacia abajo (UI → Negocio → Datos), Onion invierte esto: todas las dependencias apuntan hacia adentro hacia el núcleo del dominio. La infraestructura, UI y servicios externos viven en los bordes exteriores y dependen de abstracciones internas, nunca al revés. Esto hace que el modelo de dominio quede completamente aislado de frameworks, bases de datos y mecanismos de entrega.

Cuándo Usar

  • Necesitas un modelo de dominio que sobreviva cambios de framework
  • Tu lógica de negocio es compleja y cambia frecuentemente
  • Quieres diferir decisiones tecnológicas (base de datos, framework, UI)
  • Probar reglas de negocio sin base de datos ni servidor web es prioridad
  • Estás aplicando principios de Domain-Driven Design (DDD)

Las Capas

CapaResponsabilidadDependencias
Núcleo de DominioEntidades, objetos de valor, eventos de dominio, reglas de negocioNinguna (pura)
Servicios de DominioOperaciones que no pertenecen a una entidadNúcleo de Dominio
Servicios de AplicaciónCasos de uso, orquestación, DTOsNúcleo de Dominio, Servicios de Dominio
InfraestructuraAcceso a BD, APIs externas, mensajería, sistema de archivosServicios de Aplicación (vía interfaces)
PresentaciónControladores, manejadores CLI, vistasServicios de Aplicación

Regla de Dependencia

Todas las dependencias apuntan hacia adentro. Las capas exteriores dependen de las capas interiores vía interfaces definidas en las capas internas.

// Núcleo de Dominio — capa más interna
public interface IOrderRepository
{
    Task<Order> GetByIdAsync(OrderId id);
    Task SaveAsync(Order order);
}

public class Order
{
    public OrderId Id { get; private set; }
    public Money Total { get; private set; }
    private List<OrderLine> _lines = new();

    public void AddLine(Product product, int quantity)
    {
        if (quantity <= 0) throw new DomainException("La cantidad debe ser positiva");
        _lines.Add(new OrderLine(product, quantity));
        RecalculateTotal();
    }

    private void RecalculateTotal() =>
        Total = _lines.Aggregate(Money.Zero, (sum, line) => sum + line.Subtotal);
}
// Capa de Aplicación — orquesta casos de uso
public class PlaceOrderHandler
{
    private readonly IOrderRepository _orderRepository;
    private readonly IProductRepository _productRepository;
    private readonly IEventBus _eventBus;

    public PlaceOrderHandler(
        IOrderRepository orderRepository,
        IProductRepository productRepository,
        IEventBus eventBus)
    {
        _orderRepository = orderRepository;
        _productRepository = productRepository;
        _eventBus = eventBus;
    }

    public async Task<OrderId> Handle(PlaceOrderCommand command)
    {
        var order = new Order();
        foreach (var item in command.Items)
        {
            var product = await _productRepository.GetByIdAsync(item.ProductId);
            order.AddLine(product, item.Quantity);
        }
        await _orderRepository.SaveAsync(order);
        await _eventBus.PublishAsync(new OrderPlacedEvent(order.Id, order.Total));
        return order.Id;
    }
}
// Capa de Infraestructura — implementa interfaces del dominio
public class SqlOrderRepository : IOrderRepository
{
    private readonly AppDbContext _dbContext;

    public SqlOrderRepository(AppDbContext dbContext) => _dbContext = dbContext;

    public async Task<Order> GetByIdAsync(OrderId id) =>
        await _dbContext.Orders
            .Include(o => o.Lines)
            .FirstAsync(o => o.Id == id);

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

Puertos y Adaptadores

Las capas exteriores implementan interfaces (puertos) definidas por las capas internas. Este es el patrón Puertos y Adaptadores.

┌─────────────────────────────────────┐
│  Presentación (Controladores, CLI) │
│         ↓ usa interfaces           │
├─────────────────────────────────────┤
│  Servicios de Aplicación (casos)   │
│         ↓ usa interfaces           │
├─────────────────────────────────────┤
│  Servicios de Dominio (operaciones)│
│         ↓ usa                     │
├─────────────────────────────────────┤
│  Núcleo de Dominio (entidades)    │
└─────────────────────────────────────┘

   Infraestructura implementa interfaces definidas arriba

Errores Comunes

  • Filtrar detalles del ORM al dominio — la configuración de mapeo pertenece a infraestructura, no a clases de entidad
  • Servicios de aplicación con lógica de negocio — las reglas de negocio pertenecen al dominio, la orquestación a aplicación
  • Dependencias circulares — usa herramientas como ArchUnit o NetArchTest para forzar límites de capa
  • Modelo de dominio anémico — las entidades deben encapsular comportamiento, no solo datos
  • Demasiadas capas — para CRUD simple, Onion puede ser exceso; úsala cuando la complejidad del dominio lo justifica

FAQ

Onion vs Clean Architecture? Ambas comparten el mismo principio de inversión de dependencias. Onion nombra explícitamente las capas (Dominio, Aplicación, Infraestructura, Presentación), mientras que Clean Architecture usa un modelo de anillos concéntricos más genérico. Son funcionalmente equivalentes.

Puedo usar Onion en una aplicación monolítica? Sí. La Arquitectura Onion funciona a nivel de módulo o aplicación. Un monolito puede tener múltiples módulos estructurados con Onion.

Qué ORM funciona mejor con Onion? Cualquier ORM que soporte entidades POCO/POJO sin requerir clases base o atributos. EF Core con Fluent API, Dapper, Hibernate con mapeos XML, o SQLAlchemy con base declarativa funcionan bien.