Aula 01 · 40 minutos

Inverter a seta: DI não é DIP

Aula 1 — diferencie injeção de dependência de inversão de dependência e aplique DIP em Go.

Resultado, pré-requisito e mapa

Ao fim, você conseguirá revisar um caso de uso Go e responder, com evidência no código: houve apenas injeção de dependência (DI) ou também inversão de dependência (DIP)? Você também conseguirá extrair um contrato mínimo, montar a aplicação na borda e testar a regra sem banco.

Pré-requisito: ler structs, métodos, interfaces e construtores em Go. Esta aula não ensina SQL, HTTP ou um framework de DI; ela estabelece o modelo mental que as próximas aulas aplicam a contratos, composição, testes e refatoração.

  1. Veremos o acoplamento que existe mesmo com um construtor.
  2. Separaremos a direção estática do código da chamada que ocorre em execução.
  3. Montaremos a solução e testaremos sua consequência observável.
  4. Decidiremos quando não criar uma interface.

O problema aparece antes do teste

Imagine que checkout contém a política de criar um pedido. A aplicação já usa injeção por construtor: alguém cria o repositório e o entrega ao serviço. Ainda assim, a regra conhece PostgreSQL pelo nome.

// package checkout
type Service struct {
    orders *postgres.OrderRepository
}

func NewService(orders *postgres.OrderRepository) *Service {
    return &Service{orders: orders}
}

DI responde somente como o objeto recebe um colaborador: por construtor, por exemplo. Ela já evita que Service construa a conexão sozinho. Mas o campo, o parâmetro e o import ainda apontam de checkout para postgres. Se o detalhe mudar, a regra precisa mudar junto; se o teste quiser outra implementação, ele ainda precisa conhecer a infraestrutura.

Dependência estática inicial: checkout aponta para postgres

DEPENDÊNCIA ESTÁTICA (compilação) checkout ─────────────────────────────→ postgres regra de negócio detalhe de infraestrutura

Consequência real: o problema não é que PostgreSQL seja ruim. É a política de negócio carregar uma decisão que pertence à borda da aplicação: qual tecnologia persiste o pedido.

DI e DIP respondem perguntas diferentes

DI pode realizar DIP, mas não o garante. Uma frase segura para revisão ou entrevista é: "Injetar um concreto separa criação e uso; depender de uma abstração definida pelo consumidor separa a política do detalhe."

A inversão: o consumidor declara sua necessidade

O serviço não precisa saber como um pedido é salvo; precisa apenas solicitar que ele seja salvo. Essa necessidade vira um contrato pequeno no pacote que a consome.

// package checkout
type OrderSaver interface {
    Save(ctx context.Context, order Order) error
}

type Service struct { orders OrderSaver }

func NewService(orders OrderSaver) *Service {
    return &Service{orders: orders}
}

Em Go, *postgres.OrderRepository satisfaz OrderSaver implicitamente se tiver o método Save com a mesma assinatura. O adaptador não importa checkout para declarar que implementa a interface; o compilador verifica a compatibilidade quando ele é passado ao construtor.

Dependência estática depois da inversão

DEPENDÊNCIA ESTÁTICA (compilação) checkout ──→ OrderSaver ←── postgres política contrato detalhe

main importa checkout e postgres para conectá-los.

É essa seta do código-fonte que se inverte. A direção da chamada em execução continua natural: a regra chama Save; o valor concreto que recebe a chamada pode ser PostgreSQL em produção.

Quando o código executa — e por quê

  1. Compilação: Go verifica que o valor entregue a NewService satisfaz OrderSaver. Isso protege a forma do contrato, não a conectividade com o banco.
  2. Inicialização: main, o ponto de composição, abre recursos, cria o repositório e escolhe injetá-lo. Ele pode conhecer detalhes porque sua função é montar a aplicação.
  3. Operação: o adaptador de entrada chama o caso de uso; Service valida a regra e chama Save pelo contrato. main não participa dessa chamada.
  4. Teste unitário: o teste cria um fake e o injeta. A mesma regra executa sem processo de banco, rede, schema ou dados compartilhados.
Use os controles do diagrama para comparar as composições e seguir as relações.

Abra o diagrama e compare as visões "Composição em produção" e "Composição no teste". Em ambas, checkout depende de OrderSaver; o que muda é o valor escolhido na borda.

Implementação, composição e evidência de teste

O repositório existente não precisa ser reescrito se já oferece o método esperado; só a forma como a regra o enxerga muda.

// package main — composição: cria detalhes uma vez, na inicialização.
repo := postgres.NewOrderRepository(db)
service := checkout.NewService(repo)

// package checkout_test — teste da regra, sem PostgreSQL.
type fakeOrderSaver struct { saved checkout.Order }

func (f *fakeOrderSaver) Save(_ context.Context, order checkout.Order) error {
    f.saved = order
    return nil
}

func TestCreatePersistsTheOrder(t *testing.T) {
    fake := &fakeOrderSaver{}
    service := checkout.NewService(fake)
    // Execute Create e compare fake.saved com o pedido esperado.
}

A asserção deve provar um efeito da regra — o pedido entregue ao contrato ou a reação a um erro controlado. SQL, migrations e conexão pertencem a testes de integração do adaptador PostgreSQL.

Alternativas que parecem boas, mas falham

Interface no pacote de infraestrutura

postgres.Repository com Save, Find, Delete e outros métodos descreve a capacidade do banco, não a necessidade de checkout. Ela transforma detalhes em API para todo consumidor e força contratos maiores que o uso real.

Interface criada antes de um consumidor

Uma interface para cada struct "por padrão" acrescenta indireção e nomes, sem remover um acoplamento demonstrável. Em Go, comece por tipos concretos e extraia o contrato quando surgir uma borda, variação ou teste com necessidade real.

Service locator ou banco criado dentro do serviço

Essas abordagens escondem a colaboração. O construtor deixa de declarar o que a regra precisa, a inicialização se espalha e o teste precisa de caminhos especiais. A composição explícita em main é mais fácil de ler, revisar e encerrar.

Segurança: uma abstração não autoriza uma operação

OrderSaver reduz acoplamento; ele não prova que alguém pode salvar um pedido nem que o pedido é válido. Em uma aplicação real, o adaptador de entrada valida a entrada e obtém a identidade de uma fonte confiável; a regra valida transições de negócio e escopo; o adaptador de persistência usa consultas parametrizadas e retorna erros tratados sem vazar detalhes internos.

Não coloque credenciais, SQL concatenado ou decisões de autorização dentro do exemplo de contrato. Esses detalhes devem ser implementados e verificados nas bordas corretas. DIP melhora a testabilidade desses controles, mas não os substitui.

Recupere da memória

Em Go, quem deve declarar a interface mínima para salvar um pedido quando somente o checkout precisa desse comportamento?

Feche o modelo mental

DI entrega uma colaboração. DIP faz a política depender de um contrato definido por sua necessidade, enquanto os detalhes se adaptam a ele. Use uma interface pequena quando ela isola uma fronteira real; mantenha um concreto quando não há variação, borda ou teste que justifique a abstração.

Próxima: Aula 02 — O contrato do consumidor.

Referências para aprofundar

A recomendação de declarar interfaces no consumidor e não antecipá-las apenas para mocking está no Go Code Review Comments — Interfaces. A implementação implícita está na especificação da linguagem Go.

Para a formulação do princípio, leia The Dependency Inversion Principle, de Robert C. Martin. Para DI como separação entre configuração e uso, consulte Inversion of Control Containers and the Dependency Injection pattern, de Martin Fowler.

Guia de solução do exercício

Fluxo de execução: O construtor recebe uma dependência na inicialização; durante a operação, a regra chama Save. A pergunta é quem possui o contrato dessa chamada.

Abordagem inadequada: Apenas injetar um tipo de infraestrutura continua acoplando checkout ao pacote postgres.

// package checkout
type Service struct { orders *postgres.OrderRepository }

Por que falha e como corrigir: O teste precisa conhecer a infraestrutura e trocar PostgreSQL exige editar a regra. DI ocorreu, mas a direção de dependência não mudou.

Abordagem correta: Faça checkout possuir um contrato do comportamento que consome; PostgreSQL o satisfaz sem importação inversa.

// package checkout
type OrderSaver interface { Save(Order) error }
type Service struct { orders OrderSaver }

Raciocínio, trade-offs e limites: Comece pelo uso real: o serviço só precisa salvar. Não acrescente Find, Delete ou transações sem uma necessidade do consumidor. A solução é apropriada quando a regra precisa isolar uma borda; um valor simples sem variação pode continuar concreto.

Abra o diagrama relacionado e compare o fluxo.

Pergunta de mercado e entrevista

1. Qual a diferença entre DI e DIP?

Como responder: DI é o mecanismo de fornecer uma dependência. DIP define a direção: a regra depende de um contrato próprio, e o detalhe se adapta a ele. Feche com checkout → OrderSaver ← PostgreSQL.

2. Por que injeção por construtor não basta?

Como responder: Porque receber *postgres.OrderRepository ainda faz checkout conhecer o detalhe. Mostre que trocar o campo para OrderSaver remove essa importação da regra.

3. Qual é o ganho de negócio do DIP?

Como responder: Explique que o ganho é testar decisões sem infraestrutura e trocar detalhes localmente. Não venda interfaces como objetivo: elas são o meio para proteger a regra.

Pratique mais 9 questões de recuperação

Responda sem consultar a aula. O feedback é imediato; volte ao trecho relevante antes de tentar de novo.

DI entrega uma dependência; DIP define:

Em Go, uma interface é satisfeita quando:

Quem deve possuir uma interface pequena?

Onde a implementação concreta é escolhida?

Para testar uma regra sem banco, injete:

Uma interface deve conter:

O detalhe PostgreSQL deve depender de:

Qual sinal pede uma interface?

Após a refatoração, a regra não deve importar: