Wire / fx

Wire Overview

// Wire: compile-time dependency injection for Go
// google.golang.org/wire
//
// Two phases:
//   1. Define providers (how to create each type)
//   2. Define injectors (how to wire them together)
//
// Run `wire` to generate wire_gen.go

//go:build wireinject

package main

Provider Functions

func NewDB(cfg *Config) *sql.DB {
    db, _ := sql.Open("postgres", cfg.DSN)
    return db
}

func NewUserRepo(db *sql.DB) *UserRepo {
    return &UserRepo{db: db}
}

func NewUserService(repo *UserRepo) *UserService {
    return &UserService{repo: repo}
}

func NewLogger() *zap.Logger {
    logger, _ := zap.NewProduction()
    return logger
}

Injector Functions

//go:build wireinject

func InitializeApp(cfg *Config) (*App, error) {
    wire.Build(
        NewDB,
        NewUserRepo,
        NewUserService,
        NewLogger,
        NewApp,
    )
    return nil, nil
}

Wire Build

// wire.go (build tag triggers generation)

//go:build wireinject

package main

import "github.com/google/wire"

func InitializeApp(cfg *Config) (*App, func(), error) {
    wire.Build(
        NewDB,
        NewUserRepo,
        NewUserService,
        NewLogger,
        NewApp,
    )
    return nil, nil, nil
}

// Run: wire ./...
// Produces wire_gen.go with actual implementation

Bind Interfaces

type UserRepository interface {
    GetByID(id int) (*User, error)
    Create(user *User) error
}

type postgresUserRepo struct {
    db *sql.DB
}

var UserRepoSet = wire.NewSet(
    NewPostgresUserRepo,
    wire.Bind(new(UserRepository), new(*postgresUserRepo)),
)

Struct Providers

type App struct {
    DB      *sql.DB
    Service *UserService
    Logger  *zap.Logger
}

func NewApp(db *sql.DB, svc *UserService, log *zap.Logger) *App {
    return &App{DB: db, Service: svc, Logger: log}
}

// wire.Struct can inject fields automatically
var AppSet = wire.NewSet(
    wire.Struct(new(App), "*"),
)

// selective fields
var AppSet2 = wire.NewSet(
    wire.Struct(new(App), "DB", "Service"),
)

Wire Sets

var RepoSet = wire.NewSet(
    NewUserRepo,
    NewOrderRepo,
    NewProductRepo,
)

var ServiceSet = wire.NewSet(
    NewUserService,
    NewOrderService,
)

var InfraSet = wire.NewSet(
    NewDB,
    NewLogger,
    NewCache,
)

func InitializeApp(cfg *Config) (*App, error) {
    wire.Build(
        InfraSet,
        RepoSet,
        ServiceSet,
        NewApp,
    )
    return nil, nil
}

Cleanup Functions

func NewDB(cfg *Config) (*sql.DB, func(), error) {
    db, err := sql.Open("postgres", cfg.DSN)
    if err != nil {
        return nil, nil, err
    }
    cleanup := func() {
        db.Close()
    }
    return db, cleanup, nil
}

func NewRedis(addr string) (*redis.Client, func(), error) {
    rdb := redis.NewClient(&redis.Options{Addr: addr})
    cleanup := func() { rdb.Close() }
    return rdb, cleanup, nil
}

// Wire chains cleanup functions automatically
// The returned func() calls all cleanup functions in reverse order

fx App Lifecycle

func main() {
    fx.New(
        fx.Provide(
            NewDB,
            NewLogger,
            NewUserService,
        ),
        fx.Invoke(StartServer),
        fx.WithLogger(func() fxevent.Logger {
            return &fxevent.ConsoleLogger{W: os.Stderr}
        }),
    ).Run()
}

fx Provide / Invoke

fx.New(
    fx.Provide(
        NewConfig,
        NewDB,
        NewUserRepo,
        NewUserService,
        NewHTTPServer,
    ),
    fx.Invoke(RegisterRoutes),
)

func NewHTTPServer(lc fx.Lifecycle, log *zap.Logger) *http.Server {
    srv := &http.Server{Addr: ":8080"}
    lc.Append(fx.Hook{
        OnStart: func(ctx context.Context) error {
            log.Info("starting server", zap.String("addr", srv.Addr))
            go srv.ListenAndServe()
            return nil
        },
        OnStop: func(ctx context.Context) error {
            log.Info("stopping server")
            return srv.Shutdown(ctx)
        },
    })
    return srv
}

fx Options

fx.New(
    fx.Options(
        fx.Provide(NewDB, NewLogger),
        fx.Invoke(StartServer),
    ),
    fx.Invoke(func(lc fx.Lifecycle) {
        lc.Append(fx.Hook{
            OnStart: func(ctx context.Context) error {
                fmt.Println("app started")
                return nil
            },
            OnStop: func(ctx context.Context) error {
                fmt.Println("app stopped")
                return nil
            },
        })
    }),
)

// Module pattern
var Module = fx.Options(
    fx.Provide(NewUserService, NewUserRepo),
    fx.Invoke(RegisterUserRoutes),
)

fx Logging

fx.New(
    fx.WithLogger(func() fxevent.Logger {
        return &fxevent.ConsoleLogger{W: os.Stderr}
    }),
)

// Custom logger using Zap
fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
    return &fxevent.ZapLogger{Logger: log}
})

// Suppress fx logs in tests
fx.WithLogger(func() fxevent.Logger {
    return fxevent.NopLogger
})

fx Shutdown

func main() {
    app := fx.New(
        fx.Provide(NewDB, NewHTTPServer),
        fx.Invoke(StartServer),
    )

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := app.Stop(ctx); err != nil {
        log.Fatal(err)
    }
}

// Signal-based shutdown (default with fx.New().Run())
// fx.New().Run() handles SIGINT and SIGTERM automatically

Comparison Wire vs fx

Wire:
  - Compile-time DI, errors caught at build time
  - Generates Go code (wire_gen.go)
  - No reflection, no runtime magic
  - Requires //go:build wireinject tag
  - Best for: performance-critical, compile-time safety

fx:
  - Runtime DI using reflection
  - Lifecycle management (OnStart/OnStop hooks)
  - No code generation step
  - Rich logging and visualization
  - Best for: long-running services, apps needing lifecycle hooks

Wire 概述

// Wire:Go 的编译时依赖注入
// google.golang.org/wire
//
// 两个阶段:
//   1. 定义 Provider(如何创建每个类型)
//   2. 定义 Injector(如何将它们组装起来)
//
// 运行 `wire` 生成 wire_gen.go

//go:build wireinject

package main

Provider 函数

func NewDB(cfg *Config) *sql.DB {
    db, _ := sql.Open("postgres", cfg.DSN)
    return db
}

func NewUserRepo(db *sql.DB) *UserRepo {
    return &UserRepo{db: db}
}

func NewUserService(repo *UserRepo) *UserService {
    return &UserService{repo: repo}
}

func NewLogger() *zap.Logger {
    logger, _ := zap.NewProduction()
    return logger
}

Injector 函数

//go:build wireinject

func InitializeApp(cfg *Config) (*App, error) {
    wire.Build(
        NewDB,
        NewUserRepo,
        NewUserService,
        NewLogger,
        NewApp,
    )
    return nil, nil
}

Wire 构建

// wire.go(构建标签触发生成)

//go:build wireinject

package main

import "github.com/google/wire"

func InitializeApp(cfg *Config) (*App, func(), error) {
    wire.Build(
        NewDB,
        NewUserRepo,
        NewUserService,
        NewLogger,
        NewApp,
    )
    return nil, nil, nil
}

// 运行:wire ./...
// 生成 wire_gen.go,包含实际实现

绑定接口

type UserRepository interface {
    GetByID(id int) (*User, error)
    Create(user *User) error
}

type postgresUserRepo struct {
    db *sql.DB
}

var UserRepoSet = wire.NewSet(
    NewPostgresUserRepo,
    wire.Bind(new(UserRepository), new(*postgresUserRepo)),
)

结构体 Provider

type App struct {
    DB      *sql.DB
    Service *UserService
    Logger  *zap.Logger
}

func NewApp(db *sql.DB, svc *UserService, log *zap.Logger) *App {
    return &App{DB: db, Service: svc, Logger: log}
}

// wire.Struct 自动注入字段
var AppSet = wire.NewSet(
    wire.Struct(new(App), "*"),
)

// 选择性字段
var AppSet2 = wire.NewSet(
    wire.Struct(new(App), "DB", "Service"),
)

Wire 集合

var RepoSet = wire.NewSet(
    NewUserRepo,
    NewOrderRepo,
    NewProductRepo,
)

var ServiceSet = wire.NewSet(
    NewUserService,
    NewOrderService,
)

var InfraSet = wire.NewSet(
    NewDB,
    NewLogger,
    NewCache,
)

func InitializeApp(cfg *Config) (*App, error) {
    wire.Build(
        InfraSet,
        RepoSet,
        ServiceSet,
        NewApp,
    )
    return nil, nil
}

清理函数

func NewDB(cfg *Config) (*sql.DB, func(), error) {
    db, err := sql.Open("postgres", cfg.DSN)
    if err != nil {
        return nil, nil, err
    }
    cleanup := func() {
        db.Close()
    }
    return db, cleanup, nil
}

func NewRedis(addr string) (*redis.Client, func(), error) {
    rdb := redis.NewClient(&redis.Options{Addr: addr})
    cleanup := func() { rdb.Close() }
    return rdb, cleanup, nil
}

// Wire 自动链式组合清理函数
// 返回的 func() 按逆序调用所有清理函数

fx 应用生命周期

func main() {
    fx.New(
        fx.Provide(
            NewDB,
            NewLogger,
            NewUserService,
        ),
        fx.Invoke(StartServer),
        fx.WithLogger(func() fxevent.Logger {
            return &fxevent.ConsoleLogger{W: os.Stderr}
        }),
    ).Run()
}

fx Provide / Invoke

fx.New(
    fx.Provide(
        NewConfig,
        NewDB,
        NewUserRepo,
        NewUserService,
        NewHTTPServer,
    ),
    fx.Invoke(RegisterRoutes),
)

func NewHTTPServer(lc fx.Lifecycle, log *zap.Logger) *http.Server {
    srv := &http.Server{Addr: ":8080"}
    lc.Append(fx.Hook{
        OnStart: func(ctx context.Context) error {
            log.Info("starting server", zap.String("addr", srv.Addr))
            go srv.ListenAndServe()
            return nil
        },
        OnStop: func(ctx context.Context) error {
            log.Info("stopping server")
            return srv.Shutdown(ctx)
        },
    })
    return srv
}

fx 选项

fx.New(
    fx.Options(
        fx.Provide(NewDB, NewLogger),
        fx.Invoke(StartServer),
    ),
    fx.Invoke(func(lc fx.Lifecycle) {
        lc.Append(fx.Hook{
            OnStart: func(ctx context.Context) error {
                fmt.Println("app started")
                return nil
            },
            OnStop: func(ctx context.Context) error {
                fmt.Println("app stopped")
                return nil
            },
        })
    }),
)

// 模块模式
var Module = fx.Options(
    fx.Provide(NewUserService, NewUserRepo),
    fx.Invoke(RegisterUserRoutes),
)

fx 日志

fx.New(
    fx.WithLogger(func() fxevent.Logger {
        return &fxevent.ConsoleLogger{W: os.Stderr}
    }),
)

// 使用 Zap 的自定义日志
fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
    return &fxevent.ZapLogger{Logger: log}
})

// 测试中静默 fx 日志
fx.WithLogger(func() fxevent.Logger {
    return fxevent.NopLogger
})

fx 关闭

func main() {
    app := fx.New(
        fx.Provide(NewDB, NewHTTPServer),
        fx.Invoke(StartServer),
    )

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := app.Stop(ctx); err != nil {
        log.Fatal(err)
    }
}

// 基于信号的关闭(fx.New().Run() 默认行为)
// fx.New().Run() 自动处理 SIGINT 和 SIGTERM

Wire vs fx 对比

Wire:
  - 编译时 DI,构建时捕获错误
  - 生成 Go 代码(wire_gen.go)
  - 无反射,无运行时魔法
  - 需要 //go:build wireinject 标签
  - 适用于:性能关键、编译时安全

fx:
  - 基于反射的运行时 DI
  - 生命周期管理(OnStart/OnStop 钩子)
  - 无代码生成步骤
  - 丰富的日志和可视化
  - 适用于:长时间运行的服务、需要生命周期钩子的应用