最佳实践
遵循最佳实践可以更好地使用 Wire 进行依赖注入。
项目结构
推荐目录结构
myapp/
├── cmd/
│ ├── server/
│ │ ├── main.go
│ │ ├── wire.go # Injector 定义
│ │ └── wire_gen.go # 生成代码
│ └── worker/
│ │ ├── main.go
│ │ ├── wire.go
│ │ └── wire_gen.go
├── internal/
│ ├── conf/
│ │ ├── conf.go
│ │ └── wire.go # Provider Set
│ ├── data/
│ │ ├── data.go
│ │ ├── repo.go
│ │ └── wire.go # Provider Set
│ ├── biz/
│ │ ├── biz.go
│ │ ├── service.go
│ │ └── wire.go # Provider Set
│ └── server/
│ │ ├── server.go
│ │ ├── http.go
│ │ └── wire.go # Provider Set
└── pkg/
└── log/
├── log.go
└── wire.go # Provider Set每层定义 Provider Set
go
package data
import "github.com/google/wire"
var ProviderSet = wire.NewSet(
NewData,
NewUserRepo,
NewOrderRepo,
)
package biz
import "github.com/google/wire"
var ProviderSet = wire.NewSet(
NewUseCase,
NewUserService,
NewOrderService,
)Provider 设计原则
清晰命名
go
// 推荐:使用 NewXXX 命名
func NewDatabase(cfg *Config) (*Database, error) {
// ...
}
func NewUserService(repo UserRepository) *UserService {
// ...
}
// 不推荐:模糊命名
func CreateDB(cfg *Config) (*Database, error) {
// ...
}单一职责
go
// 推荐:每个 Provider 只创建一个组件
func NewDatabase(cfg *Config) (*Database, error) {
return &Database{cfg: cfg}, nil
}
func NewRepository(db *Database) Repository {
return &MySQLRepository{db: db}
}
// 不推荐:一个 Provider 创建多个组件
func NewDataLayer(cfg *Config) (*Database, Repository, error) {
db, err := NewDatabase(cfg)
repo := NewRepository(db)
return db, repo, err
}接口优先
go
package biz
type UserRepository interface {
FindByID(id int64) (*User, error)
Save(user *User) error
}
type UserService struct {
repo UserRepository // 使用接口而非具体实现
}
package data
type MySQLUserRepository struct {
db *Database
}
var ProviderSet = wire.NewSet(
NewMySQLUserRepository,
wire.Bind(new(biz.UserRepository), new(*MySQLUserRepository)),
)Injector 设计原则
简洁 Injector
go
// 推荐:简洁的 Injector
func InitializeApp() (*App, func(), error) {
wire.Build(
conf.ProviderSet,
data.ProviderSet,
biz.ProviderSet,
server.ProviderSet,
NewApp,
)
return nil, nil, nil
}
// 不推荐:复杂的 Injector
func InitializeApp() (*App, func(), error) {
wire.Build(
conf.NewConfig,
data.NewDatabase,
data.NewMySQLUserRepository,
wire.Bind(new(biz.UserRepository), new(*data.MySQLUserRepository)),
biz.NewUserService,
biz.NewOrderService,
server.NewHTTPServer,
NewApp,
)
return nil, nil, nil
}分离 Injector 文件
go
// wire.go 单独文件
//+build wireinject
package main
import (
"github.com/google/wire"
"myapp/internal/conf"
"myapp/internal/data"
"myapp/internal/biz"
"myapp/internal/server"
)
func InitializeApp() (*App, func(), error) {
wire.Build(
conf.ProviderSet,
data.ProviderSet,
biz.ProviderSet,
server.ProviderSet,
NewApp,
)
return nil, nil, nil
}错误处理
Provider 错误处理
go
func NewDatabase(cfg *Config) (*Database, func(), error) {
if cfg.Host == "" {
return nil, nil, errors.New("database host is required")
}
db, err := sql.Open("postgres", cfg.ConnectionString())
if err != nil {
return nil, nil, fmt.Errorf("open database: %w", err)
}
if err := db.Ping(); err != nil {
return nil, nil, fmt.Errorf("ping database: %w", err)
}
cleanup := func() {
if err := db.Close(); err != nil {
log.Printf("close database: %v", err)
}
}
return &Database{DB: db}, cleanup, nil
}Injector 调用错误处理
go
func main() {
app, cleanup, err := InitializeApp()
if err != nil {
log.Fatalf("初始化应用失败: %v", err)
}
defer cleanup()
if err := app.Run(); err != nil {
log.Printf("运行应用失败: %v", err)
}
}资源清理
清理函数设计
go
func NewDatabase(cfg *Config) (*Database, func(), error) {
db, err := sql.Open("postgres", cfg.DSN)
if err != nil {
return nil, nil, err
}
cleanup := func() {
// 清理逻辑
db.Close()
log.Println("database closed")
}
return &Database{DB: db}, cleanup, nil
}
func NewRedis(cfg *Config) (*Redis, func(), error) {
client := redis.NewClient(&redis.Options{
Addr: cfg.RedisAddr,
})
cleanup := func() {
client.Close()
log.Println("redis closed")
}
return &Redis{Client: client}, cleanup, nil
}清理顺序
Wire 生成的清理函数按相反顺序调用。
go
// 生成的代码
func InitializeApp() (*App, func(), error) {
db, cleanup1, err := NewDatabase(config)
if err != nil {
return nil, nil, err
}
redis, cleanup2, err := NewRedis(config)
if err != nil {
cleanup1() // 先清理 db
return nil, nil, err
}
app := NewApp(db, redis)
return app, func() {
cleanup2() // 先清理 redis
cleanup1() // 再清理 db
}, nil
}测试友好设计
接口设计
go
type UserRepository interface {
FindByID(id int64) (*User, error)
Save(user *User) error
}
type UserService struct {
repo UserRepository // 可以注入 Mock 实现
}Mock 实现
go
type MockUserRepository struct {
mock.Mock
}
func (m *MockUserRepository) FindByID(id int64) (*User, error) {
args := m.Called(id)
return args.Get(0).(*User), args.Error(1)
}
func (m *MockUserRepository) Save(user *User) error {
args := m.Called(user)
return args.Error(0)
}
var MockRepoSet = wire.NewSet(
func() UserRepository { return &MockUserRepository{} },
)
func InitializeTestApp() (*App, func(), error) {
wire.Build(
MockRepoSet,
biz.ProviderSet,
server.ProviderSet,
NewApp,
)
return nil, nil, nil
}环境管理
多环境配置
go
package conf
type Config struct {
Env string
DB DatabaseConfig
}
func NewDevConfig() *Config {
return &Config{
Env: "dev",
DB: DatabaseConfig{Host: "localhost", Port: 5432},
}
}
func NewProdConfig() *Config {
return &Config{
Env: "prod",
DB: DatabaseConfig{Host: "prod-db", Port: 5432},
}
}
var DevConfigSet = wire.NewSet(NewDevConfig)
var ProdConfigSet = wire.NewSet(NewProdConfig)
func InitializeDevApp() (*App, func(), error) {
wire.Build(
DevConfigSet,
data.ProviderSet,
biz.ProviderSet,
server.ProviderSet,
NewApp,
)
return nil, nil, nil
}
func InitializeProdApp() (*App, func(), error) {
wire.Build(
ProdConfigSet,
data.ProviderSet,
biz.ProviderSet,
server.ProviderSet,
NewApp,
)
return nil, nil, nil
}依赖图优化
避免重复 Provider
go
// 推荐:每个类型只有一个 Provider
var ProviderSet = wire.NewSet(
NewDatabase,
NewUserRepo,
)
// 不推荐:重复 Provider
var ProviderSet = wire.NewSet(
NewDatabase,
NewUserRepo,
NewDatabase, // 重复
)合理拆分 Provider Set
go
// 推荐:按功能拆分
var CoreSet = wire.NewSet(
NewConfig,
NewDatabase,
)
var RepoSet = wire.NewSet(
NewUserRepo,
NewOrderRepo,
)
var ServiceSet = wire.NewSet(
NewUserService,
NewOrderService,
)
// 按需组合
var AppSet = wire.NewSet(
CoreSet,
RepoSet,
ServiceSet,
)文档和注释
Provider 注释
go
// NewDatabase 创建数据库连接
// 参数: cfg - 配置对象
// 返回: Database - 数据库实例
// 返回: func() - 清理函数
// 返回: error - 错误信息
func NewDatabase(cfg *Config) (*Database, func(), error) {
// ...
}Provider Set 注释
go
// DataSet 提供数据层组件
// 包含:数据库连接、Repository 实现
var DataSet = wire.NewSet(
NewDatabase,
NewUserRepository,
NewOrderRepository,
)代码生成管理
使用 go:generate
go
// wire.go
//go:generate go run github.com/google/wire/cmd/wire
//+build wireinject
package main
func InitializeApp() (*App, func(), error) {
wire.Build(...)
return nil, nil, nil
}Makefile 管理
makefile
.PHONY: wire
wire:
go generate ./cmd/server/...
go generate ./cmd/worker/...
.PHONY: build
build: wire
go build -o bin/server ./cmd/server
go build -o bin/worker ./cmd/worker检查清单
| 检查项 | 说明 |
|---|---|
| Provider 命名 | 使用 NewXXX 格式 |
| 接口使用 | 依赖使用接口而非具体实现 |
| 错误处理 | Provider 返回错误处理失败情况 |
| 清理函数 | 资源类 Provider 返回清理函数 |
| Provider Set | 每层定义独立的 Provider Set |
| Injector 简洁 | Injector 使用 Provider Set 组合 |
| 分离文件 | wire.go 和 wire_gen.go 分离 |
| 文档注释 | Provider 和 Set 添加注释 |
| 测试友好 | 支持 Mock 依赖注入 |
| 环境管理 | 不同环境使用不同配置 |
参考:https://github.com/google/wire/blob/main/docs/best-practices.md