Los detalles dependen de las políticas, no al revés
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.
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.
El servicio de informes conoce directamente el cliente HTTP del proveedor de correo y la ruta del archivo PDF.
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); } }
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.
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».