Hexagonal (Ports & Adapters)
El dominio define puertos (interfaces); cada actor externo (DB, UI, MQ) llega a través de un adaptador.
Contexto
Mismo objetivo que Clean: aislar el dominio. La metáfora hexagonal enfatiza la simetría entre lo que llama al dominio (driving) y lo que el dominio llama (driven).
Solución
- Driving / Primary adapters: Web API, CLI, tests → llaman a un puerto de entrada del dominio.
- Driven / Secondary adapters: EF, HTTP client, MQ → implementan un puerto de salida del dominio.
[HTTP API] [CLI] [Tests]
│ │ │
▼ ▼ ▼
┌─────── Application ───────┐
│ ports in / ports out │
└────────────┬──────────────┘
│
┌────────────┴──────────────┐
▼ ▼ ▼
[EF Core] [Stripe SDK] [RabbitMQ]
#Ejemplo en C#
// Puerto de entrada (driving)
public interface IPlaceOrder { Task<Guid> Handle(PlaceOrderCommand cmd); }
// Puerto de salida (driven)
public interface IOrderRepository { void Add(Order o); }
public interface IPaymentGateway { Task<bool> ChargeAsync(decimal amount); }
// Caso de uso (núcleo, sin frameworks)
public class PlaceOrderUseCase : IPlaceOrder
{
private readonly IOrderRepository _repo;
private readonly IPaymentGateway _payments;
public PlaceOrderUseCase(IOrderRepository r, IPaymentGateway p) { _repo = r; _payments = p; }
public async Task<Guid> Handle(PlaceOrderCommand cmd)
{
var order = Order.Create(cmd);
if (!await _payments.ChargeAsync(order.Total))
throw new PaymentDeclined();
_repo.Add(order);
return order.Id;
}
}
// Adapters concretos viven afuera y se inyectanTradeoffs
| Pro | Contra |
|---|---|
| Tests del dominio sin levantar nada | Necesitas ports duales (in/out) bien diseñados |
| Intercambias adapters libremente | Diseño inicial cuidadoso |
#architecture #ports #adapters