Dependency Injection em Go com Google Wire - Guia Prático de Injeção de Dependências Automática!
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:
- Google Wire: https://github.com/google/wire
- Wire Tutorial Oficial: https://github.com/google/wire/blob/main/_tutorial/README.md
- Dependency Injection em Go (blog post): https://blog.drewolson.org/dependency-injection-in-go
- Repositório do Trivium: https://github.com/carloseduardodb/trivium_backend