Voltar ao blog
9 de maio de 20266 min de leitura

Dependency Injection em Go com Google Wire - Guia Prático de Injeção de Dependências Automática!

godependencyinjectionbackendprogramming

Oitenta linhas de main.go só para instanciar repositórios, use cases e controllers na ordem certa. Adicionou um parâmetro novo no construtor do AuthUseCase? Parabéns, agora precisa atualizar o main, o teste de integração e aquele outro lugar que você esqueceu. Esse era o meu dia a dia antes de usar o Google Wire. O projeto tinha 10 controllers, 8 use cases e 12 repositórios, e conectar tudo manualmente virou um trabalho que consumia mais tempo do que escrever lógica de negócio. Neste artigo vou mostrar como o Wire resolve isso gerando código de instanciação automaticamente, sem reflection e sem mágica de runtime.

Guia de tópicos:

  • O Problema: Instanciação Manual que Não Escala
  • O que é o Google Wire e Por Que Ele Existe
  • Providers e Modules: Organizando suas Dependências
  • O Truque do wire.txt: Convivendo com Wire no Dia a Dia
  • O Arquivo Gerado: O que o Wire Faz por Você
  • Wire vs Injeção Manual: Quando Cada Um Vale a Pena
  • Considerações Finais
  • Links Indicativos

O Problema: Instanciação Manual que Não Escala

Imagina o seguinte cenário: você tem um projeto com Clean Architecture, separou tudo bonitinho em camadas, tem seus repositórios, use cases e controllers. Agora na hora de subir a aplicação, você precisa instanciar tudo na ordem certa. O repositório de usuário precisa da conexão com o banco. O use case de autenticação precisa do repositório de usuário e do repositório do Firebase. O controller precisa do use case. O middleware precisa de outro repositório. E o servidor precisa de todos os controllers e do middleware.

Quando seu projeto tem 3 endpoints, fazer isso na mão é tranquilo. Você escreve 10 linhas no main e pronto. Mas quando o projeto cresce para 10 controllers, 8 use cases, 12 repositórios e 3 middlewares, aquele main.go vira um monstro. E o pior: toda vez que você adiciona uma dependência nova em qualquer use case, precisa ir lá no main e atualizar a instanciação. Esqueceu? O compilador te dá um erro dizendo que falta um argumento, e você precisa rastrear qual dependência nova foi adicionada.

Esse é o problema que o Google Wire resolve. Ele gera esse código de instanciação automaticamente, analisando os construtores e resolvendo a árvore de dependências em tempo de compilação.

O que é o Google Wire e Por Que Ele Existe

O Wire é uma ferramenta de injeção de dependências para Go criada pelo Google. Mas diferente de frameworks de DI em outras linguagens (como Spring no Java ou Nest no TypeScript), o Wire não usa reflection, não usa containers em runtime, não tem mágica escondida. Ele é um gerador de código. Você define os providers (funções que criam coisas) e ele gera um arquivo Go normal com todo o código de instanciação.

Isso significa que o resultado é código Go puro, legível, debugável. Se algo der errado, você abre o arquivo gerado e lê como se fosse código que você mesmo escreveu. Não tem container mágico, não tem resolução em runtime que pode falhar silenciosamente. Se uma dependência não pode ser resolvida, o Wire te avisa em tempo de compilação, não em tempo de execução.

A filosofia do Wire é: "eu gero o código chato que você teria que escrever na mão, mas faço isso de forma correta e automática". Nada mais que isso.

Providers e Modules: Organizando suas Dependências

No Wire, um provider é qualquer função que retorna algo. Se você tem uma função NewUserRepository() repositorier.UserRepositorier, isso é um provider. O Wire olha para o retorno dessa função e sabe que ela é capaz de fornecer um UserRepositorier para quem precisar.

No projeto que uso como referência, organizei os providers em 3 módulos separados por responsabilidade: o módulo de infraestrutura (que cria repositórios e conexões), o módulo de domínio (que cria use cases) e o módulo de apresentação (que cria controllers, middlewares e o servidor).

O módulo de infraestrutura tem providers como NewUserRepository, NewCryptoCurrencyRepository, NewFirebaseRepository. Cada um retorna uma interface, não o tipo concreto. Isso é importante porque o Wire resolve dependências pelo tipo de retorno. Se o use case precisa de um UserRepositorier, o Wire procura qual provider retorna esse tipo e conecta automaticamente.

O módulo de domínio tem providers como NewAuthUseCase, NewPositionUseCase, NewPortfolioUseCase. Cada use case recebe interfaces no construtor, e o Wire sabe quais providers do módulo de infra satisfazem essas interfaces.

O módulo de apresentação tem os controllers e o servidor. O servidor recebe todos os controllers, e cada controller recebe seu use case e o middleware de autenticação.

A beleza disso é que o Wire reutiliza instâncias automaticamente. Se o UserRepositorier é necessário tanto no AuthUseCase quanto no middleware de autenticação, o Wire cria uma única instância e passa para os dois. Você não precisa se preocupar com isso.

O Truque do wire.txt: Convivendo com Wire no Dia a Dia

Aqui vem um detalhe prático que ninguém te conta nos tutoriais. O Wire precisa de um arquivo com a tag //go:build wireinject que contém a definição do injector. Esse arquivo não pode coexistir com o arquivo gerado (wire_gen.go) durante a compilação normal, porque ambos definem a mesma função.

A solução que adotei no projeto foi renomear o arquivo fonte para wire.txt. Assim ele não é compilado pelo Go no dia a dia. Quando preciso regenerar as dependências, o Makefile renomeia para .go, roda o Wire, e renomeia de volta para .txt. O comando fica assim no Makefile:

generate:
    cd cmd/app && mv wire.txt wire.go && wire && mv wire.go wire.txt && cd ../..

Parece gambiarra? Talvez. Mas funciona perfeitamente e evita conflitos de compilação. O arquivo wire.txt é simples: ele importa os 3 módulos e diz "Wire, resolve tudo isso pra mim e me dá um App pronto". São menos de 20 linhas.

No dia a dia o fluxo é: você adiciona uma dependência nova (por exemplo, um novo use case), registra o provider no módulo correto, roda make generate, e o Wire regenera o wire_gen.go com a nova dependência resolvida. Se algo não bate (tipo um use case que precisa de uma interface que nenhum provider fornece), o Wire te diz exatamente o que está faltando.

O Arquivo Gerado: O que o Wire Faz por Você

O arquivo wire_gen.go gerado no projeto tem cerca de 50 linhas e instancia 10 controllers, 6 use cases, 9 repositórios e 2 middlewares. Se eu fosse escrever isso na mão, seriam as mesmas 50 linhas, mas com a diferença de que eu teria que manter manualmente. Toda vez que adicionasse um parâmetro novo em qualquer construtor, teria que lembrar de atualizar.

O que o Wire gera é algo assim (simplificado): ele cria o repositório de usuário, passa para o use case de autenticação junto com o repositório do Firebase, cria o controller de autenticação com o use case, cria o middleware com o repositório de verificação de token e o repositório de usuário, e assim por diante até montar o servidor completo.

Um detalhe importante: o Wire detecta quando a mesma dependência é usada em múltiplos lugares e cria uma única instância. No projeto, o UserRepositorier é usado no AuthUseCase, no middleware de autenticação e no UserController. O Wire cria uma vez e passa para os três. Isso é eficiente e correto.

Wire vs Injeção Manual: Quando Cada Um Vale a Pena

Vou ser honesto: se seu projeto tem menos de 5 dependências no total, o Wire é overhead. Você vai instalar uma ferramenta, criar arquivos de configuração, aprender a sintaxe de providers e modules, tudo para gerar 10 linhas de código que você escreveria em 2 minutos.

O Wire começa a valer a pena quando você tem mais de 10 dependências e a árvore começa a ficar complexa. Quando adicionar um parâmetro novo em um construtor significa atualizar 3 arquivos diferentes. Quando você esquece de passar uma dependência e perde 5 minutos debugando um nil pointer que só aparece em runtime.

Outro ponto: o Wire é especialmente útil quando você trabalha com interfaces. Como ele resolve por tipo, se você tem um provider que retorna repositorier.UserRepositorier e um use case que recebe repositorier.UserRepositorier, a conexão é automática. Não precisa dizer "use esse provider para satisfazer essa interface". O Wire infere.

A desvantagem do Wire é que ele adiciona uma etapa no build. Você precisa rodar wire toda vez que muda a árvore de dependências. Se esquecer, o wire_gen.go fica desatualizado e o compilador reclama. Mas isso é um erro de compilação, não de runtime, então é seguro.

Para projetos em equipe, o Wire tem outra vantagem: ele força uma estrutura. Todo mundo sabe que providers ficam nos módulos, que o Wire resolve a árvore, e que o wire_gen.go não deve ser editado manualmente. Isso reduz discussões sobre "onde instanciar o que".

Considerações Finais

No enfrentamento da complexidade de projetos que crescem, a injeção de dependências é uma daquelas coisas que parece desnecessária no começo e se torna indispensável depois. O Google Wire resolve esse problema de forma elegante para Go: sem reflection, sem mágica de runtime, sem containers pesados. É só geração de código.

O que eu quero que você leve deste artigo é que DI não é sobre frameworks ou ferramentas. É sobre desacoplar a criação de objetos do uso deles. Se você faz isso na mão com 5 dependências, ótimo. Se precisa de ajuda com 30, o Wire está ali. A decisão é pragmática, não dogmática.

E lembre-se: o Wire gera código Go normal. Se um dia você decidir que não quer mais usar Wire, basta pegar o wire_gen.go, renomear, e manter manualmente. Não tem lock-in. Não tem dependência de runtime. É só código.


Links indicativos: