Casos de uso importantes costumam depender de persistência, filas, relógio e comunicação externa. Quando importam esses detalhes diretamente, a política de negócio passa a mudar junto com a infraestrutura.
O Dependency Inversion Principle, ou DIP, reorganiza essa relação. Módulos de alto nível e detalhes de baixo nível dependem de abstrações que representam as necessidades do negócio.
O problema que o princípio resolve
Considere um cadastro que verifica duplicidade, salva o cliente e publica um evento. Se a classe cria um repositório de arquivo e chama uma fila HTTP diretamente, sua regra só funciona com essas escolhas.
Testes precisam tocar disco e rede. Trocar a fila altera o caso de uso. A direção das dependências coloca a política abaixo dos detalhes.
Inversão não é injeção
Injeção de dependência é uma técnica para entregar colaboradores a um objeto. DIP é o princípio que decide a direção entre políticas e detalhes.
Receber uma classe concreta pelo construtor melhora a criação, mas não inverte a relação se o caso de uso continuar tipado por aquela infraestrutura. A abstração deve ser definida a partir do que a regra precisa.
Exemplo ruim
import { appendFile, readFile } from "node:fs/promises";
type Customer = {
id: string;
email: string;
createdAt: string;
};
class RegisterCustomer {
async execute(id: string, email: string): Promise<Customer> {
const filePath = "data/customers.json";
const content = await readFile(filePath, "utf8").catch(() => "[]");
const customers = JSON.parse(content) as Customer[];
if (customers.some((customer) => customer.email === email)) {
throw new Error("E-mail já cadastrado");
}
const customer = {
id,
email,
createdAt: new Date().toISOString()
};
await appendFile(filePath, `${JSON.stringify(customer)}\n`, "utf8");
await fetch("https://queue.internal/events", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ type: "customer.registered", customer })
});
return customer;
}
}Por que o exemplo é ruim?
A regra conhece formato de arquivo, relógio global, serialização e endpoint da fila. Além do acoplamento, há uma inconsistência: a leitura espera um array JSON, mas a escrita adiciona linhas independentes.
Validar duplicidade exige preparar arquivo. Simular o tempo requer alterar recursos globais. Uma indisponibilidade da fila ocorre depois da persistência sem uma política explícita.
Refatoração
Definimos portas com o vocabulário do caso de uso:
interface CustomerRepository {
findByEmail(email: string): Promise<Customer | null>;
save(customer: Customer): Promise<void>;
}
interface EventPublisher {
publish(event: {
type: "customer.registered";
customerId: string;
occurredAt: string;
}): Promise<void>;
}
interface Clock {
now(): Date;
}Esses contratos não mencionam arquivos, HTTP ou bibliotecas. Eles expressam operações necessárias à política.
Exemplo bom
class RegisterCustomer {
constructor(
private readonly customers: CustomerRepository,
private readonly events: EventPublisher,
private readonly clock: Clock
) {}
async execute(id: string, email: string): Promise<Customer> {
const normalizedEmail = email.trim().toLowerCase();
const existingCustomer = await this.customers.findByEmail(normalizedEmail);
if (existingCustomer) {
throw new Error("E-mail já cadastrado");
}
const customer = {
id,
email: normalizedEmail,
createdAt: this.clock.now().toISOString()
};
await this.customers.save(customer);
await this.events.publish({
type: "customer.registered",
customerId: customer.id,
occurredAt: customer.createdAt
});
return customer;
}
}O caso de uso continua dono da ordem e das regras. Repositório, publicador e relógio oferecem detalhes por trás dos contratos.
Detalhes implementam as portas
Uma implementação em memória é útil para testes e desenvolvimento:
class MemoryCustomerRepository implements CustomerRepository {
private readonly customers = new Map<string, Customer>();
async findByEmail(email: string): Promise<Customer | null> {
return this.customers.get(email) ?? null;
}
async save(customer: Customer): Promise<void> {
this.customers.set(customer.email, customer);
}
}
class SystemClock implements Clock {
now(): Date {
return new Date();
}
}Uma implementação com banco ou fila dependeria das APIs escolhidas, mas o caso de uso não precisaria importá-las.
A composição fica na borda
Algum lugar ainda precisa conhecer classes concretas. Esse ponto é a composição da aplicação:
const customers = new MemoryCustomerRepository();
const events: EventPublisher = {
async publish(event) {
console.log(JSON.stringify(event));
}
};
const registerCustomer = new RegisterCustomer(
customers,
events,
new SystemClock()
);DIP não elimina dependências; ele as move. O núcleo conhece abstrações estáveis, enquanto a borda conecta detalhes concretos.
Testando a política
Um relógio controlado e um publicador em memória tornam o comportamento determinístico sem alterar globais:
const fixedClock: Clock = {
now: () => new Date("2027-03-25T12:00:00.000Z")
};
const publishedEvents: Array<{ type: string; customerId: string }> = [];
const events: EventPublisher = {
async publish(event) {
publishedEvents.push(event);
}
};O teste pode verificar normalização, duplicidade, data e evento como regras do caso de uso. Infraestrutura real recebe seus próprios testes de integração.
Benefícios
- regras de negócio independentes de infraestrutura;
- testes rápidos e determinísticos;
- troca de detalhes com impacto local;
- necessidades do domínio explícitas em contratos;
- composição centralizada;
- fronteira clara entre política e mecanismo.
Quando aplicar?
Sempre pergunte: "As regras de alto nível dependem diretamente de detalhes de baixo nível que dificultam testes ou mudanças?" Se a resposta for "sim", é hora de aplicar o DIP.
Fechamento
Dependency Inversion Principle mantém políticas importantes acima dos mecanismos que as executam. O núcleo define do que precisa, e a infraestrutura adapta suas ferramentas a esse contrato.
Aplicado nas fronteiras certas, o princípio reduz o custo de testar e substituir detalhes. Aplicado em toda função, apenas troca acoplamento por cerimônia.
Série SOLID
Este artigo faz parte de uma série de artigos: