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
| Capa | Responsabilidad | Dependencias |
|---|---|---|
| Núcleo de Dominio | Entidades, objetos de valor, eventos de dominio, reglas de negocio | Ninguna (pura) |
| Servicios de Dominio | Operaciones que no pertenecen a una entidad | Núcleo de Dominio |
| Servicios de Aplicación | Casos de uso, orquestación, DTOs | Núcleo de Dominio, Servicios de Dominio |
| Infraestructura | Acceso a BD, APIs externas, mensajería, sistema de archivos | Servicios de Aplicación (vía interfaces) |
| Presentación | Controladores, manejadores CLI, vistas | Servicios 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.
Recursos Relacionados
Layered Architecture — N-Tier Explained
A practical guide to Layered (N-Tier) Architecture: separating presentation, business logic, and data layers with clear responsibilities and dependency rules.
GuideVertical Slice Architecture — Feature-First Organization
A practical guide to Vertical Slice Architecture: organizing code by feature instead of technical concern, reducing cross-layer navigation and improving cohesion.