golang使用Fx实现自动依赖注入| 青训营笔记

1,901 阅读2分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记。

关于依赖注入是什么不想废话,java里面spring就是大量使用了依赖注入,想要理解依赖注入的思想直接看其他的文章就行,这里直接抛干货。

例子

首先我们在logger.New()里面构造一个zap.Logger对象。

var (
	logger *zap.Logger
	once   sync.Once
)

// 创建新的logger
func New(cfg *config.Config) *zap.Logger {
	once.Do(func() {
		logger, _ = zap.NewProduction()
	})
	return logger
}

我们还需要一个S3ObjectAPI的Interface,他提供PutObject和PresignGetObject的方法。我们有一个s3.New()的构造函数,需要我们提供config和logger两个对象进行构造,返回一个Mys3对象并有对应S3ObjectAPI的方法。

type Mys3 struct {
	s3 *s3.Client
}

var (
	s3Client *Mys3
	once     sync.Once
)

func New(config *config.Config, logger *zap.Logger) S3ObjectAPI {
	if !config.S3.Vaild {
		return nil
	}
	once.Do(func() {
		// your create s3 client code here...
                var client s3.client
		s3Client = &Mys3{
			s3: &client,
		}
	})
	return s3Client
}

type S3ObjectAPI interface {
	PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
	PresignGetObject(ctx context.Context, input *s3.GetObjectInput, optFns ...func(*s3.PresignOptions)) (*v4.PresignedHTTPRequest, error)
}

我们的Service层也有一个service.New()的构造函数,他依赖我们上面构造的Logger对象和S3ObjectAPI接口。

type Service struct {
	cfg      *config.Config
	db       *gorm.DB
	rds      *redis.Client
	logger   *zap.Logger
	producer sarama.AsyncProducer
	s3       s3Object.S3ObjectAPI
}

var (
	service *Service
	once    sync.Once
)

// 启动一个新的service实例,当然是单例模式
func New(cfg *config.Config, db *gorm.DB,
	rds *redis.Client,
	logger *zap.Logger,
	producer sarama.AsyncProducer,
	s3 s3Object.S3ObjectAPI) *Service {
	once.Do(func() {
		service = &Service{
			cfg:      cfg,
			db:       db,
			rds:      rds,
			logger:   logger,
			producer: producer,
			s3:       s3,
		}
	})
	return service
}

这样的对象需要在main.go里面进行手动注入。你需要自己手动处理好他们的依赖关系和构建对象的顺序。

// 这不用依赖注入框架真的好吗。。。
func main() {
	cfg, err := config.Phase()
	if err != nil {
		panic(err)
	}
	mylogger := logger.New(cfg)
	db := mysql.New(cfg, mylogger)
	rds := redis.New(cfg, mylogger)
	pdc := kafka.NewProducer(cfg)
	s3 := s3.New(cfg, mylogger)
	ser := service.New(cfg, db, rds, mylogger, pdc, s3)
	ctl := controller.New(ser, mylogger)
	httpserver.Run(cfg, ctl, mylogger)
}

对于青训营的demo版本的抖音项目来说,这个依赖注入的数量已经比较复杂了。进入真正的商业化项目的开发后,依赖的数量只会比这个更多,届时管理各个对象之间的依赖关系会极大的加重开发者的心理负担。我们的确需要一个工具来帮我们完成依赖注入的任务。

什么是🦄Fx?

Fx是一个Golang下的依赖注入框架,他降低了在Golang中使用依赖注入的难度。 Fx可以和上面的构造函数一起使用而无需嵌入特殊的类型和使用特殊的struct,所以Fx可以轻松的使用在大多数的Go packages中。实际上在青训营项目中,我们也是在后期才引入Fx的。Fx帮我们管理好了对象间的关系,避免了我们在main.go写出臃肿的注入代码。

使用了🦄Fx以后的代码

// 用了 好用
func main() {
	app := fx.New(
		fx.Provide(
			config.Phase,
			logger.New,
			mysql.New,
			redis.New,
			kafka.NewProducer,
			s3.New,
			service.New,
			controller.New,
		),
		fx.Invoke(
			httpserver.Run,
		),
		fx.WithLogger(
			func(logger *zap.Logger) fxevent.Logger {
				return &fxevent.ZapLogger{Logger: logger}
			},
		),
	)
	app.Run()
}

我们把上述的对象的构造函数全部喂给fx.Provice(),然后把最终的http实例直接扔到fx.Invoke()里面,获得一个fx.app,执行这个app的Run方法,应用就跑起来了,实际的效果和上述的main.go的效果是一样的。

参考资料

uber-go/fx: A dependency injection based application framework for Go. (github.com)

fx package - go.uber.org/fx - Go Packages