Para a maioria dos serviços Go em 2026, comece sem framework de DI. Construtores explícitos, interfaces pequenas e um main que monta o grafo continuam sendo o caminho idiomático — e o que descrevemos no guia de dependency injection em Go sem framework. Quando o grafo passa de dezenas de componentes, a pergunta muda: Google Wire (código gerado em compile-time) ou Uber Fx (container e lifecycle em runtime)?
A resposta curta: Wire se você quer manter DI “como código Go” e falhar no build; Fx se você precisa de módulos reutilizáveis, OnStart/OnStop e composição de aplicação em escala. Este artigo compara os dois com código, critérios de decisão, armadilhas e o momento certo de não adotar nenhum deles. Complementa Clean Architecture em Go, graceful shutdown, configuração com Viper, slog e mocks em Go.
Resposta rápida: Wire, Fx ou só construtores?
| Situação | Recomendação |
|---|---|
| API/worker com poucos serviços e 1–2 repositórios | Construtores manuais |
| Grafo grande, mas time quer zero runtime magic | Google Wire |
| Muitos módulos de infra compartilhados entre serviços | Uber Fx |
| Lifecycle complexo (HTTP + workers + filas + cron) | Fx (ou main manual bem estruturado) |
| Biblioteca open source que outros vão importar | Sem Wire/Fx na API pública |
| Time júnior ou onboarding constante | Prefira manual ou Wire (mais legível no debugger) |
| “Preciso de DI porque vi no Spring/Nest” | Não adote ainda — modele interfaces primeiro |
Regra prática: se o main ainda cabe em uma tela e cada teste monta fakes com três linhas, você não tem problema de DI — tem problema de escopo de feature. Ferramenta de DI não substitui interfaces pequenas nem testes de tabela.
O que cada ferramenta realmente resolve
As duas resolvem o mesmo incômodo: montar o grafo de dependências sem copiar e colar NewService(...) em cinco mains. A diferença está em quando e como o grafo é resolvido.
| Critério | Construtores manuais | Google Wire | Uber Fx |
|---|---|---|---|
| Momento da montagem | Compile-time (seu código) | Compile-time (código gerado) | Runtime |
| Falha de grafo | Erro de compilação / nil | Erro no wire / build | Panic ou erro no fx.New |
Lifecycle (Start/Stop) | Você implementa | Você implementa | Embutido (OnStart/OnStop) |
| Módulos reutilizáveis | Pacotes + funções New* | Provider sets | fx.Module / fx.Options |
| Reflexão em runtime | Não | Não | Sim (parcial) |
| Curva de aprendizado | Baixa | Média | Alta |
| Debug no IDE | Excelente | Bom (código gerado legível) | Mais indirection |
| Acoplamento | Zero | //go:build wireinject | Import de go.uber.org/fx no bootstrap |
| Ideal para | Serviços pequenos/médios | Grafo grande e explícito | Plataformas multi-módulo |
Nenhuma linha dessa tabela autoriza pular o desenho das dependências. Wire e Fx não inventam boas interfaces; eles apenas montam o que você já declarou.
Baseline: o grafo manual que todo time deveria dominar
Antes de Wire ou Fx, o padrão saudável parece com isto:
package main
import (
"context"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"example.com/app/internal/config"
"example.com/app/internal/httpapi"
"example.com/app/internal/pedido"
"example.com/app/internal/postgres"
)
func main() {
cfg, err := config.Load()
if err != nil {
slog.Error("config", "err", err)
os.Exit(1)
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
db, err := postgres.Open(cfg.DatabaseURL)
if err != nil {
logger.Error("db", "err", err)
os.Exit(1)
}
defer db.Close()
repo := postgres.NewPedidoRepository(db)
svc := pedido.NewService(repo, logger)
handler := httpapi.NewHandler(svc, logger)
srv := &http.Server{
Addr: cfg.HTTPAddr,
Handler: handler.Routes(),
ReadHeaderTimeout: 5 * time.Second,
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
go func() {
logger.Info("http listening", "addr", cfg.HTTPAddr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Error("http", "err", err)
stop()
}
}()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_ = srv.Shutdown(shutdownCtx)
}
Esse bootstrap é legível, testável e alinhado com graceful shutdown em produção. O problema aparece quando surgem cache Redis, cliente de antifraude, publisher de eventos, worker de outbox, health checks e três repositórios — e o main vira um script de 200 linhas com ordem de criação frágil.
Google Wire: DI em compile-time
Wire é um gerador de código. Você declara providers (funções que constroem dependências) e um injector (função com corpo vazio e a diretiva wire.Build). O comando wire gera o wire_gen.go com a montagem real.
Provider e injector
//go:build wireinject
// +build wireinject
package main
import (
"github.com/google/wire"
"example.com/app/internal/config"
"example.com/app/internal/httpapi"
"example.com/app/internal/pedido"
"example.com/app/internal/postgres"
)
func initializeApp() (*App, error) {
wire.Build(
config.Load,
postgres.Open,
postgres.NewPedidoRepository,
pedido.NewService,
httpapi.NewHandler,
NewApp,
)
return nil, nil
}
O gerador produz algo no espírito de:
// Code generated by Wire. DO NOT EDIT.
func initializeApp() (*App, error) {
cfg, err := config.Load()
if err != nil {
return nil, err
}
db, err := postgres.Open(cfg.DatabaseURL)
if err != nil {
return nil, err
}
repo := postgres.NewPedidoRepository(db)
logger := newLogger() // se estiver no provider set
svc := pedido.NewService(repo, logger)
handler := httpapi.NewHandler(svc, logger)
app := NewApp(cfg, db, handler)
return app, nil
}
Provider sets reutilizáveis
var InfraSet = wire.NewSet(
config.Load,
newLogger,
postgres.Open,
)
var PedidoSet = wire.NewSet(
postgres.NewPedidoRepository,
pedido.NewService,
httpapi.NewHandler,
)
Você compõe sets por domínio e mantém o injector enxuto. Erros de grafo — tipo faltando, ambiguidade de provider, ciclo — aparecem antes do deploy, no go generate ou no CI.
O que Wire não faz
- Não gerencia
Start/Stopautomaticamente. - Não registra hooks de health check.
- Não resolve “qual implementação de
Repositoryusar” por nome mágico — você escolhe comwire.Bindou providers específicos. - Não substitui feature flags nem configuração.
Wire brilha quando o time já escreve bons construtores e só quer eliminar o boilerplate do main sem pagar o custo de um container.
Fluxo de trabalho no dia a dia
go install github.com/google/wire/cmd/wire@latest
# no módulo
wire ./...
go test ./...
No CI, falhe se wire_gen.go estiver desatualizado (git diff --exit-code). Trate o arquivo gerado como artefato versionado — a maioria dos times brasileiros versiona wire_gen.go para builds reproduzíveis sem instalar o binário Wire em todo ambiente.
Uber Fx: aplicação como grafo com lifecycle
Fx é um framework de aplicação. Você registra construtores com fx.Provide, invoca funções de bootstrap com fx.Invoke e deixa o Fx:
- montar o grafo;
- chamar hooks
OnStartna ordem certa; - bloquear até sinal de shutdown;
- chamar
OnStopna ordem inversa.
package main
import (
"context"
"net/http"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
"example.com/app/internal/config"
"example.com/app/internal/httpapi"
"example.com/app/internal/pedido"
"example.com/app/internal/postgres"
)
func main() {
fx.New(
fx.WithLogger(func() fxevent.Logger { return fxevent.NopLogger }),
fx.Provide(
config.Load,
postgres.Open,
postgres.NewPedidoRepository,
pedido.NewService,
httpapi.NewHandler,
newHTTPServer,
),
fx.Invoke(registerHooks),
).Run()
}
func newHTTPServer(h *httpapi.Handler, cfg config.Config) *http.Server {
return &http.Server{
Addr: cfg.HTTPAddr,
Handler: h.Routes(),
}
}
func registerHooks(lc fx.Lifecycle, srv *http.Server) {
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)
},
})
}
Módulos: o superpoder (e a armadilha)
var Module = fx.Module("pedido",
fx.Provide(
postgres.NewPedidoRepository,
pedido.NewService,
httpapi.NewHandler,
),
)
Serviços da empresa passam a compartilhar fx.Module de logging, tracing, banco e mensageria. Isso acelera novos microsserviços — e também espalha o acoplamento a Fx por dezenas de pacotes se você não isolar o bootstrap.
Grupos e valores nomeados
Fx permite fx.Out, fx.In, tags e grupos para coletar várias implementações (vários http.Handler de rotas, vários workers). É poderoso em plataformas grandes; em serviços pequenos, vira indirection desnecessária.
O que Fx resolve melhor que Wire
- Lifecycle unificado para HTTP, gRPC, consumers Kafka/SQS e tickers.
- Ordem de start/stop sem você manter uma lista manual.
- Módulos para times de plataforma entregarem “plugue este pacote e ganhe métricas + health”.
- Integração natural com padrões de worker pool, health checks e OpenTelemetry.
Comparativo prático: a mesma decisão em três estilos
1) Trocar implementação em teste
Manual / Wire: no teste, chame pedido.NewService(fakeRepo, logger) direto. Não suba o injector de produção.
Fx: use fx.Replace, fx.Decorate ou um fx.Options de teste com fakes. Funciona, mas o teste fica mais “framework-aware”.
2) Falha de dependência faltando
Manual: o compilador reclama do argumento.
Wire: wire falha com mensagem de provider ausente.
Fx: fx.New falha em runtime na subida do processo (ainda cedo, mas depois do build).
3) Graceful shutdown
Manual / Wire: você escreve o loop de sinal — veja o guia de graceful shutdown.
Fx: App.Run() + hooks OnStop já modelam o ciclo. Ainda assim, timeouts de shutdown e drenagem de filas são responsabilidade sua.
4) Biblioteca reutilizável
Se o pacote é importado por outros times, não force Wire nem Fx na API pública. Exporte NewService(deps) e deixe o consumidor montar como quiser. O mesmo princípio vale para middleware HTTP e clientes de API REST.
Critérios de decisão para times brasileiros
Use esta rubrica em review de arquitetura:
- Tamanho do grafo. Menos de ~15 construtores no bootstrap? Manual. Entre 15 e 40 com tipos estáveis? Wire. Dezenas de módulos e vários binários? Avalie Fx.
- Número de processos no mesmo binário. Só HTTP? Manual/Wire bastam. HTTP + consumers + cron no mesmo processo? Fx reduz erro de ordem de start.
- Maturidade do time. Wire se lê como Go gerado. Fx exige disciplina de módulos e cuidado para não esconder dependências.
- Plataforma interna. Se já existe um “kit” Fx da empresa, remar contra a maré custa mais do que aprender o framework.
- Observabilidade e ops. Lifecycle central ajuda a padronizar health, métricas e shutdown — desde que o time não use isso para empurrar lógica de negócio para hooks.
- Testabilidade. O domínio continua recebendo interfaces; o framework fica na borda. Se o caso de uso importa Fx, o desenho errou.
Armadilhas comuns (e como evitar)
“Container para tudo”
Injetar *sql.DB, *redis.Client, *http.Client e *slog.Logger em todo handler cria um god-object distribuído. Prefira interfaces no domínio (PedidoRepository, Clock, Publisher) como no guia de DI sem framework.
Esconder configuração no grafo
Configuração deve ser validada cedo — idealmente com o padrão Twelve-Factor + Viper/env. Não deixe um provider ler os.Getenv no meio do grafo sem falhar rápido.
Wire com providers ambíguos
Dois NewRepository que devolvem a mesma interface quebram o grafo. Use wire.Bind explícito ou tipos distintos por bounded context.
Fx Invoke com side effects opacos
fx.Invoke que “só registra rotas” e também abre conexão global vira caça ao tesouro. Hooks de lifecycle devem ser óbvios: abrir listener, iniciar consumer, fechar pool.
Gerar código e não rodar no CI
wire_gen.go desatualizado é bug de produção esperando o merge. Trate wire ./... como passo de lint, no mesmo espírito de staticcheck/golangci-lint.
Misturar DI framework com service locator
Passar o fx.App ou um container.Get("repo") para o domínio anula o benefício. Dependências entram pelo construtor — sempre.
Como migrar sem reescrever o monólito
Do manual para Wire
- Extraia cada
New*para assinaturas puras (sem globals). - Crie
provider setspor pacote. - Substitua o
mainpor um injector; versionarwire_gen.go. - Mantenha testes unitários chamando construtores diretamente — não o injector.
Do manual para Fx
- Isole o bootstrap em
internal/appoucmd/api. - Mova
Providesem mudar o domínio. - Introduza
Lifecyclesó onde há start/stop real (HTTP, consumers). - Só depois quebre em
fx.Modulepor bounded context.
De Fx para Wire (ou vice-versa)
Raro e caro. Só faça se o framework virou atrito mensurável (builds lentos, onboarding, incidentes de ordem de start). Migre um binário por vez; não “padronize a empresa” em um PR heróico.
O que isso tem a ver com vagas e entrevistas Go
Descrições de vagas backend plenas/sênior no Brasil pedem cada vez mais “experiência com DI”, “modularização” e “produção”. Em entrevista, o sinal de maturidade não é citar Wire ou Fx de cor — é explicar quando o main manual deixa de escalar, como testar com fakes e como o lifecycle evita vazamento de conexões no deploy. Combine isso com repertório de context e timeout, rate limiting e observabilidade.
Se você está montando portfólio, um serviço pequeno com construtores manuais bem testados vale mais do que um CRUD com Fx copiado de template. Veja ideias em projetos Go para portfólio e o roadmap Go 2026.
Checklist antes de adotar Wire ou Fx
- Interfaces do domínio são pequenas e estão perto de quem usa?
- O
mainatual é realmente o gargalo (não a modelagem)? - Configuração falha cedo e de forma explícita?
- Testes unitários não precisam do container?
- CI valida código gerado (Wire) ou sobe o app com smoke test (Fx)?
- Shutdown drena requests e workers com timeout?
- Logs estruturados e correlação existem independentemente do DI?
- A escolha está documentada no README do serviço (uma página, sem dogma)?
Se a maioria das respostas for “não”, volte ao guia sem framework e ao clean architecture sem overengineering.
Perguntas frequentes
Qual a diferença entre Google Wire e Uber Fx?
Wire gera código Go em tempo de compilação e monta o grafo com funções puras, sem container em runtime. Fx monta o grafo em runtime, com módulos, lifecycle (OnStart/OnStop), grupos de dependências e hooks de inicialização. Wire é mais próximo de construtores manuais; Fx é um framework de aplicação.
Quando usar Wire em vez de Fx?
Use Wire quando o grafo cresceu demais para o main manual, mas você quer manter DI explícita, zero magia em runtime e falhas detectadas no build. É a evolução natural do padrão de construtores.
Quando Uber Fx vale a pena?
Fx vale a pena em serviços grandes com muitos módulos, inicialização ordenada, graceful shutdown centralizado e times que compartilham pacotes de infraestrutura (logger, métricas, banco, filas). O custo é acoplamento ao framework e uma curva de aprendizado maior.
Preciso de Wire ou Fx para fazer DI em Go?
Não. A forma idiomática começa com structs, interfaces pequenas e construtores no main. Ferramentas de DI só entram quando a montagem manual virou fonte real de erro ou o lifecycle da aplicação ficou complexo demais para um bootstrap legível.
Wire e Fx podem coexistir no mesmo projeto?
Tecnicamente sim, mas raramente vale a pena. Escolha um modelo de montagem por serviço. Misturar gera dois estilos de bootstrap, dois jeitos de testar e onboarding mais caro. Prefira um padrão único por repositório.
Conclusão
Google Wire e Uber Fx resolvem dores reais — mas só depois que o time já domina DI idiomática com construtores. Wire é a ponte conservadora: menos boilerplate, mesmo modelo mental, erros no build. Fx é a aposta de plataforma: módulos, lifecycle e composição em escala, com mais indirection. Na dúvida, mantenha o main explícito, invista em interfaces testáveis e só introduza ferramenta quando o grafo ou o ciclo de vida cobrarem o preço.
Continue com dependency injection sem framework, Clean Architecture em Go, mocks e fakes, graceful shutdown e as vagas Go no Brasil. Se ainda está estruturando a base da linguagem, o caminho como aprender Go e o curso gratuito fecham o funil de aprendizado.