商城项目用Go开发,依赖关系一复杂就头疼。Java有Spring自动注入,Go社区的答案是Wire——Google出品的编译时依赖注入工具。
Go为什么需要DI
Go没有注解、没有反射式框架(不推荐用reflect做DI)。项目小的时候,手动在main里new一堆struct然后层层传递还能忍。但当你有20个service、每个service依赖3-5个repo和其他service的时候,main函数会变成这样:
db := initDB()
redis := initRedis()
userRepo := repo.NewUserRepo(db)
productRepo := repo.NewProductRepo(db)
orderRepo := repo.NewOrderRepo(db)
inventoryRepo := repo.NewInventoryRepo(db)
cacheService := service.NewCacheService(redis)
userService := service.NewUserService(userRepo, cacheService)
productService := service.NewProductService(productRepo, inventoryRepo, cacheService)
orderService := service.NewOrderService(orderRepo, productService, userService)
// ... 还有二十行
维护噩梦。加一个依赖要改好几个地方。Wire就是解决这个问题的。
Wire的核心概念
Wire是编译时DI,不是运行时。它通过代码生成来完成注入,没有反射开销,类型安全。
Provider
Provider就是一个普通的Go函数,接受依赖作为参数,返回一个实例:
// 这就是一个Provider
func NewUserService(repo *UserRepo, cache *CacheService) *UserService {
return &UserService{repo: repo, cache: cache}
}
ProviderSet
把相关的Provider打包:
var UserSet = wire.NewSet(
NewUserRepo,
NewUserService,
)
var ProductSet = wire.NewSet(
NewProductRepo,
NewInventoryRepo,
NewProductService,
)
Injector
Injector是你声明"我需要什么"的地方。写一个函数签名,Wire自动生成实现:
//go:build wireinject
func InitializeApp(cfg *config.Config) (*App, error) {
wire.Build(
// 基础设施
InitDB, // func(*config.Config) (*sql.DB, error)
InitRedis, // func(*config.Config) (*redis.Client, error)
// 业务模块
UserSet,
ProductSet,
OrderSet,
// 应用
NewApp,
)
return nil, nil // Wire会替换这个返回值
}
运行wire ./cmd/server/,Wire分析依赖图,生成wire_gen.go:
// Code generated by Wire. DO NOT EDIT.
func InitializeApp(cfg *config.Config) (*App, error) {
db, err := InitDB(cfg)
if err != nil {
return nil, err
}
redisClient, err := InitRedis(cfg)
if err != nil {
return nil, err
}
userRepo := repo.NewUserRepo(db)
cacheService := service.NewCacheService(redisClient)
userService := service.NewUserService(userRepo, cacheService)
// ... 自动排好顺序
app := NewApp(userService, productService, orderService)
return app, nil
}
编译时就确定了所有依赖关系,缺啥直接报编译错误,不会等到运行时才炸。
实际项目中的用法
按模块组织Provider
// internal/user/provider.go
package user
var ProviderSet = wire.NewSet(
repo.NewUserRepo,
service.NewUserService,
handler.NewUserHandler,
)
// internal/product/provider.go
package product
var ProviderSet = wire.NewSet(
repo.NewProductRepo,
repo.NewInventoryRepo,
service.NewProductService,
handler.NewProductHandler,
)
接口绑定
如果Provider返回的是实现类型,但依赖方需要接口:
var ProviderSet = wire.NewSet(
NewMySQLUserRepo,
// 告诉Wire:*MySQLUserRepo 实现了 UserRepository 接口
wire.Bind(new(UserRepository), new(*MySQLUserRepo)),
)
配置分组
用wire.Struct自动注入struct的字段:
type AppConfig struct {
DB *DBConfig
Redis *RedisConfig
Auth *AuthConfig
}
var ConfigSet = wire.NewSet(
LoadDBConfig,
LoadRedisConfig,
LoadAuthConfig,
wire.Struct(new(AppConfig), "*"), // 自动填充所有字段
)
常见问题
Q: Wire和uber/fx有什么区别?
Wire是编译时生成代码,fx是运行时反射注入。Wire更Go-style(类型安全、无反射),fx更灵活(支持生命周期管理)。我选Wire因为编译时检查更安全。
Q: 循环依赖怎么办?
Wire会直接报错。解决方案和其他DI框架一样——通过接口解耦或重新设计模块边界。
Q: Provider可以返回error吗?
可以。Wire会自动处理error传播,如果某个Provider返回了error,整个注入链会正确地往上传递。
Wire用起来有一点学习成本,但在中大型Go项目里绝对值得。比手写依赖初始化代码清晰太多了。