Injeção de Dependências com Google Wire: DI em Tempo de Compilação
Como montar o grafo de dependências de um projeto Go em tempo de compilação com Google Wire, sem container de IoC em tempo de execução.
Publicada em · 6 min de leitura
Injeção de Dependências com Google Wire: DI em Tempo de Compilação
O desacoplamento entre camadas exige que as dependências sejam passadas explicitamente aos construtores (Constructor Injection). Em projetos pequenos, montar o grafo de objetos manualmente na função main() é simples e direto.
No entanto, à medida que a aplicação cresce para dezenas de Use Cases, Repositórios, Gateways, Pools de Banco de Dados, Clientes Redis e Middlewares, a função main() transforma-se em um monólito frágil de 300+ linhas instanciando structs na ordem exata de dependência.
Para solucionar essa complexidade sem abrir mão dos princípios do Go, o Google desenvolveu o Wire (github.com/google/wire): um gerador de código que resolve todo o grafo de dependências em tempo de compilação, gerando código Go puro, legível e com zero reflexão em runtime.
1. Runtime IoC Containers vs Compile-Time Code Generation
Diferente de frameworks tradicionais (como Spring no Java, NestJS no Node ou uber-go/dig no Go), o Google Wire não roda contêineres de injeção durante a execução do programa:
| Critério | DI em Runtime (uber-go/dig, facebook/inject) | DI em Tempo de Compilação (Google Wire) |
|---|---|---|
| Mecanismo | Reflexão em tempo de execução (reflect.Value) | Geração estática de código Go antes do go build |
| Erros de Configuração | Descobertos apenas quando o container sobe (ou em runtime) | Erros detectados pelo compilador antes de subir o binário |
| Overhead de Startup | Lento (inspeção de tipos via reflection) | Zero (instanciação direta de structs) |
| Debugabilidade | Stack traces complexos com chamadas internas do framework | Código gerado idêntico ao que você escreveria à mão |
| Rastreabilidade (IDE) | Quebrada (não é possível usar “Go to Definition” facilmente) | 100% navegável com atalhos padrão da IDE |
2. O Pipeline do Compilador Google Wire
O Wire atua como uma ferramenta de automação para o seu Composition Root:
Fluxo de Trabalho:
- Você cria um arquivo declarativo
wire.gocom a tag de compilação//go:build wireinject. - O CLI do Wire (
wire ./...) inspeciona os tipos de entrada e saída dos seus construtores (Providers). - O Wire calcula a Ordem Topológica do grafo de dependências e gera o arquivo
wire_gen.gocom código Go comum. - O compilador padrão do Go (
go build) ignora owire.goe compila apenas owire_gen.go.
3. A Árvore de Resolução do Grafo
O Wire identifica automaticamente quais componentes são independentes (folhas) e quais dependem de outros para serem construídos:
4. Conceitos Fundamentais do Wire
4.1 Providers (Provedores)
Um Provider é simplesmente uma função construtora Go comum que aceita dependências como parâmetros e retorna o objeto construído:
// Construtor comum Go (Provider)
func NewOrderRepository(db *pgxpool.Pool) *OrderRepository {
return &OrderRepository{db: db}
}
Se a inicialização puder falhar ou exigir encerramento gracioso (cleanup), o Provider pode retornar erros e funções de limpeza:
func NewDBPool(cfg *Config) (*pgxpool.Pool, func(), error) {
pool, err := pgxpool.New(context.Background(), cfg.DatabaseURL)
if err != nil {
return nil, nil, err
}
cleanup := func() {
pool.Close() // Fecha conexões ao desligar
}
return pool, cleanup, nil
}
4.2 Injectors (Injetores)
Um Injector é uma função declarada no wire.go que informa ao Wire o que precisa ser construído e quais Providers devem ser consultados:
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
func InitializeServer() (*Server, func(), error) {
wire.Build(
NewConfig,
NewDBPool,
NewOrderRepository,
NewCreateOrderUseCase,
NewOrderHandler,
NewServer,
)
return nil, nil, nil // O Wire substitui este corpo no wire_gen.go
}
4.3 Provider Sets e Binding de Interfaces
Em arquiteturas limpas, Use Cases dependem de interfaces, mas os Providers retornam ponteiros de structs concretas. Para ensinar o Wire a satisfazer uma interface através de uma struct, usamos wire.Bind():
package di
import (
"github.com/google/wire"
"meu-projeto/internal/adapter/repository/postgres"
"meu-projeto/internal/usecase"
)
// RepositorySet agrupa os provedores de persistência
var RepositorySet = wire.NewSet(
postgres.NewOrderRepository,
// Informa: sempre que alguém pedir usecase.OrderRepository, forneça *postgres.OrderRepository
wire.Bind(new(usecase.OrderRepository), new(*postgres.OrderRepository)),
)
5. Implementação Completa Passo a Passo
5.1 Declarando o Injetor (cmd/api/wire.go)
//go:build wireinject
// +build wireinject
package main
import (
"github.com/google/wire"
"meu-projeto/internal/config"
"meu-projeto/internal/database"
"meu-projeto/internal/adapter/handler/http"
"meu-projeto/internal/adapter/repository/postgres"
"meu-projeto/internal/adapter/gateway/stripe"
"meu-projeto/internal/usecase"
)
// ServerSet agrega todas as dependências da aplicação
var ServerSet = wire.NewSet(
config.NewConfig,
database.NewPostgresPool,
postgres.NewOrderRepository,
wire.Bind(new(usecase.OrderRepository), new(*postgres.OrderRepository)),
stripe.NewPaymentGateway,
wire.Bind(new(usecase.PaymentGateway), new(*stripe.PaymentGateway)),
usecase.NewCreateOrderUseCase,
http.NewOrderHandler,
NewHTTPServer,
)
// InitializeApplication é o ponto de entrada da injeção
func InitializeApplication() (*HTTPServer, func(), error) {
wire.Build(ServerSet)
return nil, nil, nil
}
5.2 Gerando o Código com a CLI do Wire
Execute na raiz do projeto:
go run github.com/google/wire/cmd/wire ./cmd/api
O Wire analisará o grafo e criará o arquivo cmd/api/wire_gen.go:
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package main
import (
"meu-projeto/internal/adapter/handler/http"
"meu-projeto/internal/adapter/repository/postgres"
"meu-projeto/internal/adapter/gateway/stripe"
"meu-projeto/internal/config"
"meu-projeto/internal/database"
"meu-projeto/internal/usecase"
)
func InitializeApplication() (*HTTPServer, func(), error) {
cfg, err := config.NewConfig()
if err != nil {
return nil, nil, err
}
pool, cleanup, err := database.NewPostgresPool(cfg)
if err != nil {
return nil, nil, err
}
orderRepo := postgres.NewOrderRepository(pool)
paymentGateway := stripe.NewPaymentGateway(cfg)
createOrderUC := usecase.NewCreateOrderUseCase(orderRepo, paymentGateway)
orderHandler := http.NewOrderHandler(createOrderUC)
httpServer := NewHTTPServer(orderHandler, cfg)
return httpServer, func() {
cleanup() // Encadeamento automático de limpezas
}, nil
}
5.3 A Nova Função main.go
Com o Wire, o arquivo main.go encolhe para poucas linhas limpas, focando apenas no ciclo de vida do processo e no encerramento gracioso:
package main
import (
"log"
"os"
"os/signal"
"syscall"
)
func main() {
// Inicialização 100% tipada gerada pelo Wire
server, cleanup, err := InitializeApplication()
if err != nil {
log.Fatalf("Falha crítica ao inicializar aplicação: %v", err)
}
defer cleanup() // Fecha pools e conexões ao sair
go func() {
if err := server.Start(); err != nil {
log.Fatalf("Servidor finalizado com erro: %v", err)
}
}()
// Aguarda sinal de encerramento do SO
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Encerrando aplicação com segurança...")
}
6. Diagnóstico de Erros em Tempo de Compilação
Se você adicionar uma nova dependência no construtor de um Use Case e esquecer de fornecer o Provider no Wire, o Wire não permite a compilação:
$ wire ./cmd/api
wire: /meu-projeto/cmd/api/wire.go:25:1: no provider found for meu-projeto/internal/usecase.EmailService
needed by *meu-projeto/internal/usecase.CreateOrderUseCase in provider set "ServerSet"
A mensagem aponta com precisão cirúrgica a linha, a struct e a dependência ausente, eliminando completamente falhas silenciosas de injeção em produção.
7. Automação no CI/CD e Pre-commit
Assim como fizemos no Documentação de APIs com Swagger, adicione uma verificação na esteira de CI para garantir que nenhum desenvolvedor alterou dependências sem regenerar o wire_gen.go:
- name: Verify Wire Drift
run: |
go run github.com/google/wire/cmd/wire ./cmd/api
if [ -n "$(git status --porcelain cmd/api/wire_gen.go)" ]; then
echo "ERRO: O arquivo wire_gen.go está desatualizado!"
echo "Execute 'wire ./cmd/api' localmente e envie o commit."
exit 1
fi
8. Resumo Conceitual
- O Google Wire realiza Injeção de Dependências em tempo de compilação, sem usar reflexão em tempo de execução.
- Erros de dependências faltantes, tipos incompatíveis ou ciclos são barrados antes da geração do binário.
- Providers são funções construtoras comuns (
NewXxx); Injectors são as declarações de montagem do grafo. - Use
wire.Bind()para mapear structs concretas de infraestrutura para as interfaces de porta exigidas pela Clean Architecture em Go. - O Wire encadeia funções de
cleanupautomaticamente, simplificando o fechamento de pools de banco e conexões de rede no shutdown do servidor. - Utilize o Wire para projetos de médio e grande porte, mantendo o Composition Root do seu microsserviço enxuto, previsível e manutenível.