美文网首页
使用 google/wire 对 Go 项目进行依赖注入

使用 google/wire 对 Go 项目进行依赖注入

作者: DJN_ | 来源:发表于2021-01-20 15:38 被阅读0次

    google/wire 是 Go 语言的编译时依赖注入框架,与 Spring IoC 一样,wire 的目的也是让开发者从对项目中大量依赖的创建和管理中解脱出来,但两者在实现方式上有着很大的不同。

    Go 中的依赖注入

    在 Go 中,我们通常采取在构造函数中传入依赖的方式创建对象:

    func main() {
        NewUserStore(conf.Load(),db.InitMySQL())
    }
    
    func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}
    

    这在小规模项目中效果很好,但当项目规模变大时,单个对象的创建往往需要多个依赖,而这些依赖通常还有它自己的依赖,这就导致对象的创建变得繁琐,容易出错。

    wire 如何完成依赖注入

    在开发中,我们创建对象的过程可以分为两步:

    1. 定义结构体的构造函数
    func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}
    
    1. 调用结构体的构造函数进行实例化
    NewUserStore(conf.Load(),db.InitMySQL())
    

    第一步我们声明了构造结构体需要的依赖,而 wire 做的,就是 帮我们“写”好第二步的代码

    依赖声明

    为了生成第二步中的代码,我们首先需要将所有的构造函数(准确的说是所有需要注入的依赖)进行声明并传递给 wire.Build 方法:

    func setup(ctx context.Context) (sv *server.Server, clean func(), err error) {
        wire.Build(
            conf.Load,
            db.InitMySQL,
            userstore.NewUserStore,
        )
        return nil, nil, nil
    }
    

    setup 函数的内容最终会被 wire 用下面的实现替换(接下来会进行说明):

    func setup(ctx context.Context) (sv *server.Server, clean func(), err error) {
        config := conf.Load()
        engine := db.InitMySQL()
        userstoreHandler, clean1, err := userstore.NewUserStore(config,engine)
        if err != nil {
            clean()
            return nil, nil, err
        }
        sv := server.New(userstoreHandler)
        return sv, func() { clean1() }, nil
    }
    

    可以看到上面 wire 为我们“写”好的代码其实跟我们自己将会写的代码是一样的,可以很容易的读懂。但随着依赖增多,这样的代码就会增加,想象一下,如果项目中有上百个依赖,那么就会有上百行的 New...(...), if err != nil {...} 代码。

    使用 go generate 生成依赖注入代码

    wire 就是做了这样一件事:帮我们生成所有需要的对象创建代码,开发时我们只需要在结构体的构造函数中声明自己需要什么。

    wire 在实际项目中的使用步骤:

    1. 通过 wire.Bind 方法进行依赖声明,假设这部分代码放在 inject.go 文件中
    2. 使用 go generate 命令生成代码
      go generate 需要通过 //go:generate 注释的方式使用,创建 wire_gen.go 文件并添加该注释:
    //go:generate wire
    
    1. 在项目目录下执行 go generate 命令。

    运行该命令时,它将扫描与当前包相关的源代码文件,找出所有包含 //go:generate 的特殊注释,提取并执行该特殊注释后面的命令。

    至此,你就可以看到 wire_gen.go 里已经生成好 func setup(ctx context.Context) (sv *server.Server, clean func(), err error) 方法,方法体为依赖注入(对象创建)代码。

    1. 最后一步,在 main 方法中调用 setup 方法(这里假设我们的项目是一个提供 RESTful 接口的 HTTP 服务)
    package main
    
    func main() {
        ctx := context.Background()
        srv, cleanup, err := setupGCP(ctx)
        if err != nil {
            log.Fatalf("failed to setup the server, got error: %s", err)
        }
        defer cleanup()
    
        log.Fatal(srv.ListenAndServe(":8080"))
    }
    

    需要注意的是,如果 inject.gowire_gen.go 在同一个 package 下,那此时 IDE 会提示语法错误,因为两个文件下存在方法签名一样的两个方法。此时可以使用 //+build go build 构建约束对其中一个文件进行排除。

    首先在 inject.go 中加入如下的构建标签:

    //+build wireinject
    

    然后在 wire_gen.go 中加入构建约束使 go build 时排除带 wireinject 标签的文件:

    //+build !wireinject
    

    使用 wire.Bind, wire.Value 等方法声明和组织依赖

    wire 在 go generate 扫描代码时从 wire.Bind 中提取项目依赖关系并为我们生成依赖注入代码,那我们要怎样将依赖关系更高效,清晰的“告知”给 wire 呢?

    wire 提供了几个函数帮助我们组织和声明项目中的依赖关系:

    • wire.Bind: 将接口和其实现进行绑定
    • wire.Value: 将值(实例)包装为依赖
      ...

    更详细的文档可以在 这里 查看。

    如何把 wire 应用到实际项目中

    wire 安装命令:
    go get github.com/google/wire/cmd/wire

    下面是一个在实际项目中用 wire 组织代码,实现依赖注入的完整示例:
    DuanJiaNing/thewaytowire

    相关文章

      网友评论

          本文标题:使用 google/wire 对 Go 项目进行依赖注入

          本文链接:https://www.haomeiwen.com/subject/qqohzktx.html