5 min read

Inversão de Dependências

Table of Contents

Inversão de Dependências: Como Deixar Seu Código Mais Flexível

O que é Inversão de Dependências?

Imagine que você tem uma classe que precisa salvar dados. Em vez de ela “conhecer” diretamente qual banco de dados usar (MySQL, PostgreSQL, MongoDB), ela simplesmente define “preciso de algo que saiba salvar dados”. Isso é Inversão de Dependências!

Em termos simples: Classes importantes não devem depender de detalhes específicos, mas sim de contratos (interfaces) que definem o que precisa ser feito.

Por que isso é importante?

Flexibilidade

  • Trocar MySQL por PostgreSQL? Fácil!
  • Mudar para MongoDB? Sem problemas!
  • Testar com dados em memória? Simples!

Manutenibilidade

  • Mudanças em um banco não quebram o resto do código
  • Cada parte tem sua responsabilidade bem definida
  • Bugs ficam isolados em suas respectivas camadas

Exemplo Prático: Sistema de Usuários

Vamos ver como isso funciona na prática com um sistema que gerencia usuários:

// 1. Definimos o CONTRATO (o que precisa ser feito)
interface IUserRepository {
  save(user: User): Promise<void>;
  findById(id: string): Promise<User | null>;
  findByEmail(email: string): Promise<User | null>;
}

// 2. Implementação para PostgreSQL
class PostgreSQLUserRepository implements IUserRepository {
  async save(user: User): Promise<void> {
    // Código específico do PostgreSQL
    console.log(`Salvando usuário no PostgreSQL: ${user.name}`);
  }
  
  async findById(id: string): Promise<User | null> {
    // Busca no PostgreSQL
    return null; // Exemplo simplificado
  }
  
  async findByEmail(email: string): Promise<User | null> {
    // Busca no PostgreSQL
    return null; // Exemplo simplificado
  }
}

// 3. Implementação para MongoDB
class MongoUserRepository implements IUserRepository {
  async save(user: User): Promise<void> {
    // Código específico do MongoDB
    console.log(`Salvando usuário no MongoDB: ${user.name}`);
  }
  
  async findById(id: string): Promise<User | null> {
    // Busca no MongoDB
    return null; // Exemplo simplificado
  }
  
  async findByEmail(email: string): Promise<User | null> {
    // Busca no MongoDB
    return null; // Exemplo simplificado
  }
}

// 4. Implementação para testes (em memória)
class InMemoryUserRepository implements IUserRepository {
  private users: User[] = [];
  
  async save(user: User): Promise<void> {
    this.users.push(user);
    console.log(`Usuário salvo em memória: ${user.name}`);
  }
  
  async findById(id: string): Promise<User | null> {
    return this.users.find(u => u.id === id) || null;
  }
  
  async findByEmail(email: string): Promise<User | null> {
    return this.users.find(u => u.email === email) || null;
  }
}

// 5. Classe de serviço que USA o repositório
class UserService {
  constructor(private userRepository: IUserRepository) {}
  
  async createUser(name: string, email: string): Promise<void> {
    // Validações de negócio
    if (!email.includes('@')) {
      throw new Error('Email inválido');
    }
    
    // Verifica se já existe
    const existingUser = await this.userRepository.findByEmail(email);
    if (existingUser) {
      throw new Error('Usuário já existe');
    }
    
    // Cria e salva o usuário
    const user = new User(name, email);
    await this.userRepository.save(user);
  }
}

// 6. Classe User (modelo)
class User {
  public id: string;
  
  constructor(public name: string, public email: string) {
    this.id = Math.random().toString(36);
  }
}

A Mágica da Troca de Banco

Veja como é fácil trocar o banco de dados:

// Usando PostgreSQL
const postgresRepo = new PostgreSQLUserRepository();
const userService1 = new UserService(postgresRepo);

// Mudou para MongoDB? Só trocar uma linha!
const mongoRepo = new MongoUserRepository();
const userService2 = new UserService(mongoRepo);

// Nos testes? Usa dados em memória
const inMemoryRepo = new InMemoryUserRepository();
const userService3 = new UserService(inMemoryRepo);

// O UserService funciona igual com qualquer implementação!
await userService1.createUser('João', 'joao@email.com'); // PostgreSQL
await userService2.createUser('Maria', 'maria@email.com'); // MongoDB
await userService3.createUser('Pedro', 'pedro@email.com'); // Memória

Vantagens na Prática

1. Mudança de Banco sem Dor de Cabeça

Decidiu migrar de PostgreSQL para MongoDB? Você só precisa:

  • Criar a nova implementação MongoUserRepository
  • Trocar na configuração da aplicação
  • Pronto! O resto do código continua funcionando

2. Testes Mais Fáceis

// Teste unitário super simples
test('deve criar usuário com sucesso', async () => {
  const fakeRepo = new InMemoryUserRepository();
  const userService = new UserService(fakeRepo);
  
  await userService.createUser('Teste', 'teste@email.com');
  
  const user = await fakeRepo.findByEmail('teste@email.com');
  expect(user).toBeDefined();
  expect(user?.name).toBe('Teste');
});

3. Desenvolvimento Paralelo

  • Time A desenvolve a lógica de negócio
  • Time B desenvolve a integração com banco
  • Ambos trabalham sem depender um do outro

4. Ambientes Diferentes

  • Desenvolvimento: banco em memória (rápido)
  • Testes: banco de teste isolado
  • Produção: banco real com alta performance

Dicas Importantes

  1. Mantenha as interfaces simples - Só inclua métodos realmente necessários
  2. Pense no contrato primeiro - Defina a interface antes das implementações
  3. Use injeção de dependências - Passe as dependências no construtor
  4. Teste com implementações fake - Facilita muito os testes unitários

Conclusão

A Inversão de Dependências não é apenas um conceito teórico - é uma ferramenta prática que torna seu código mais flexível, testável e fácil de manter.

Com ela, trocar de banco de dados deixa de ser um pesadelo e vira uma tarefa simples de configuração. Seu código fica preparado para mudanças futuras e seus testes ficam mais rápidos e confiáveis.

Lembre-se: dependa de abstrações, não de implementações concretas. Seu futuro eu vai agradecer! 🚀