PrincípioIntermediário6 min de leitura9 seções1996, Data aproximada

DIP — Inversão de Dependência

Módulos de alto nível não devem depender de módulos de baixo nível; ambos devem depender de abstrações — e a abstração pertence a quem a usa, não a quem a implementa.

Em 10 segundos

O caso de uso declara `RepositorioPedidos` do jeito que precisa; o Postgres implementa esse contrato. A seta de dependência passa a apontar da infraestrutura para o domínio — e o domínio vira testável sem subir nada.

Antes de ter nome

A inversão do fluxo de dependência já era o coração do 'Hollywood Principle' dos frameworks ('não nos chame, nós chamamos você') e de padrões como o Observer.

  • #solid
  • #abstracao
  • #inversao
  • #arquitetura
Analogia

O parafuso e a rosca padronizada

A indústria não fabrica uma furadeira para cada marca de parafuso. Ela definiu roscas padrão, e os fabricantes de parafuso se adaptam a elas. Quem tem o problema a resolver definiu o encaixe; quem fornece a peça obedece. Se fosse ao contrário, cada fornecedor novo obrigaria a trocar a ferramenta.

01Conceito

O problema

Sem inversão, a regra de negócio importa o driver do banco, o SDK do provedor de e-mail e o cliente HTTP. Ela deixa de ser legível como negócio e passa a ser um roteiro de integrações.

Testar exige infraestrutura, mudar de fornecedor exige tocar no domínio, e o código mais valioso do sistema fica refém do mais substituível.

A parte que costuma passar despercebida é a segunda metade do princípio: a abstração pertence a quem a usa. Criar uma interface e colocá-la no pacote de infraestrutura não inverte nada — a dependência continua apontando para fora. A interface precisa viver junto do domínio, escrita no vocabulário dele.

Isso muda o desenho do contrato. Um repositório definido pelo domínio fala de salvar(pedido) e buscarPorCliente(id) — não de conexões, transações ou SQL. Se a interface parece a API do banco, ela foi escrita do lado errado.

Convém separar do que costuma ser confundido: injeção de dependência é uma técnica de fornecer colaboradores, e inversão de controle é sobre quem chama quem. Nenhuma das duas garante DIP. Dá para injetar uma classe concreta de infraestrutura e continuar com a dependência apontando na direção errada — o princípio é sobre a direção da seta, não sobre o mecanismo.

02Conceito

O princípio em escala de arquitetura

DIP é o único dos cinco princípios SOLID que se aplica igual da classe até a arquitetura inteira — é a regra que sustenta hexagonal, ports and adapters e clean architecture.

Em todas essas arquiteturas, a regra é a mesma: as dependências apontam para dentro, em direção ao domínio, e o núcleo não conhece nada do que está fora. As 'portas' são exatamente as abstrações declaradas pelo domínio; os 'adaptadores' são as implementações de infraestrutura.

O ganho prático mais imediato é a testabilidade. Com as portas no lugar, a suíte do domínio roda em milissegundos com implementações em memória, sem docker, sem migração, sem rede — e isso muda o ritmo de trabalho mais do que qualquer outra decisão de design.

O ganho de longo prazo é a substituibilidade real. Trocar de provedor de e-mail ou de banco deixa de ser projeto e passa a ser escrever um adaptador novo. Vale notar que essa troca acontece menos do que se imagina — o que quase sempre se paga é o primeiro ganho, não o segundo.

Sem o padrão × com o padrão

Dependência invertida

  • CriarPedido declara e depende de RepositorioPedidos
  • o teste injeta um repositório em memória
  • trocar de banco é escrever outro adaptador
  • o domínio não importa nada de infraestrutura

O custo é uma interface a mais por porta e um lugar onde tudo é montado (a composição). Aplicado sem critério, produz interfaces com um implementador só e indireção sem ganho.

A seta em tempo de compilação passa a apontar no sentido contrário ao do fluxo de execução: em runtime o caso de uso chama o banco, mas no código quem depende de quem se inverteu.
Como se organiza
  • Domínio / caso de usonão importa nada de fora

    • interface RepositorioPedidosa porta — declarada aqui dentro

  • Infraestruturaconhece o domínio, e não o contrário

    • PostgresPedidosadaptador de produção

    • RepoEmMemoriaadaptador de teste

A interface fica dentro da fronteira do domínio — esse é o detalhe que faz a inversão existir. Colocá-la no pacote de infraestrutura mantém a dependência apontando para fora.
03Anatomia

Navegue pelas camadas

Camada 1/3Política (alto nível)

Camada 1

Política (alto nível)

as regras, sem saber de infraestrutura

Contém o que dá valor ao sistema e muda por motivo de negócio. Não importa driver, framework nem SDK — se importar, a inversão não aconteceu.

No código

class CriarPedido { constructor(private repo: RepositorioPedidos) {} }

Se violar: um `import` de biblioteca de banco dentro do caso de uso já denuncia a dependência na direção errada.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

// ANTES: a regra de negocio depende do detalhe concreto
class PostgresPedidos {
  salvar(p: unknown): void { console.log("INSERT ..."); }
}
class CriarPedidoRuim {
  private repo = new PostgresPedidos(); // acoplado ao banco
  executar(dados: unknown): void {
    this.repo.salvar(dados);            // testar exige um Postgres
  }
}

// DEPOIS: a abstracao pertence a quem USA
// (declarada junto do caso de uso, no dominio)
interface RepositorioPedidos {
  salvar(p: Pedido): void;
}
interface Pedido {
  id: string;
  total: number;
}

class CriarPedido {
  // depende da abstracao; nao sabe que Postgres existe
  constructor(private repo: RepositorioPedidos) {}

  executar(p: Pedido): void {
    if (p.total <= 0) throw new Error("total inválido");
    this.repo.salvar(p);
  }
}

// A infra implementa o contrato do dominio — a seta se inverteu
class PostgresPedidosOk implements RepositorioPedidos {
  salvar(p: Pedido): void { console.log("INSERT INTO pedidos", p.id); }
}

// Em teste, sem banco nenhum
class RepoEmMemoria implements RepositorioPedidos {
  readonly salvos: Pedido[] = [];
  salvar(p: Pedido): void { this.salvos.push(p); }
}

const repo = new RepoEmMemoria();
new CriarPedido(repo).executar({ id: "1", total: 100 });
console.log(repo.salvos.length); // 1
06No mundo real

Casos de uso

Domínio testável sem banco

Cenário

A suíte de testes levava oito minutos porque cada caso subia um container de banco e aplicava migrações.

Como o padrão entra

Os casos de uso passam a depender de portas de repositório; os testes injetam implementações em memória. A suíte de domínio cai para segundos e roda a cada salvamento.

Trade-off: Implementações em memória divergem do banco real em detalhes que importam (unicidade, ordenação, transação). Sem uma suíte de contrato rodando contra as duas, os testes rápidos passam a validar um comportamento que a produção não tem.

Troca de provedor de pagamento

Cenário

A empresa precisa migrar de gateway por questão de taxa, e a integração atual está espalhada por vários serviços.

Como o padrão entra

Uma porta de cobrança declarada pelo domínio e dois adaptadores permitem migrar por etapas, inclusive rodando os dois em paralelo para comparar resultados.

Trade-off: A porta precisa ser genérica o bastante para os dois provedores sem virar o menor denominador comum — e recursos exclusivos de um deles ficam de fora ou vazam por opções específicas.

Relógio e aleatoriedade injetados

Cenário

Regras que dependem de data ('vence em 30 dias') e de sorteio ('amostragem de 10%') são impossíveis de testar de forma determinística.

Como o padrão entra

Tempo e aleatoriedade viram portas. Em produção usam o relógio e o gerador reais; em teste, valores fixos. Regras temporais passam a ter teste confiável.

Trade-off: É invasivo: praticamente todo objeto que precisa da data passa a recebê-la, e o código fica mais verboso em troca de determinismo nos testes.

Passo a passoComo inverter a seta
  1. 1

    Declarar a porta no domínio

    O caso de uso escreve a interface no vocabulário dele (`salvar(pedido)`), junto dele — não no pacote de infraestrutura.

  2. 2

    Depender só da porta

    O alto nível recebe a abstração por construtor. Nenhum `import` de driver, SDK ou framework entra no caso de uso.

  3. 3

    Implementar o adaptador fora

    Postgres, HTTP e fila implementam o contrato alheio. A seta de compilação aponta da infra para o domínio.

  4. 4

    Injetar na composição

    Produção pluga o adaptador real; teste pluga memória. A montagem é o único lugar que conhece os dois lados.

A interface no pacote errado

Compila, roda, passa no code review. E quebra depois.

Há uma interface, há injeção no construtor, o time diz que 'segue DIP' — mas o contrato vive na infra e fala SQL. A seta nunca inverteu; só ganhou um I no nome.

// "Temos DIP": existe uma interface... no pacote de infraestrutura.
// A seta de dependencia CONTINUA apontando para fora.
import type { PostgresPedidos } from "../infra/postgres-pedidos";

interface IPostgresPedidos {
  // espelho da classe concreta — nomes de SQL vazaram
  executarInsert(row: Record<string, unknown>): void;
}

class CriarPedido {
  constructor(private repo: IPostgresPedidos) {} // depende de "infra"

  executar(dados: Record<string, unknown>) {
    this.repo.executarInsert(dados); // dominio fala SQL disfarçado
  }
}

// Trocar de banco ainda toca o caso de uso: a abstração nasceu do lado errado.

O que quebra

  • No importO domínio importa de ../infra — a dependência ainda aponta para fora.
  • Ao trocar de bancoA interface carrega executarInsert e obriga o caso de uso a mudar junto com a tecnologia.
  • No testeO falso precisa imitar a API do Postgres, não a necessidade do domínio — e diverge em silêncio.

A saída: Mova a porta para o domínio e reescreva no vocabulário do negócio: salvar(p: Pedido). A infra implementa esse contrato; o caso de uso deixa de saber que Postgres existe.

08Cuidado

Armadilhas

  1. Interface no pacote errado

    Criar RepositorioPedidos dentro do módulo de infraestrutura é o erro mais comum e o mais invisível: o código ganha uma interface, parece seguir o princípio, e a dependência continua apontando para fora. A abstração precisa morar junto de quem a usa — é isso que inverte a seta.

  2. Abstrair tudo por precaução

    Uma interface para cada classe, com um implementador cada, transforma navegar no código numa caçada e não entrega substituibilidade nenhuma. Abstraia as fronteiras que realmente separam política de detalhe: banco, rede, relógio, fila. O resto pode ser concreto.

  3. Porta modelada pela tecnologia

    Se a interface tem executarQuery, abrirTransacao ou commit, ela é a API do banco com outro nome. O domínio passa a depender de um conceito de infraestrutura disfarçado, e trocar de tecnologia continua exigindo mexer nele — a inversão é só aparente.

09Decisão

Quando usar × quando evitar

Use quando

3
  • A regra de negócio precisa ser testável sem infraestrutura.
  • Detalhes externos podem mudar de provedor ou tecnologia.
  • O domínio deve ser legível sem conhecimento de framework.

Evite quando

3
  • Scripts e ferramentas pequenas.
  • Abstrações sem segunda implementação nem uso em teste.
  • Espelhar bibliotecas inteiras atrás de interfaces próprias.

Testar o que ficou

3 armadilhas de DIP — Inversão de Dependência viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • injeção de dependência (NestJS)O container injeta implementações via construtor, e suas classes dependem de interfaces — o detalhe concreto é escolhido de fora, não lá dentro.
  • React ContextO componente consome um contrato exposto pelo Provider sem saber quem o fornece; a implementação é injetada acima na árvore.
  • ports do HexagonalO domínio declara a interface do repositório e o adaptador de banco a implementa: a dependência aponta para a abstração, não para o Postgres.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Alto nível depende de abstração, não de SmtpClient.
constructor(private mail: PortaEmail) {}

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Uma abstração a mais entre o cliente e a implementação, com a injeção a conectar os dois
  • A indireção dificulta seguir do uso até o código concreto que roda de fato

Não vale se: a implementação é única e estável e não precisa ser trocada nem testada isoladamente — depender dela direto é mais simples.

Onde isto aparece

Continue por aqui