Go入门:write依赖注入

说明:新手个人笔记记录

Go 语言常用的依赖注入工具有 google/wire、uber-go/dig、facebookgo/inject

  • wire github:https://github.com/google/wire

基于 write v0.5.0

一: 依赖注入是什么?

在软件设计中,从架构模块到函数方法都存在大大小小的依赖关系,例如: 在初始化service层时,NewService可能依赖DB DAO层, Conf文件等, 不同的关系之间存在的一连串的依赖关系,耦合性很强,这个时候就需要一种东西来解开他们之间的耦合, 怎么结偶呢,只能利用三方力量,把 所有的依赖控制权交给第三方,这种思想被称为 控制反转( IOC inversion of control),这个第三方称为IOC容器。这个过程被叫做依赖注入。

二: 为什么要引入依赖注入?

我的理解: 帮助我们解决依赖关系,很多代码可以自动生成,不需要我们手动维护那些凡人的依赖关系了!

可以看一段代码:(没有引入依赖注入之前的代码)

// main.go

package main

import "fmt"

func main() {
    conf := NewConfig()
    db := NewDB(conf) // DB 依赖 Config
    result := db.Find()
    fmt.Println(result)
}

package main

type Config struct {
    DbSource string
}

func NewConfig() *Config {
    return &Config{
        DbSource: "root:root@tcp(127.0.0.1:3306)/test_db",
    }
}

type DB struct {
    table string
}

func NewDB(cfg *Config) *DB {
    return &DB{table:"test_table"}
}

func (db *DB) Find() string {
    return "db info string"
}

使用wire之前:调用的步骤如下:

  • 首先用 NewConfig 获取 Config 资源
  • 然后 NewDB 获取 DB 资源,这里需要注入 Config 的资源
  • 所以这里的 NewDB 依赖 NewConfig

使用依赖注入后的代码为:

wire.go

//go:build wireinject
// +build wireinject

package main

import (
    "github.com/google/wire"
)

// 调用wire.Build方法传入所有的依赖对象以及构建最终对象的函数得到目标对象
func InitApp() (*App, error) {
    wire.Build(NewConfig, NewDB, NewApp)
    return &App{}, nil // 这里返回值没有实际意义,只需符合函数签名即可,生成的 wire_gen.go 会帮你包装该值
}


上手

Provider:负责创建对象的方法,比如上文中控制反转示例的NewDB(提供DB对象)和NewConfig(提供Config对象)方法。

Injector:负责根据对象的依赖,依次构造依赖对象,最终构造目的对象的方法,比如上文中控制反转示例的InitApp方法。


一个完整的例子:

先看下项目结构:

|--cmd
    |--main.go
    |--wire.go
|--config
    |--app.json
|--internal
    |--config
        |--config.go
    |--db
        |--db.go

config/app.go

{
  "database": {
    "dsn": "root:root@tcp(127.0.0.1:3306)/test_db"
  }
}

internal/config/config.go

package config

import (
    "encoding/json"
    "github.com/google/wire"
    "os"
)

var Provider = wire.NewSet(New) // 将New方法声明为Provider,表示New方法可以创建一个被别人依赖的对象,也就是Config对象

type Config struct {
    Database database `json:"database"`
}

type database struct {
    Dsn string `json:"dsn"`
}

func New() (*Config, error) {
    fp, err := os.Open("../config/app.json")
    if err != nil {
        return nil, err
    }
    defer fp.Close()
    var cfg Config
    if err := json.NewDecoder(fp).Decode(&cfg); err != nil {
        return nil, err
    }
    return &cfg, nil
}

internal/db/db.go

package db

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "github.com/google/wire"
    "wire-example/internal/config"
)

var Provider = wire.NewSet(NewDb) // 将New方法声明为Provider,表示New方法可以创建一个被别人依赖的对象

func NewDb(cfg *config.Config) (db *sql.DB, err error) {
    db, err = sql.Open("mysql", cfg.Database.Dsn)
    if err != nil {
        return
    }
    if err = db.Ping(); err != nil {
        return
    }
    return db, nil
}

cmd/wire.go

//go:build wireinject
// +build wireinject

package main

import (
    "github.com/google/wire"
    "wire-example/internal/config"
    "wire-example/internal/db"
)

// 调用wire.Build方法传入所有的依赖对象以及构建最终对象的函数得到目标对象
func InitApp() (*App, error) {
    // 写法1(参考Kratos框架写法)
    // panic(wire.Build(config.Provider, db.Provider, NewApp))

    // 写法2(参考wire官方文档写法)
    wire.Build(config.Provider, db.Provider, NewApp)
    return &App{}, nil // 这里返回值没有实际意义,只需符合函数签名即可,生成的 wire_gen.go 会帮你包装该值
}

cmd/main.go

package main

import (
    "database/sql"
    "log"
)

type App struct { // 最终需要的对象
    db *sql.DB // db可以自定义命名,*sql.DB 需要和 internal/db/db.go 中 NewDb 方法返回的类型相同
}

func NewApp(db *sql.DB) *App {
    return &App{
        db: db,
    }
}

func main() {
    app, err := InitApp() // 使用 wire 生成的 injector 方法获取app对象
    if err != nil {
        log.Fatal(err)
    }

    // 测试数据库连接
    var version string
    row := app.db.QueryRow("SELECT VERSION()")
    if err := row.Scan(&version); err != nil {
        log.Fatal(err)
    }
    log.Println(version)
}

注意事项:

// +build wireinject 是什么

用于告诉编译器无需编译该文件。在injector的签名定义函数中,通过调用wire.Build方法,指定用于生成依赖的provider

+build wireinject 和 package main 之间,建议空一行,否则容易报错。

参考:https://github.com/mailjobblog/dev_go/tree/master/220512-DI-wire

你可能感兴趣的:(Go入门:write依赖注入)