Pular para o conteúdo

DIP - Dependency Inversion Principle

Robert C. Martin enuncia o DIP em duas cláusulas:

  1. “High-level modules should not depend on low-level modules. Both should depend on abstractions.”
  2. “Abstractions should not depend on details. Details should depend on abstractions.”

O termo “inversão” refere-se à inversão do sentido convencional da dependência. No fluxo ingênuo, a regra de negócio (alto nível) chama diretamente o código de infraestrutura (baixo nível: banco, HTTP, e-mail) — logo, o de alto nível depende do de baixo nível, e a política fica refém dos detalhes. O DIP inverte isso: a abstração é definida pelo/junto ao módulo de alto nível (é ele quem declara o contrato de que precisa), e o módulo de baixo nível a implementa. A seta de dependência de código passa a apontar do detalhe para a política.

Sem DIP, a lógica de negócio importa diretamente classes de infraestrutura. Consequências:

  • A política de negócio não pode ser testada sem o banco/serviço real (sem mocks fáceis).
  • Trocar tecnologia (JDBC → JPA, e-mail → SMS) obriga a mexer na regra de negócio.
  • O núcleo do sistema fica acoplado a frameworks voláteis.

O DIP quebra essa dependência introduzindo uma interface que pertence à camada de negócio. A infraestrutura passa a implementar essa interface. A regra de negócio nunca conhece a implementação concreta.

A inversão é literal e verificável: trata-se do sentido da dependência de código-fonte — na prática, de quem precisa dar import em quem. Sem DIP, o domínio importa a classe de infraestrutura e a seta aponta para fora. Com DIP, a interface mora no pacote do domínio e é a infraestrutura que a importa para implementá-la; a seta passa a apontar para dentro. O teste é mecânico: apague o módulo de infraestrutura e veja se o domínio ainda compila. Note que o fluxo de execução continua indo do domínio para o banco — o que inverte é a dependência de compilação, e é daí que vem o nome do princípio.

Satisfeita a direção da seta, resta uma segunda armadilha, mais sutil: o vocabulário da interface. Se você extrai a interface a partir da classe de banco (mesmos métodos, mesma linguagem de persistência), o compilador atesta a inversão, mas a política continua refém do detalhe — trocar Postgres por uma API externa muda a interface, e a mudança sangra para dentro do domínio. A abstração deve expressar o que a política precisa, não o que a infraestrutura oferece: aguardandoPagamento() é um conceito de negócio; selectByStatus("PENDING") é persistência disfarçada.

As duas coisas são independentes, e vale não confundi-las: a direção da seta é o DIP. O vocabulário é o que separa uma inversão real de uma que apenas cumpre a letra do princípio — uma interface com nomes de negócio morando no pacote de infraestrutura não é DIP nenhum, é só uma interface bem nomeada.

A mecânica de colocar a interface num pacote separado do implementador tem nome próprio no catálogo de Fowler: Separated Interface.

Na Arquitetura Hexagonal, essas interfaces de propriedade do domínio são os Ports; as implementações concretas de infraestrutura são os Adapters. É exatamente o DIP operando em escala arquitetural, e é o que sustenta A Regra da Dependência da Clean Architecture: o código-fonte só pode apontar para dentro, em direção às políticas de mais alto nível.

Toda abstração tem custo: mais tipos, mais indireção, navegação de código mais difícil. Aplicar DIP a tudo produz “interface para cada classe” sem ganho real.

A regra de negócio depende diretamente da implementação concreta de infraestrutura.

@Service
public class CadastroUsuario {
private final MySqlUsuarioDao dao = new MySqlUsuarioDao();
private final SmtpEmailSender emailSender = new SmtpEmailSender();
public void cadastrar(String nome, String email) {
dao.inserir(nome, email);
emailSender.enviar(email, "Bem-vindo, " + nome);
}
}
public class MySqlUsuarioDao {
public void inserir(String nome, String email) {
System.out.println("INSERT no MySQL: " + nome);
}
}
public class SmtpEmailSender {
public void enviar(String destino, String corpo) {
System.out.println("SMTP para " + destino);
}
}

CadastroUsuario (alto nível) instancia e depende de detalhes concretos. Trocar MySQL ou testar sem banco é inviável.

Depois — dependência invertida sobre abstrações do domínio

Seção intitulada “Depois — dependência invertida sobre abstrações do domínio”

As interfaces pertencem ao domínio e falam a linguagem do negócio. A infraestrutura implementa e é injetada pelo Spring.

public record Usuario(String nome, String email) {}
public interface UsuarioRepository {
void registrar(Usuario usuario);
}
public interface NotificadorBoasVindas {
void enviarBoasVindas(Usuario usuario);
}
@Service
public class CadastroUsuario {
private final UsuarioRepository repository;
private final NotificadorBoasVindas notificador;
public CadastroUsuario(UsuarioRepository repository, NotificadorBoasVindas notificador) {
this.repository = repository;
this.notificador = notificador;
}
public void cadastrar(String nome, String email) {
Usuario usuario = new Usuario(nome, email);
repository.registrar(usuario);
notificador.enviarBoasVindas(usuario);
}
}
@Repository
public class JdbcUsuarioRepository implements UsuarioRepository {
private final JdbcTemplate jdbcTemplate;
public JdbcUsuarioRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void registrar(Usuario usuario) {
jdbcTemplate.update("INSERT INTO usuario (nome, email) VALUES (?, ?)",
usuario.nome(), usuario.email());
}
}
@Component
public class EmailNotificadorBoasVindas implements NotificadorBoasVindas {
private final JavaMailSender mailSender;
public EmailNotificadorBoasVindas(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
@Override
public void enviarBoasVindas(Usuario usuario) {
SimpleMailMessage msg = new SimpleMailMessage();
msg.setTo(usuario.email());
msg.setSubject("Bem-vindo");
msg.setText("Bem-vindo, " + usuario.nome());
mailSender.send(msg);
}
}

CadastroUsuario não conhece MySQL nem SMTP. Depende só de UsuarioRepository e NotificadorBoasVindas, interfaces do domínio. Em teste, injeta-se um dublê; em produção, o Spring injeta os adaptadores concretos.

classDiagram
    class CadastroUsuario {
        +cadastrar(String, String)
    }
    class UsuarioRepository {
        <<interface>>
        +registrar(Usuario)
    }
    class NotificadorBoasVindas {
        <<interface>>
        +enviarBoasVindas(Usuario)
    }
    class JdbcUsuarioRepository {
        +registrar(Usuario)
    }
    class EmailNotificadorBoasVindas {
        +enviarBoasVindas(Usuario)
    }

    CadastroUsuario --> UsuarioRepository
    CadastroUsuario --> NotificadorBoasVindas
    UsuarioRepository <|.. JdbcUsuarioRepository
    NotificadorBoasVindas <|.. EmailNotificadorBoasVindas

As setas de implementação (<|..) apontam do detalhe para a abstração: é a inversão. A infraestrutura depende do contrato do domínio, nunca o contrário.

  1. Qual é a “inversão” no Princípio da Inversão de Dependência?
Resposta

A inversão do sentido convencional da dependência: em vez de o alto nível depender do baixo nível, ambos dependem de uma abstração definida junto ao alto nível. A seta de dependência de código passa a apontar do detalhe (infra) para a política (domínio).

  1. Qual a diferença entre DIP e injeção de dependência (o que o Spring faz)?
Resposta

DIP é o princípio de depender de abstrações. Injeção de dependência é um mecanismo (IoC) que fornece implementações de fora. É possível usar injeção e mesmo assim violar o DIP, ao injetar uma classe concreta em vez de uma abstração de propriedade do domínio.

  1. Por que “extrair a interface a partir da classe de banco” não satisfaz o DIP?
Resposta

Porque a abstração continua falando o vocabulário da tecnologia e reflete o que a infraestrutura oferece, não o que a política precisa. Não houve inversão — apenas uma camada de indireção. A interface deve ser de propriedade do domínio e expressar suas necessidades.

  1. Como o DIP se relaciona com a Arquitetura Hexagonal e a Clean Architecture?
Resposta

O DIP é a base teórica dessas arquiteturas: os Ports são abstrações de propriedade do domínio e os Adapters são os detalhes que as implementam. A Regra da Dependência (dependências apontam para dentro) é o DIP aplicado às fronteiras do sistema.