Resumo
Injeção de Dependência (Dependency Injection, DI) é a prática de fornecer as dependências de uma classe de fora — em vez de deixá-la criar suas próprias. Quando PedidoService precisa de PedidoRepository, alguém de fora passa o repositório para ele. O Service não sabe qual implementação concreta vai receber — e não precisa saber.
Essa inversão de controle é o que torna o código desacoplado, testável e flexível para mudança.
O problema
Sem injeção de dependência, as classes criam suas próprias dependências:
class PedidoService {
private repo = new PedidoRepositoryPostgres(); // hardcoded
private mailer = new SendGridMailer(); // hardcoded
async confirmar(id: string) {
const pedido = await this.repo.findById(id);
pedido.confirmar();
await this.repo.save(pedido);
await this.mailer.enviar(pedido.email, "Pedido confirmado");
}
}Problemas concretos:
- Para testar
confirmar, você precisa de PostgreSQL e SendGrid configurados - Trocar o banco ou o provedor de e-mail exige modificar
PedidoService - Não é possível reutilizar o service com implementações diferentes
Antes e depois
class RelatórioService {
// Acoplado à implementação concreta
private exportador = new ExportadorPDF();
async gerar(dados: Dados) {
const relatorio = this.processar(dados);
return this.exportador.exportar(relatorio);
}
}
// Não tem como gerar CSV ou Excel sem alterar a classeinterface Exportador {
exportar(relatorio: Relatorio): Promise<Buffer>;
}
class RelatorioService {
constructor(private exportador: Exportador) {}
async gerar(dados: Dados) {
const relatorio = this.processar(dados);
return this.exportador.exportar(relatorio);
}
}
// Em produção:
new RelatorioService(new ExportadorPDF());
// Em teste:
new RelatorioService(new ExportadorMemoria());
// Para nova funcionalidade:
new RelatorioService(new ExportadorCSV());Formas de injetar dependências
Via construtor (recomendado):
class PedidoService {
constructor(
private repo: PedidoRepository,
private mailer: Mailer
) {}
}As dependências são obrigatórias, visíveis na assinatura e imutáveis após a criação. É a forma mais explícita e testável.
Via setter (casos específicos):
class RelatorioService {
private exportador!: Exportador;
setExportador(e: Exportador) {
this.exportador = e;
}
}Útil quando a dependência é opcional ou pode mudar em tempo de execução. Menos comum.
Via container de DI (frameworks): Frameworks como NestJS, InversifyJS e TSyringe automatizam a criação e injeção. Você declara o que precisa, o container resolve as dependências:
@Injectable()
class PedidoService {
constructor(private repo: PedidoRepository) {} // NestJS injeta automaticamente
}Quando usar
Injeção de dependência é útil sempre que:
- A classe precisa de colaboradores que podem variar (banco, API externa, serviço de e-mail)
- Você quer testar a classe sem subir infraestrutura real
- Diferentes ambientes precisam de implementações diferentes (ex: e-mail real em produção, e-mail em memória em teste)
Erros comuns
Injetar o container inteiro:
// Errado: a classe pode puxar qualquer coisa do container
constructor(private container: Container) {
this.repo = container.get(PedidoRepository);
}
// Correto: declare explicitamente o que precisa
constructor(private repo: PedidoRepository) {}Depender de classes concretas, não de interfaces:
// Errado: acoplado à implementação
constructor(private repo: PedidoRepositoryPostgres) {}
// Correto: depende da abstração
constructor(private repo: PedidoRepository) {} // interfaceCriar dependências no meio do método:
async confirmar(id: string) {
const mailer = new Mailer(); // dependência criada na hora
// ...
}Dependências criadas dentro de métodos são invisíveis, não testáveis e impossíveis de substituir.
Conceitos relacionados
- Repository Pattern — exemplo clássico de uma dependência que se injeta por interface
- Service Layer — beneficiária direta da injeção: recebe repositórios e serviços sem acoplamento
- Acoplamento — o problema que a Injeção de Dependência resolve ao remover dependências diretas entre implementações