选型Go项目过程中,针对依赖注入方式的分析和使用
参考资料
- https://go.dev/blog/wire
- https://medium.com/@dche423/master-wire-cn-d57de86caa1b
- https://toutiao.io/posts/et0t2lk/preview
- https://imlht.com/archives/223/
- https://lailin.xyz/post/go-training-week4-wire.html
- https://luenci.me/2022/01/08/%E8%81%8A%E8%81%8AWire%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5/
- http://c.biancheng.net/view/78.html
- https://zhuanlan.zhihu.com/p/338926709
- https://github.com/DuanJiaNing/thewaytowire
wire与其他同类工具的对比
Wire 是一个轻巧的Golang依赖注入工具。它由Go Cloud团队开发,通过自动生成代码的方式在编译期完成依赖注入。
依赖注入是保持软件 “低耦合、易维护” 的重要设计准则之一
同类型哦工具有:来自Uber 的 dig 、来自Facebook 的 inject,下边我们只对比下wire和dig
wire和dig的区别
uber 推出的依赖注入库,采用反射,在运行时计算依赖关系,构造依赖对象。
上面简单介绍了wire 和 dig 两者之间的特点:
1、dig 通过反射识别依赖关系,wire 是编译前计算依赖关系
2、dig 只能在代码运行时,才能知道哪个依赖不对,比如构造函数返回类型的是结构体指针,但是其他依赖的是interface,这样的错误只能在运行时发现,而wire可以在编译的时候就发现。
3、由于采用了依赖注入,所以在代码调试时可以注入一些mock 服务或者函数,wire在mock上支持更友好些,dig的话可以通过build tag 来使用mock。 个人比较推荐使用wire,可以在编译时就发现问题,避免了 多次的build和尝试后才解决编译问题。更多的使用方式和最佳实践,可以参考官方文档。
根据依赖倒置原则(Dependence Inversion Principle),对象应当依赖于接口,而不是直接依赖于具体实现。
使用wire的优势:
- 方便debug,若有依赖缺失编译时会报错
- 因为不需要 Service Locators, 所以对命名没有特殊要求
- 避免依赖膨胀。生成的代码只包含被依赖的代码,而运行时依赖注入则无法作- 到这一点
- 依赖关系静态存于源码之中, 便于工具分析与可视化
wire 中的两个核心概念: Provider 和 Injector
provider: a function that can produce a value. These functions are ordinary Go code.
injector: a function that calls providers in dependency order. With Wire, you write the injector’s signature, then Wire generates the function’s body.
- Provider 是一个生成组件的普通函数,这个函数会返回构建依赖关系所需的组件。
- Injector 是很多个 Provider 组装在一起的时候,可以得到一个管理对象,由wire自动生成的函数。函数内部会按根据依赖顺序调用相关privoder 。
使用wire实战
安装
- 一、仓库地址:https://github.com/google/wire
- 二、使用方法:
- 1、执行 go get github.com/google/wire/cmd/wire 命令
- 2、执行 go install github.com/google/wire/cmd/wire@latest 命令
- 3、确认gopath中bin是否存在wire.exe或wire
- 三、我们的代码目录结构
route -->> controller–> service -->>Idao -->>dao ->>model
用法效果对比
wire 依赖注入方式想要达到的效果:
我们有一个主函数:
func main() {
message := NewMessage()
greeter := NewGreeter(message)
event := NewEvent(greeter)
event.Start()
}
我们改成这样:
func main() {
event := InitializeEvent()
event.Start()
}
代码目录结构
├── routes
│ ├── init_server.go
│ ├── routes.go
│ ├── wire.go
│ └── wire_gen.go
第一步:编写wire.go
//go:build wireinject
// +build wireinject
package routes
import (
"gdonline_backend/databases/redis"
"github.com/google/wire"
"github.com/rs/zerolog"
"gorm.io/gorm"
"gdonline_backend/internal/controller"
"gdonline_backend/internal/dao"
"gdonline_backend/internal/service"
)
var chpoPhenotype = wire.NewSet(
dao.NewChpoPhenotypeDao,
wire.Bind(new(dao.IChpoPhenotypesDao), new(*dao.ChpoPhenotypesDao)),
service.NewChpoPhenotypeService,
controller.NewChpoPhenotypesController,
)
func InitChpoPhenotypes(db *gorm.DB, log *zerolog.Logger) *controller.ChpoPhenotypesController {
panic(wire.Build(chpoPhenotype))
}
第二步:wire
第三步:生成 wire_gen.go
// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package routes
import (
"gdonline_backend/databases/redis"
"gdonline_backend/internal/controller"
"gdonline_backend/internal/dao"
"gdonline_backend/internal/service"
"github.com/google/wire"
"github.com/rs/zerolog"
"gorm.io/gorm"
)
// Injectors from wire.go:
func InitChpoPhenotypes(db *gorm.DB, log *zerolog.Logger) *controller.ChpoPhenotypesController {
chpoPhenotypesDao := dao.NewChpoPhenotypeDao(db, log)
chpoPhenotypesService := service.NewChpoPhenotypeService(chpoPhenotypesDao)
chpoPhenotypesController := controller.NewChpoPhenotypesController(chpoPhenotypesService)
return chpoPhenotypesController
}
// wire.go:
var chpoPhenotype = wire.NewSet(dao.NewChpoPhenotypeDao, wire.Bind(new(dao.IChpoPhenotypesDao), new(*dao.ChpoPhenotypesDao)), service.NewChpoPhenotypeService, controller.NewChpoPhenotypesController)
第四步:main.go 中调用
# routes>init_server.go
func InitServer() {
// 初始化数据库连接
db := mysql.DB
redisDB := redis.DB
zeroLog := logging.NewZerolog("business")
// 注册服务-wire
cc = InitChpoPhenotypes(db, zeroLog)
}
# main.go
routes.InitServer()
第五步:实际调用的路由
func Routes(r *gin.Engine) {
// 设置路由组: v1
v1 := r.Group("v1")
chpoPhenotype := v1.Group("chpo_phenotypes")
// 知识库管理
chpoPhenotype.GET("lists", cc.Lists) // 列表接口
chpoPhenotype.GET("detail", cc.Detail) // 获取表型详情
chpoPhenotype.POST("add", cc.Add) // 新增
chpoPhenotype.POST("edit", cc.Edit) // 编辑
chpoPhenotype.POST("delete", cc.Delete) // 删除
}