← dev-notes
SOLID · 05 / 05

DIP
DEPENDENCY INVERSION

Los detalles dependen de las políticas, no al revés

Abstracciones Inyección Frameworks al borde
01 Definición formal
FORMAL · DEFINICIÓN

Los módulos de alto nivel (políticas de negocio) no deben depender de módulos de bajo nivel (detalles de implementación). Ambos deben depender de abstracciones. Las abstracciones no deben depender de los detalles; son los detalles (infraestructura, SDKs, drivers) los que deben depender de las abstracciones.

En Java moderno suele traducirse en interfaces en el núcleo del dominio o de la aplicación, con adaptadores en el borde que implementan esas interfaces usando JDBC, REST, colas, etc.

02 Idea clave

Si el caso de uso «generar informe semanal de ventas» instancia new SmtpEmailClient(), el negocio queda acoplado a SMTP y a esa biblioteca. Cambiar a SendGrid o a un bus de eventos obliga a editar la lógica que debería ser estable. La inversión: el caso de uso depende de ReportDelivery; el adaptador SMTP implementa eso.

03 Cuando se rompe · Java

El servicio de informes conoce directamente el cliente HTTP del proveedor de correo y la ruta del archivo PDF.

ROMPE DIP — ALTO NIVEL → DETALLE CONCRETO
public final class WeeklySalesReportJob {

    public void run(SalesRepository repo) {
        var rows = repo.lastWeekRows();
        byte[] pdf = new PdfBoxRenderer().render(rows);

        var mail = new com.vendor.mail.MailApiClient(
            System.getenv("MAIL_API_KEY")); // SDK concreto
        mail.sendBinary("ops@empresa.com", "ventas.pdf", pdf);
    }
}
04 Aplicación adecuada · Java

El trabajo declara la necesidad de «entregar un informe»; quien compone el grafo en el main o el contenedor Spring elige SMTP, S3 o Slack.

RESPETA DIP — POLÍTICA SOBRE ABSTRACCIÓN
public interface ReportRenderer {
    byte[] render(SalesSummary summary);
}

public interface ReportDelivery {
    void deliver(String filename, byte[] content);
}

public final class WeeklySalesReportJob {
    private final ReportRenderer renderer;
    private final ReportDelivery delivery;

    public WeeklySalesReportJob(ReportRenderer renderer, ReportDelivery delivery) {
        this.renderer = renderer;
        this.delivery = delivery;
    }

    public void run(SalesRepository repo) {
        var summary = SalesSummary.from(repo.lastWeekRows());
        byte[] pdf = renderer.render(summary);
        delivery.deliver("ventas-semana.pdf", pdf);
    }
}

// En infraestructura: MailApiReportDelivery implements ReportDelivery { … }

WeeklySalesReportJob sigue estable si mañana el informe va a un bucket S3 o a un canal interno: solo cambian los adaptadores, no la política de «obtener datos → renderizar → entregar».

05 Relación con patrones
PUENTE SOLID ↔ GOF
  • Abstract Factory / Factory Method: ayudan a crear dependencias concretas fuera del caso de uso.
  • Adapter: implementa puertos del dominio envolviendo SDKs o frameworks de infraestructura.
  • Evita sobreingeniería: no abstraigas librerías que no van a cambiar ni forman parte del núcleo.