Skip to content

最佳实践 ​

遵循最佳实践可以更好地使用 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