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 inyectan
Tradeoffs
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