Pular para o conteúdo

Trilha de Go · parte 30 de 37

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

Tamanho da fonte:
Largura da coluna:

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érioDI em Runtime (uber-go/dig, facebook/inject)DI em Tempo de Compilação (Google Wire)
MecanismoReflexão em tempo de execução (reflect.Value)Geração estática de código Go antes do go build
Erros de ConfiguraçãoDescobertos apenas quando o container sobe (ou em runtime)Erros detectados pelo compilador antes de subir o binário
Overhead de StartupLento (inspeção de tipos via reflection)Zero (instanciação direta de structs)
DebugabilidadeStack traces complexos com chamadas internas do frameworkCó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:

Pipeline de Compilação do Google Wire
figura 1 · Pipeline de Compilação do Google Wire

Fluxo de Trabalho:

  1. Você cria um arquivo declarativo wire.go com a tag de compilação //go:build wireinject.
  2. O CLI do Wire (wire ./...) inspeciona os tipos de entrada e saída dos seus construtores (Providers).
  3. O Wire calcula a Ordem Topológica do grafo de dependências e gera o arquivo wire_gen.go com código Go comum.
  4. O compilador padrão do Go (go build) ignora o wire.go e compila apenas o wire_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:

Grafo de Resolução de Dependências
figura 2 · Grafo de Resolução de Dependências

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 cleanup automaticamente, 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.

↑ Voltar ao topo

Atalhos de teclado

Abrir busca
/
Alternar tema claro/escuro
t
Aula anterior na série
←
Próxima aula na série
→
Lista de atalhos
?