Injeção de dependência (DI) é um dos padrões mais importantes para escrever código Go testável e manutenível. Em vez de criar dependências dentro de uma função, você as recebe como parâmetros — o que facilita testes, desacopla módulos e torna a arquitetura limpa viável na prática.
Go não tem um framework de DI obrigatório como Spring (Java), mas existem duas ferramentas maduras que resolvem o problema de formas diferentes: Google Wire (geração de código) e Uber Fx (container em runtime). Neste guia, exploramos ambas com exemplos prontos para usar.
Por que Injeção de Dependência Importa em Go
Considere um serviço de pedidos que depende de um repositório e um serviço de notificação:
// SEM injeção de dependência — acoplamento forte
type PedidoService struct{}
func (s *PedidoService) CriarPedido(p Pedido) error {
repo := &PostgresRepo{} // dependência hardcoded
notif := &EmailNotifier{} // impossível trocar em testes
repo.Salvar(p)
notif.Enviar(p.Email, "Pedido criado")
return nil
}
O problema: você não consegue testar CriarPedido sem um banco PostgreSQL real e um servidor de e-mail. Com DI:
// COM injeção de dependência — desacoplado e testável
type PedidoService struct {
repo PedidoRepository
notif Notificador
}
func NewPedidoService(repo PedidoRepository, notif Notificador) *PedidoService {
return &PedidoService{repo: repo, notif: notif}
}
func (s *PedidoService) CriarPedido(p Pedido) error {
if err := s.repo.Salvar(p); err != nil {
return fmt.Errorf("salvar pedido: %w", err)
}
return s.notif.Enviar(p.Email, "Pedido criado")
}
Agora em testes, basta injetar mocks que implementam as interfaces. Mas em projetos grandes, a fiação manual dessas dependências fica trabalhosa — e é aí que Wire e Fx entram.
DI Manual vs Assistida por Framework
Em projetos pequenos, a DI manual funciona bem:
func main() {
db := database.Connect(os.Getenv("DATABASE_URL"))
repo := repository.NewPostgres(db)
notif := notification.NewEmail(os.Getenv("SMTP_HOST"))
service := pedido.NewPedidoService(repo, notif)
handler := api.NewHandler(service)
http.ListenAndServe(":8080", handler)
}
Mas quando seu projeto tem 20, 30 ou 50 dependências com relações hierárquicas, a main() vira um emaranhado de New*() calls. Wire e Fx automatizam essa fiação.
Google Wire: Geração de Código
Wire é a abordagem idiomática de Go para DI — resolve tudo em tempo de compilação via geração de código. Sem reflexão, sem mágica em runtime.
Instalação
go install github.com/google/wire/cmd/wire@latest
Definindo Providers
Cada função construtora (New*) é um provider — ela declara o que precisa e o que produz:
// internal/repository/postgres.go
package repository
import "database/sql"
type PostgresRepo struct {
db *sql.DB
}
func NewPostgresRepo(db *sql.DB) *PostgresRepo {
return &PostgresRepo{db: db}
}
// internal/service/pedido.go
package service
type PedidoService struct {
repo PedidoRepository
notif Notificador
}
func NewPedidoService(repo PedidoRepository, notif Notificador) *PedidoService {
return &PedidoService{repo: repo, notif: notif}
}
Definindo o Injector
Crie um arquivo wire.go com a função injector:
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
func InitializeApp() (*App, error) {
wire.Build(
database.NewConnection,
repository.NewPostgresRepo,
wire.Bind(new(service.PedidoRepository), new(*repository.PostgresRepo)),
notification.NewEmailNotifier,
wire.Bind(new(service.Notificador), new(*notification.EmailNotifier)),
service.NewPedidoService,
api.NewHandler,
NewApp,
)
return nil, nil
}
Execute wire para gerar o código de fiação:
wire ./...
Wire gera um wire_gen.go com toda a fiação explícita — código Go puro que você pode inspecionar e depurar como qualquer outro código. Sem reflexão, sem containers ocultos.
Agrupando com ProviderSets
Para projetos grandes, agrupe providers relacionados:
package repository
var Set = wire.NewSet(
NewPostgresRepo,
wire.Bind(new(service.PedidoRepository), new(*PostgresRepo)),
)
// wire.go — fica muito mais limpo
wire.Build(
database.Set,
repository.Set,
notification.Set,
service.Set,
api.Set,
NewApp,
)
Uber Fx: Container em Runtime
Fx é a abordagem de DI do Uber — resolve dependências em runtime usando reflexão. Mais flexível que Wire, mas com overhead em tempo de execução.
Instalação
go get go.uber.org/fx
Aplicação Básica com Fx
package main
import (
"context"
"net/http"
"go.uber.org/fx"
)
func main() {
app := fx.New(
fx.Provide(
database.NewConnection,
repository.NewPostgresRepo,
notification.NewEmailNotifier,
service.NewPedidoService,
api.NewHandler,
),
fx.Invoke(startServer),
)
app.Run()
}
func startServer(lc fx.Lifecycle, handler *api.Handler) {
srv := &http.Server{Addr: ":8080", Handler: handler}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
go srv.ListenAndServe()
return nil
},
OnStop: func(ctx context.Context) error {
return srv.Shutdown(ctx)
},
})
}
Fx automaticamente resolve o grafo de dependências em runtime. fx.Provide registra construtores, fx.Invoke executa funções que consomem dependências.
Lifecycle Hooks
Um diferencial do Fx é o gerenciamento de ciclo de vida. Conexões de banco, consumers Kafka e servidores HTTP são iniciados e finalizados de forma ordenada:
func NewKafkaConsumer(lc fx.Lifecycle, cfg Config) *kafka.Consumer {
consumer := kafka.NewConsumer(cfg.Brokers)
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
go consumer.Start()
return nil
},
OnStop: func(ctx context.Context) error {
return consumer.Close()
},
})
return consumer
}
Isso é especialmente útil em microsserviços com múltiplas conexões e workers.
Interfaces com Fx
Para injetar interfaces em vez de tipos concretos:
fx.Provide(
fx.Annotate(
repository.NewPostgresRepo,
fx.As(new(service.PedidoRepository)),
),
)
Wire vs Fx: Comparação
| Aspecto | Wire | Fx |
|---|---|---|
| Resolução | Compile-time | Runtime |
| Performance | Zero overhead | Reflexão na inicialização |
| Debug | Código gerado legível | Stack traces mais complexas |
| Lifecycle | Manual | Gerenciado (hooks start/stop) |
| Curva de aprendizado | Moderada | Moderada |
| Erros de fiação | Compilação falha | Panic na inicialização |
| Projeto ideal | Bibliotecas, CLIs, APIs simples | Microsserviços complexos |
Quando usar Wire
- Você quer zero overhead em runtime
- Precisa de código gerado inspecionável
- O projeto é uma API, CLI ou ferramenta de linha de comando
- A equipe prefere abordagens idiomáticas de Go
Quando usar Fx
- O projeto tem muitas dependências com ciclo de vida complexo
- Você precisa de graceful shutdown orquestrado
- A equipe já usa Fx em outros serviços (padrão Uber)
- Microsserviços com Kafka, gRPC e múltiplos workers
Benefícios para Testes
DI — seja manual, Wire ou Fx — transforma a testabilidade do código. Com interfaces bem definidas, seus testes ficam isolados:
func TestCriarPedido(t *testing.T) {
mockRepo := &MockRepo{}
mockNotif := &MockNotifier{}
svc := service.NewPedidoService(mockRepo, mockNotif)
err := svc.CriarPedido(Pedido{Email: "test@example.com"})
if err != nil {
t.Fatalf("erro inesperado: %v", err)
}
if !mockRepo.SalvarCalled {
t.Error("Salvar não foi chamado")
}
}
Para mais sobre testes em Go, incluindo fuzzing e testes de integração com Testcontainers, confira nossos guias dedicados.
Estrutura de Projeto Real
Uma estrutura que funciona bem com DI:
meu-servico/
├── cmd/
│ └── api/
│ ├── main.go # Fiação (Wire injector ou Fx app)
│ └── wire.go # (Se usar Wire)
├── internal/
│ ├── domain/ # Entidades e interfaces
│ │ ├── pedido.go
│ │ └── repository.go
│ ├── repository/ # Implementações de persistência
│ │ └── postgres.go
│ ├── service/ # Lógica de negócio
│ │ └── pedido.go
│ └── api/ # Handlers HTTP/gRPC
│ └── handler.go
├── go.mod
└── go.sum
Essa organização segue os princípios de clean architecture com separação clara entre domínio, infraestrutura e API. O uso de context para propagação de dados e cancelamento complementa a DI para criar serviços robustos.
Para logging dentro dos serviços injetados, use slog configurado no nível da aplicação e injetado como dependência. Para observabilidade, injete o tracer do OpenTelemetry da mesma forma.
Outras linguagens também resolvem DI de formas interessantes: Kotlin com Koin e Dagger, e Python com dependency-injector — mas a filosofia de Go com Wire prioriza simplicidade e transparência via geração de código.
FAQ
Preciso de um framework de DI em Go?
Não necessariamente. Para projetos pequenos e médios, a DI manual (passando dependências nos construtores) é simples e eficaz. Frameworks como Wire e Fx se justificam quando o grafo de dependências tem mais de 15-20 componentes e a fiação manual se torna difícil de manter.
Wire gera código que precisa ser commitado?
Sim. O arquivo wire_gen.go gerado deve ser commitado no repositório. Ele é código Go válido que qualquer pessoa pode ler e entender. Execute wire ./... sempre que alterar os providers e commit o resultado.
Fx tem impacto na performance da aplicação?
O impacto de Fx é apenas na inicialização — a resolução de dependências por reflexão acontece uma vez quando a aplicação inicia. Em runtime, os serviços já estão instanciados e não há overhead adicional. Para a maioria das aplicações, essa latência de startup é negligível.
Posso misturar Wire e Fx no mesmo projeto?
Tecnicamente sim, mas não é recomendado. Escolha uma abordagem e mantenha consistência. Se você tem microsserviços diferentes, cada um pode usar a ferramenta mais adequada, mas dentro de um mesmo serviço, mantenha uma única estratégia de DI.
Como faço DI com generics em Go?
Com generics (Go 1.18+), você pode criar repositories e services tipados. Wire suporta generics a partir do Go 1.18, e Fx trabalha com tipos concretos instanciados. Generics complementam DI mas não substituem — são ferramentas para problemas diferentes.