Dependency injection com wire ou fx
1. Introdução à Injeção de Dependência em Go
Dependency Injection (DI) é um padrão de projeto onde as dependências de um componente são fornecidas externamente, em vez de criadas internamente. Em Go, isso significa que uma struct recebe suas dependências (como conexões de banco, serviços HTTP, loggers) através do construtor ou setters, em vez de instanciá-las diretamente.
Problemas comuns sem DI:
type UserHandler struct {
db *sql.DB // Acoplamento direto
}
func NewUserHandler() *UserHandler {
db, _ := sql.Open("postgres", "conn_string")
return &UserHandler{db: db}
}
- Acoplamento rígido: o handler conhece detalhes de implementação do banco
- Testes difíceis: impossível mockar o banco sem refatorar
- Inicialização manual: cada componente precisa montar suas dependências
As duas abordagens principais para DI em Go são: frameworks estáticos (Wire) e dinâmicos (Fx). Vamos explorar cada uma.
2. Wire: Injeção Estática em Tempo de Compilação
Wire do Google gera código de inicialização em tempo de compilação, eliminando overhead de runtime. Seus conceitos fundamentais são providers, injectors e sets.
Providers: funções que criam dependências.
// provider.go
package main
import "database/sql"
func NewDB() (*sql.DB, error) {
return sql.Open("postgres", "conn_string")
}
type UserRepository struct {
db *sql.DB
}
func NewUserRepository(db *sql.DB) *UserRepository {
return &UserRepository{db: db}
}
Injector: função que Wire gera automaticamente.
// wire.go
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
func InitializeApp() (*App, error) {
wire.Build(
NewDB,
NewUserRepository,
NewUserHandler,
wire.Struct(new(App), "*"),
)
return nil, nil
}
Execute wire no terminal para gerar wire_gen.go:
// wire_gen.go (gerado automaticamente)
func InitializeApp() (*App, error) {
db, err := NewDB()
if err != nil {
return nil, err
}
userRepository := NewUserRepository(db)
userHandler := NewUserHandler(userRepository)
app := &App{
UserHandler: userHandler,
}
return app, nil
}
Tratamento de erros e dependências opcionais:
wire.Build(
NewDB,
wire.Value(Config{Port: 8080}), // Valor fixo
wire.Struct(new(App), "*"),
)
Wire é ideal para aplicações onde performance e previsibilidade são críticas, como CLIs e microsserviços simples.
3. Fx: Injeção Dinâmica com Runtime e Ciclo de Vida
Fx da Uber oferece DI em runtime com gerenciamento de ciclo de vida. A estrutura básica usa fx.New, fx.Options e fx.Provide.
Estrutura de um aplicativo Fx:
package main
import (
"go.uber.org/fx"
"net/http"
)
func NewHTTPServer(lc fx.Lifecycle) *http.Server {
srv := &http.Server{Addr: ":8080"}
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)
},
})
return srv
}
func main() {
fx.New(
fx.Provide(NewHTTPServer),
fx.Invoke(func(srv *http.Server) {
// O servidor já iniciou via lifecycle
}),
).Run()
}
Parâmetros nomeados e grupos:
type HandlerParams struct {
fx.In
UserRepo *UserRepository `name:"user"`
AdminRepo *AdminRepository `name:"admin"`
}
fx.Provide(
fx.Annotated{
Name: "user",
Target: NewUserRepository,
},
fx.Annotated{
Name: "admin",
Target: NewAdminRepository,
},
)
Fx é excelente para aplicações complexas com lifecycle gerenciado, como servidores HTTP/gRPC e sistemas que precisam de graceful shutdown.
4. Comparação Prática: Wire vs. Fx
| Característica | Wire | Fx |
|---|---|---|
| Performance | Overhead zero em runtime | Overhead mínimo de reflection |
| Tempo de erro | Compilação | Runtime |
| Lifecycle | Manual | Automático (OnStart/OnStop) |
| Complexidade | Baixa (gera código simples) | Média (abstrações do Fx) |
| Debug | Fácil (código gerado legível) | Médio (precisa entender lifecycle) |
Casos de uso ideais:
- Wire: CLIs, ferramentas de linha de comando, microsserviços simples, aplicações críticas onde performance importa
- Fx: Servidores web, sistemas com graceful shutdown, aplicações que precisam de injeção dinâmica, microsserviços complexos
5. Exemplo Completo: Servidor HTTP com DI
Implementação com Wire
// main.go
package main
import (
"net/http"
"log"
)
type App struct {
Handler *UserHandler
}
func main() {
app, err := InitializeApp()
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/users", app.Handler.GetUsers)
log.Fatal(http.ListenAndServe(":8080", nil))
}
// handler.go
type UserHandler struct {
service *UserService
}
func NewUserHandler(service *UserService) *UserHandler {
return &UserHandler{service: service}
}
func (h *UserHandler) GetUsers(w http.ResponseWriter, r *http.Request) {
users, _ := h.service.FindAll()
w.Write([]byte(users))
}
// service.go
type UserService struct {
repo *UserRepository
}
func NewUserService(repo *UserRepository) *UserService {
return &UserService{repo: repo}
}
func (s *UserService) FindAll() (string, error) {
return s.repo.FindAll()
}
// repository.go
type UserRepository struct {
db *sql.DB
}
func NewUserRepository(db *sql.DB) *UserRepository {
return &UserRepository{db: db}
}
func (r *UserRepository) FindAll() (string, error) {
// lógica de banco
return "users data", nil
}
// wire.go
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
func InitializeApp() (*App, error) {
wire.Build(
NewDB,
NewUserRepository,
NewUserService,
NewUserHandler,
wire.Struct(new(App), "*"),
)
return nil, nil
}
Implementação equivalente com Fx
// main.go
package main
import (
"context"
"net/http"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
)
func main() {
app := fx.New(
fx.WithLogger(func() fxevent.Logger {
return fxevent.NopLogger
}),
fx.Provide(
NewDB,
NewUserRepository,
NewUserService,
NewUserHandler,
NewHTTPServer,
),
fx.Invoke(func(srv *http.Server) {}),
)
app.Run()
}
// server.go
func NewHTTPServer(lc fx.Lifecycle, handler *UserHandler) *http.Server {
mux := http.NewServeMux()
mux.HandleFunc("/users", handler.GetUsers)
srv := &http.Server{Addr: ":8080", Handler: mux}
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)
},
})
return srv
}
Estrutura de pastas recomendada:
project/
├── cmd/
│ └── server/
│ ├── main.go (Wire: wire.go + wire_gen.go)
│ └── main.go (Fx: único arquivo)
├── internal/
│ ├── handler/
│ ├── service/
│ └── repository/
└── go.mod
6. Boas Práticas e Padrões Avançados
Organização de providers por camada:
// internal/wire/provider.go
package wire
import (
"github.com/google/wire"
"project/internal/handler"
"project/internal/service"
"project/internal/repository"
)
var HandlerSet = wire.NewSet(
handler.NewUserHandler,
handler.NewAdminHandler,
)
var ServiceSet = wire.NewSet(
service.NewUserService,
service.NewAdminService,
)
var RepositorySet = wire.NewSet(
repository.NewUserRepository,
repository.NewAdminRepository,
)
Uso de interfaces para testabilidade:
type UserRepository interface {
FindAll() (string, error)
}
type UserService struct {
repo UserRepository // depende da interface
}
// Teste com mock
type mockRepo struct{}
func (m *mockRepo) FindAll() (string, error) {
return "mock data", nil
}
Evitando anti-patterns:
- Dependências circulares: Use interfaces ou refatore para remover ciclos
- Providers muito genéricos: Evite
func() interface{}; prefira tipos concretos - Injeção de configuração global: Passe configs explicitamente via providers
7. Conclusão e Próximos Passos
Wire e Fx resolvem o mesmo problema fundamental — injeção de dependência em Go — mas com filosofias diferentes:
- Wire é para quem valoriza previsibilidade, performance e código gerado legível. Ideal para aplicações onde cada nanossegundo importa e o lifecycle é simples.
- Fx é para quem precisa de gerenciamento automático de ciclo de vida, injeção dinâmica e estrutura modular. Perfeito para servidores, workers e sistemas complexos.
Minha recomendação: comece com Wire se você está construindo uma CLI ou microsserviço simples. Escolha Fx se seu projeto envolve servidores HTTP, graceful shutdown ou múltiplos ciclos de vida. Ambos são ferramentas maduras e bem documentadas.
Para aprofundar, explore os repositórios oficiais e exemplos reais em código aberto. A escolha entre Wire e Fx não é binária — muitos projetos usam ambos em diferentes partes.
Referências
- Documentação oficial do Wire — Repositório oficial com guia de instalação, conceitos e exemplos completos
- Documentação oficial do Fx — Guia completo da Uber sobre Fx, incluindo lifecycle, módulos e boas práticas
- Go Dependency Injection with Wire - Tutorial — Tutorial prático da DigitalOcean com exemplos passo a passo
- Building a REST API with Fx - Medium — Artigo da Strava Engineering mostrando uso real de Fx em produção
- Wire vs Fx: Choosing the Right DI Framework — Análise comparativa detalhada com prós e contras de cada framework
- Exemplo real: Kubernetes client com Fx — Código aberto do Kubernetes usando Fx para injeção de dependência em controllers