Aula 03 · 50–65 minutos
Compor na borda
Terceira aula: componha dependências na borda em Go.
Resultado e mapa da aula
Você aprenderá a reconhecer o composition root, explicar por que ele executa apenas na partida e montar a mesma regra com infraestrutura real ou um fake. O objetivo não é esconder criação de objetos; é deixar a escolha concreta explícita e localizada.
- Distinga a fase de inicialização da requisição em operação.
- Monte o grafo de dependências em
main. - Analise o ciclo de vida de conexões e o efeito sobre testes.
- Pratique e verifique a composição.
O fluxo tem um lugar para escolher detalhes
Quando o processo inicia, main cria configurações e adaptadores. Ele conhece PostgreSQL porque está na borda: seu papel é montar a aplicação, não decidir regras de pedido.
main → postgres.New → checkout.NewService — borda → detalhe → regraSe checkout criar o próprio repositório, cada caso de uso passa a conhecer a infraestrutura. A troca de banco, de configuração ou de ambiente então se espalha pelo código de negócio.
Quando o código executa
- Inicialização:
mainabre a conexão e constróiPostgresOrderRepository. - Composição: ele passa esse valor como
OrderSaverparacheckout.NewService. - Operação: o serviço recebe pedidos e chama o contrato.
- Encerramento: a borda fecha recursos que ela própria abriu.
Essa separação permite montar a mesma regra com PostgreSQL em produção e com um fake em testes.
Uma composição explícita
O construtor continua simples; a escolha concreta fica em main.
// package main
func main() {
db := openPostgres()
orders := postgres.NewOrderRepository(db)
service := checkout.NewService(orders)
runHTTPServer(service)
}
No diagrama, compare "Composição em produção" e "Composição no teste". A seta relevante parte da regra para o contrato, não para PostgreSQL.
Recupere da memória
Em qual pacote a implementação concreta deve ser escolhida?
Fonte primária recomendada
Martin Fowler — Inversion of Control Containers and the Dependency Injection pattern. Leia a distinção entre configuração e uso de dependências.
Próxima: testar a regra com um fake.
A inicialização é diferente da operação
Problema em trabalho real: Quando checkout cria a conexão ou o repositório, a política escolhe infraestrutura, configuração e ciclo de vida. A mesma regra então não consegue ser montada com um fake sem caminhos especiais.
Fluxo e momento de execução: Na partida, main lê configuração, abre o banco, constrói o adaptador e injeta-o em checkout. Depois que o servidor começa, o adaptador de entrada chama Service; main não participa da requisição. No desligamento, main fecha os recursos que abriu.
Abordagem insuficiente: Não esconda postgres.New dentro de checkout.NewService. A dependência deixa de ser explícita, a regra ganha import de infraestrutura e o teste passa a depender de um modo alternativo de construção.
Decisão recomendada: Receba OrderSaver no construtor e conecte o valor concreto uma vez em main. A composição explícita torna configuração, ownership e desligamento auditáveis. Um contêiner de DI só é justificável quando a montagem real se torna mais complexa que sua indireção.
Como verificar na prática: Crie uma função de composição que recebe uma configuração de teste e confira que o serviço pode ser construído com um fake. Em produção, verifique falhas ao abrir o banco antes de iniciar o servidor e use defer/encerramento coordenado para liberar recursos.
Limite de segurança: Credenciais e DSNs pertencem à configuração protegida da borda, nunca ao pacote checkout nem ao teste unitário. Não registre segredos; valide a configuração e aplique o menor privilégio à conta do banco.
Fonte primária: Martin Fowler — Separating Configuration from Use.
Guia de solução do exercício
Fluxo de execução: Na inicialização, main cria o detalhe, injeta-o no serviço e inicia os adaptadores de entrada. Durante a operação, main não participa.
Abordagem inadequada: Construir PostgreSQL dentro de checkout mistura montagem de infraestrutura e política de negócio.
// package checkout
func NewService() *Service { return &Service{orders: postgres.New()} }
Por que falha e como corrigir: Configuração, ciclo de vida e substituição para teste ficam presos ao caso de uso. O teste não consegue escolher um fake sem caminhos especiais.
Abordagem correta: Use main como ponto de composição explícito.
// package main
orders := postgres.NewOrderRepository(db)
service := checkout.NewService(orders)
runHTTPServer(service)
Raciocínio, trade-offs e limites: main conhece detalhes porque é a borda da aplicação. Ele abre e fecha recursos que criou. Essa abordagem é ideal para aplicações pequenas e médias: é explícita, auditável e evita um contêiner de DI desnecessário.
Abra o diagrama relacionado e compare o fluxo.
Pergunta de mercado e entrevista
1. O que é composition root?
Como responder: É o lugar de inicialização, geralmente main, que cria detalhes e os conecta à regra. Assim configuração, ciclo de vida e infraestrutura ficam fora do negócio.
2. Por que não construir o banco dentro do serviço?
Como responder: Porque a regra perde controle sobre a colaboração, e o teste não consegue injetar um fake. main conhece o detalhe; checkout não precisa conhecê-lo.
3. Você usaria contêiner de DI em Go?
Como responder: Começaria com composição explícita em main. Um contêiner pode esconder dependências e só vale a pena quando a complexidade real de montagem justificar seu custo.
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: