Pular para o conteúdo

Boas práticas e padrões · Princípios SOLID · 6 min de leitura

D — Princípio da inversão de dependência (DIP)

Regras de negócio não dependem de detalhes (banco, HTTP, SDK): os dois dependem de abstrações definidas pelo negócio.

DomínioCadastrarCliente → ClienteRepository: dependedependePostgresRepo → ClienteRepository: implementaimplementaFakeRepo → ClienteRepository: implementaimplementaCadastrarCliente — regra de negócioCadastrarClienteregra de negócioClienteRepository — interface do domínioClienteRepositoryinterface do domínioPostgresRepo — detalhePostgresRepodetalheFakeRepo — nos testesFakeReponos testes

Arraste para o lado para ver o diagrama inteiro.

As setas de dependência apontam para o domínio: o banco é que se adapta à interface do negócio, e não o contrário.

As duas regras

  1. Módulos de alto nível não dependem de módulos de baixo nível; ambos dependem de abstrações.
  2. Abstrações não dependem de detalhes; detalhes dependem de abstrações.

A "inversão" é que a interface pertence a quem usa (o domínio), não a quem implementa (a infraestrutura).

DIP não é DI

Injeção de dependência é a técnica de entregar as dependências por construtor (ou contêiner). Inversão de dependência é a direção das setas entre módulos. A injeção é o jeito mais comum de aplicar a inversão.

Ganho concreto

Trocar Postgres por Room, Retrofit por Ktor ou rodar o caso de uso inteiro em teste unitário com um repositório em memória — sem mocks complexos.

No código: antes e depois

✗ AntesKotlin
class CadastrarCliente {
  private val db = PostgresClient("jdbc:postgresql://prod/db") // detalhe dentro da regra
  fun executar(c: Cliente) {
    require(c.email.contains("@"))
    db.insert("clientes", c)
  }
}
✓ DepoisKotlin
// domínio
interface ClienteRepository { suspend fun salvar(c: Cliente) }
class CadastrarCliente(private val repo: ClienteRepository) {
  suspend fun executar(c: Cliente) {
    require(c.email.contains("@")) { "e-mail inválido" }
    repo.salvar(c)
  }
}
// infraestrutura
class PostgresClienteRepository(private val db: PostgresClient) : ClienteRepository {
  override suspend fun salvar(c: Cliente) = db.insert("clientes", c)
}
// teste
class FakeRepo : ClienteRepository {
  val salvos = mutableListOf<Cliente>()
  override suspend fun salvar(c: Cliente) { salvos += c }
}

O caso de uso não sabe que Postgres existe. Em teste, FakeRepo verifica o comportamento em milissegundos.

Em resumo

Referências

Continue aprendendo

Todos os temas de boas práticas e padrões →