Wire es una herramienta de Google que resuelve el problema del cableado de dependencias en tiempo de compilación. A diferencia de contenedores IoC que usan reflexión en runtime, Wire genera código Go ordinario que puedes leer y depurar. El resultado es velocidad de arranque instantánea y errores detectados en compilación.
Instalar Wire
go install github.com/google/wire/cmd/wire@latest
Providers: funciones que construyen dependencias
Un provider es cualquier función que devuelve un tipo concreto. Puede tener parámetros que Wire resolverá automáticamente:
// infrastructure/database.go
package infra
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type DBConfig struct {
DSN string
}
func NewDB(cfg DBConfig) (*sql.DB, error) {
db, err := sql.Open("mysql", cfg.DSN)
if err != nil {
return nil, err
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
return db, nil
}
// domain/usuario.go
type UsuarioRepo struct {
db *sql.DB
}
func NewUsuarioRepo(db *sql.DB) *UsuarioRepo {
return &UsuarioRepo{db: db}
}
func (r *UsuarioRepo) BuscarPorID(id int) (*Usuario, error) {
// implementación...
return nil, nil
}
// application/usuario_service.go
type UsuarioService struct {
repo *UsuarioRepo
}
func NewUsuarioService(repo *UsuarioRepo) *UsuarioService {
return &UsuarioService{repo: repo}
}
// http/handler.go
type Handler struct {
svc *UsuarioService
}
func NewHandler(svc *UsuarioService) *Handler {
return &Handler{svc: svc}
}
ProviderSets: agrupar providers relacionados
// infrastructure/wire.go package infra import "github.com/google/wire" var InfraSet = wire.NewSet(NewDB) // domain/wire.go package domain import "github.com/google/wire" var DomainSet = wire.NewSet(NewUsuarioRepo) // application/wire.go package application import "github.com/google/wire" var AppSet = wire.NewSet(NewUsuarioService) // http/wire.go package httphandler import "github.com/google/wire" var HTTPSet = wire.NewSet(NewHandler)
Injector: punto de entrada que Wire genera
El injector es la función que declara qué quieres construir. La escribes con el cuerpo wire.Build(...) y Wire la reemplaza con código real:
// wire.go (en el paquete main o cmd/)
//go:build wireinject
package main
import (
"github.com/google/wire"
"tuapp/application"
"tuapp/domain"
"tuapp/httphandler"
"tuapp/infra"
)
func InicializarApp(cfg infra.DBConfig) (*httphandler.Handler, error) {
wire.Build(
infra.InfraSet,
domain.DomainSet,
application.AppSet,
httphandler.HTTPSet,
)
return nil, nil // Wire substituye esto
}
Ejecuta wire gen ./... en el directorio. Wire crea wire_gen.go con el código real:
// wire_gen.go (generado automáticamente, no editar)
func InicializarApp(cfg infra.DBConfig) (*httphandler.Handler, error) {
db, err := infra.NewDB(cfg)
if err != nil {
return nil, err
}
repo := domain.NewUsuarioRepo(db)
svc := application.NewUsuarioService(repo)
handler := httphandler.NewHandler(svc)
return handler, nil
}
wire.Bind: interfaces con implementaciones concretas
// Interfaz en el dominio
type UsuarioRepository interface {
BuscarPorID(id int) (*Usuario, error)
Guardar(u *Usuario) error
}
// Implementación concreta en infraestructura
type UsuarioRepoDB struct { db *sql.DB }
func NewUsuarioRepoDB(db *sql.DB) *UsuarioRepoDB {
return &UsuarioRepoDB{db: db}
}
// En el ProviderSet: bindear la interfaz con la implementación
var InfraSet = wire.NewSet(
NewUsuarioRepoDB,
wire.Bind(new(UsuarioRepository), new(*UsuarioRepoDB)),
)
Uso en main.go
func main() {
cfg := infra.DBConfig{
DSN: "progra7net:pass@tcp(localhost:3306)/progra7net",
}
handler, err := InicializarApp(cfg)
if err != nil {
log.Fatalf("error inicializando app: %v", err)
}
http.Handle("/api/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
La ventaja de Wire sobre la inyección manual es que el grafo de dependencias queda explícito en el fichero wire.go y los errores (ciclos de dependencia, tipos no resueltos) se detectan cuando ejecutas wire gen, no cuando arranca el servidor en producción.
