Go的依赖注入-wire

Published on in Technology with 0 views and 0 comments
  • 1. 前言

  • 接触 Golang 有一段时间了,发现 Golang 同样需要类似 Java 中 Spring 一样的依赖注入框架。如果项目规模比较小,是否有依赖注入框架问题不大,但当项目变大之后,有一个合适的依赖注入框架是十分必要的。通过调研,了解到 Golang 中常用的依赖注入工具主要有 Inject 、Dig 等。但是今天主要介绍的是 Go 团队开发的 Wire,一个编译期实现依赖注入的工具。

  • 什么是依赖注入

  • 说起就要引出另一个名词( IoC )。IoC 是一种设计思想,其核心作用是降低代码的耦合度。是一种实现且用于解决依赖性问题的设计模式。

  • 在go中,依赖注入是指组件创建的时候,就应该获取该组件所依赖的其他关系,如下代码,要创建一个App示例则需要Config和service结构体。

  • go
    type App struct {
        conf *Config
        httpServer *Server
    }
    
    // 构建 Config
    func InitConfig() *Config {
        return &Config{}
    }
    
    // 构建 Server
    func NewHttpServer() *Server {
        return &Server{}
    }
    
    // 构建 App (内部调用依赖组件的构建函数)
    func NewApp() *App {
        return &App{
            conf: InitConfig(),
            httpServer: NewHttpServer(),
        }
    }
    
    // 构建 App (需由调用者传入依赖组件)
    func NewAppByDI(conf *Config, httpSrv *Server) *App {
        return &App{
            conf: conf,
            httpServer: httpSrv,
        }
    }
    
    func main() {
        // 未使用依赖注入
        app := NewApp()
    
        // 使用依赖注入
        config := InitConfig()
        server := NewHttpServer()
        app := NewAppByDI(config, server)
    }
    
  • 未使用依赖注入的情况下,调用者无法知道 内部使用了 和 ,如果 构建函数 发生变化,假设需要增加一个参数,我们就需要在每个调用 的地方修改代码。

  • 使用依赖注入的情况下,将 和 的构建逻辑与构建 的逻辑分离开,即使 的构建函数 发生了变化,也只需要修改一处代码,但是在构建 之前,需要手动构建 和 ,随着程序推进,这些依赖关系将越来越复杂,这时候就需要依赖注入工具 来帮助我们生成依赖关系。

  • 如果上面的代码不够清晰,再看一个例子

  • javascript
    // NewUserRepositoryV1非依赖注入的写法
    func NewUserRepositoryV1(dbCfg DBConfig, c CacheConfig)*UserRepository{
        db, err := gorm.Open(mysql.Open(dbcfg.DSN))
        if err != nil {
            panic(err)
        }
        ud = dao.NewUserDAO(db)
        uc = cache.NewUserCache(redis.NewClient(&redis.Options{
                Addr: c.Addr,
        }))
        return &UserRepository{
            dao: ud,
            cache: uc,
        }
    }
    
    // NewUserRepository 依赖注入的写法
    func NewUserRepository(d *dao.UserDAO, c *cache.UserCache)*UserRepository{
        return &UserRepository{
            dao: d,
            cache: c,
        }
    }
    
  • 3. wire介绍

    • 1. 基本介绍
      • Wire 是一个的 Google 开源专为依赖注入()设计的代码生成工具,通过自动生成代码的方式在初始编译过程中完成依赖注入。它可以自动生成用于化各种依赖关系的代码,从而帮助我们更轻松地管理和注入依赖关系。
      • 分成两部分,一个是在项目中使用的依赖, 一个是命令行工具。
    • 2. 安装
      • shell
        # 导入项目
        go get -u github.com/google/wire
        
        # 安装命令
        go install github.com/google/wire/cmd/wire
        
  • 4. wire的基本使用

    • 1. 前置代码准备
    • plain text
      wire
      ├── db.go                          # 数据库相关代码
      ├── go.mod                         # Go模块依赖配置文件
      ├── go.sum                         # Go模块依赖校验文件
      ├── main.go                        # 程序入口文件
      ├── repository                     # 存放数据访问层代码的目录
      │   ├── dao                        # 数据访问对象(DAO)目录
      │   │   └── user.go                # 用户相关的DAO实现
      │   └── user.go                    # 用户仓库实现
      ├── wire.go                        # Wire依赖注入配置文件
      
    • 文件:
    • go
      // repository/dao/user.gopackage dao
      
      import "gorm.io/gorm"
      type UserDAO struct {
      	db *gorm.DB
      }
      func NewUserDAO(db *gorm.DB) *UserDAO {
        return &UserDAO{
      		db: db,
        }
      }
      
    • 文件:
    • go
      // repository/user.gopackage repository
      
      import "wire/repository/dao"
      type UserRepository struct {
      	dao *dao.UserDAO
      }
      func NewUserRepository(dao *dao.UserDAO) *UserRepository {
        return &UserRepository{
      		dao: dao,
        }
      }
      
    • 文件:
    • go
      // db.gopackage wire
      
      import ("gorm.io/driver/mysql""gorm.io/gorm")
      func InitDB() *gorm.DB {
        db, err := gorm.Open(mysql.Open("dsn"))
        if err != nil {
          panic(err)
        }
        return db
      }
      
    • 文件:
    • go
      package wire
      
      import ("fmt""gorm.io/driver/mysql""gorm.io/gorm""wire/repository""wire/repository/dao")func main() {// 非依赖注入
      	db, err := gorm.Open(mysql.Open("dsn"))if err != nil {panic(err)}
      	ud := dao.NewUserDAO(db)
      	repo := repository.NewUserRepository(ud)
      	fmt.Println(repo)}
      
    • 2. 使用wire工具生成代码
    • 现在我们已经有了基本的代码结构,接下来我们将使用 工具来生成依赖注入的代码。
    • 首先,确保你已经安装了 工具。
      shell
      # 导入项目
      go get -u github.com/google/wire
      
      # 安装命令
      go install github.com/google/wire/cmd/wire
      
    • 创建wire.go文件,然后进行注入。
      • javascript
        //go:build wireinject
        
        // 上面这个注释是一个构建约束,它告诉 go build 只有在满足条件 wireinject 的情况下才应该构建这个文件。wireinject 是一个特殊的标签,用于指示 wire 工具处理这个文件。
        package wire
        
        import (
        	"github.com/google/wire"
        	"wire/repository"
        	"wire/repository/dao"
        )
        
        func InitRepository() *repository.UserRepository {
        	wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)
        	return new(repository.UserRepository)
        }
        
    • 在wire.go同级目录下执行命令
      • wire
    • wire会在同级目录下生成wire_gen.go文件,生成注入器的具体实现。
  • 4. 高级特性

    • 1. 绑定接口
    • 依赖项注入通常用于绑定接口的具体实现。通过类型标识将输入与输出匹配,因此倾向于创建一个返回接口类型的提供者。然而,这也不是习惯写法,因为Go的最佳实践是返回具体类型。你可以在提供者集中声明接口绑定:
    • go
      type Fooer interface {
          Foo() string
      }
      type MyFooer string
      func (b *MyFooer) Foo() string {
          return string(*b)
      }
      func provideMyFooer() *MyFooer {
          b := new(MyFooer)
          *b = "Hello, World!"
        return b
      }
      type Bar string
      func provideBar(f Fooer) string {
          // f will be a *MyFooer.
        return f.Foo()
      }
      var Set = wire.NewSet(
          provideMyFooer,
          wire.Bind(new(Fooer), new(*MyFooer)),
          provideBar,
      )
      
    • 2. 结构体提供者
      • 可以使用提供的类型构造结构体。使用函数构造一个结构体类型,并告诉注入器应该注入哪个字段。注入器将使用字段类型的提供程序填充每个字段。对于生成的结构体类型,同时提供和。例如,给定以下提供者:
      • go
        type Foo int
        type Bar int
        
        func ProvideFoo() Foo {/* ... */}
        
        func ProvideBar() Bar {/* ... */}
        
        type FooBar struct {
            MyFoo Foo
            MyBar Bar
        }
        
        var Set = wire.NewSet(
            ProvideFoo,
            ProvideBar,
            wire.Struct(new(FooBar), "MyFoo", "MyBar"),
        )
        
      • 的第一个参数是指向所需结构体类型的指针,随后的参数是要注入的字段的名称。可以使用一个特殊的字符串“*”作为快捷方式,告诉注入器注入结构体的所有字段。在这里使用会产生和上面相同的结果。

标题:Go的依赖注入-wire
作者:wangzhaoo
地址:http://www.bangnimang.top/go-wire