fx 依赖注入的使用
最近手里的一项工作是将老的项目迁移到新的大仓, 且代码规范符合新的要求, 包括依赖注入使用fx, 使用新的devops, 新的监控组件等
借这个机会深入学习了下fx, 在这里做个学习记录, 行文思路完全依据我的学习路径,如有谬误,感谢大佬们指出
1. 为啥需要依赖注入?
没学过Java/Spring的同学第一次接触依赖注入的同学肯定很迷茫, 啥叫依赖注入啊,以前只听过sql注入, 但是两者好像风马牛不相及, 也确实..这两者没啥关系
让我用一句话来讲, 就是省略你一堆的的initXXX函数.
例如,参照以下代码,我想初始化一个服务, 需要各类各样的依赖,数据库,缓存,复杂的业务结构…虽然下述代码看起来并不复杂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func InitDB()*gorm.DB{...} func InitRedis()*redis.Client{...} func InitEs()es *elasticSearch.Client{...} func InitRouter()*Router func InitHttp(r *Router)*Http.Server{ s = &http.Server{...} s.Router = r }
func initExampleService(db *gorm.DB, rds *redis.Client, es *elasticSearch.Client,s *Http.Server, ...){ example.db = db example.redis = rds ... s.serve() }
|
但随着项目复杂度的提升,业务逻辑的层叠, 这一堆init会变越来越多, 代码项目启动入口会变得重复化代码极多, 充斥着大量知识构造struct的语句,
于是在这种背景下, 依赖注入的思想应运而生,如果我只告诉函数需要的类型, 函数就可以自己自己组装需要的结构,岂不代码变清晰了,也节省了很多重复的工作.
依赖注入就是干这个事情的, 你只需要写基本的结构,例如函数定义, 依赖注入自动帮你寻找需要的依赖并构造. 对大型项目非常友好, 但我也认为在并不复杂的项目中引入依赖注入,并没有必要,反而会造成简单逻辑复杂化.
2. 依赖注入的常见方案
1.wire
https://github.com/google/wire
wire是代码生成派的, 告诉wire你要的方法要做什么, wire会帮你生成代码,你再去调用他生成的方法,很简单,不赘述了
文档写的很详细,就不在此重复了,wire.Build()指定好需要new的依赖,一般实践中,我们会将wire的方法定义与生成代码放在同一目录下,方便管理.
文档:https://go.dev/blog/wire
https://github.com/google/wire/blob/main/_tutorial/README.md
2. fx
https://github.com/uber-go/fx
依赖注入通过解耦对象创建与使用,实现控制反转(IoC)。在Java/Spring体系中,注解和IoC容器已成为标配。而在Go语言中,由于缺乏官方依赖注入框架,开发者往往陷入手动注入的泥潭。Fx框架的出现填补了这一空白。
相比于wire的代码生成,fx是通过反射实现注入的,通过类型反射寻找参数,函数和接口.
3. fx 的常用操作
1 2 3 4 5 6
| app := fx.New( fx.Provide(NewService, NewLogger), fx.Invoke(RunServer, InitDB), fx.Lifecycle, )
|
3.1 依赖提供者(fx.Provide)
通过普通函数或匿名函数注册依赖:
1 2 3 4 5 6 7 8
| func NewLogger() (*zap.Logger, error) { config := zap.NewProductionConfig() return config.Build() }
app := fx.New( fx.Provide(NewLogger), )
|
3.2 依赖注入(fx.Invoke)
通过函数参数自动注入依赖:
1 2 3 4 5 6 7 8
| func RunServer(logger *zap.Logger, port int) { logger.Info("Server started", zap.Int("port", port)) }
app := fx.New( fx.Provide(NewLogger), fx.Invoke(RunServer), )
|
3.3. 生命周期管理(fx.Lifecycle)
通过Hook实现精准的资源管控:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| func main() { app := fx.New( fx.Provide(NewDB), fx.Invoke(RegisterHooks), ) }
func RegisterHooks(lc fx.Lifecycle, db *sql.DB) { lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { go db.Migrate() return nil }, OnStop: func(ctx context.Context) error { db.Close() return nil }, }) }
|
3.4 静态配置注入(fx.Supply)
直接注入预定义对象:
1 2 3 4 5 6 7 8 9 10
| var config = struct { Port int `json:"port"` }{ Port: 8080, }
app := fx.New( fx.Supply(config), fx.Invoke(StartServer), )
|
3.5 多实例管理(fx.Annotated)
通过标签区分同名依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| type Logger interface { Log(string) }
fx.Provide( fx.Annotated{ Name: "file", Target: func() Logger { return &FileLogger{} }, }, fx.Annotated{ Name: "console", Target: func() Logger { return &ConsoleLogger{} }, }, )
func ProcessData(logger Logger `name:"console"`) { logger.Log("Processing...") }
|
3.6 参数聚合(fx.In/Out)
简化复杂依赖结构:
1 2 3 4 5 6 7 8 9
| type ServiceParams struct { fx.In DB *sql.DB Logger Logger }
func NewService(p ServiceParams) *Service { return &Service{DB: p.DB, Logger: p.Logger} }
|
4.实战案例:构建RESTful API服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package main
import ( "net/http"
"go.uber.org/fx" "github.com/gin-gonic/gin" )
type Config struct { Port int `json:"port"` }
func main() { app := fx.New( fx.Provide( ProvideConfig, gin.Default, ), fx.Invoke(RegisterRoutes), )
app.Run() }
func ProvideConfig() Config { return Config{Port: 8080} }
func RegisterRoutes(router *gin.Engine) { router.GET("/ping", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) }
|
5.最佳实践与生态扩展
1. 模块化设计(fx.Module)
将功能拆分为独立模块:
1 2 3 4 5 6 7 8 9 10 11
| var AuthModule = fx.Options( fx.Provide(NewAuthService), fx.Invoke(RegisterAuthRoutes), )
func main() { app := fx.New( AuthModule, fx.Invoke(StartServer), ) }
|
总结
Fx框架通过声明式编程模型,将复杂的依赖管理转化为简洁的API调用。无论是基础的服务构建还是复杂的微服务架构,Fx都能提供优雅的解决方案。随着Go语言生态的成熟,Fx必将成为构建可维护系统的利器。建议开发者深入学习其模块化设计和生命周期管理特性,打造高内聚低耦合的应用架构。