说明:新手个人笔记记录
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