Wire en Go: dependency injection en tiempo de compilación con providers e injectors

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.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP