GoLang之使用uber-go/dig进行依赖注入

GoLang之使用uber-go/dig斤进行依赖注入

注:本文是基于Windos系统上Go SDK v1.8、 github.com/uber-go/dig@v1.14.1进行讲解;

1.依赖输注入介绍

本文介绍在golang中如何通过依赖注入(Dependency Inject,简称DI)管理全局服务。
DI是把有依赖关系的类放到容器中,解析出这些类的实例,就是依赖注入。

2.main函数反面例子

直接看,你会发现main()包含清晰的初始化流程。
但是仔细想想,随着业务的扩展,我们如果把基础服务的所有实例都在main函数里生成,即 config、db、person、Repository、server等,main函数将变得越来越臃肿。
而且如果有其他方法使用上面的实例时,需要通过参数的方式传递给方法所在包,例如:service.NewPersonService(config, personRepository),需要将config、personRepository传递给service包

func main() {
 // 生成config实例
 config := NewConfig()
 // 连接数据库
 db, err := ConnectDatabase(config)
 // 判断是否有错误
 if err != nil {
  panic(err)
 }
 // 生成repository实例,用于获取person数据,参数是db
 personRepository := repo.NewPersonRepository(db)
 // 生成service实例,用于调用repository的方法
 personService := service.NewPersonService(config, personRepository)
 // 生成http服务实例
 server := NewServer(config, personService)
 // 启动http服务
 server.Run()
}
// main包下的函数
func NewServer(config *config.Config, service *service.PersonService) *Server {
 return &Server{
  config:        config,
  personService: service,
 }
}
// main包下的函数
func NewConfig() *Config {
    // ...
}
// main包下的函数
func ConnectDatabase(config *config.Config) (*sql.DB, error) {
    // ... 
}
//repo包下的函数
func NewPersonRepository(database *sql.DB) *PersonRepository {
    // ... 
}
//service包下的函数
func NewPersonService(config *config.Config, repository *repo.PersonRepository) *PersonService {
    // ...
}
// Server
type Server struct {
 config        *config.Config
 personService *service.PersonService
}
// Server下的方法,ServerHandler
func (s *Server) Handler() http.Handler {
 mux := http.NewServeMux()

 mux.HandleFunc("/people", s.people)

 return mux
}
//Server下的方法, Run
func (s *Server) Run() {
 httpServer := &http.Server{
  Addr:    ":" + s.config.Port,
  Handler: s.Handler(),
 }

 httpServer.ListenAndServe()
}
// Server下的方法,people
func (s *Server) people(w http.ResponseWriter, r *http.Request) {
 people := s.personService.FindAll()
 bytes, _ := json.Marshal(people)

 w.Header().Set("Content-Type", "application/json")
 w.WriteHeader(http.StatusOK)
 w.Write(bytes)
}

3.下载DI依赖

我使用的是uber的dig包

go get github.com/uber-go/dig

在这里插入图片描述

在这里插入图片描述

4.main函数使用DI优化

这样的main函数不需要包含任何基础实例的初始化和参数传递的过程,可以称之:Perfect!
下面是对main函数里基础服务注入的流程说明:

1)BuildContainer,只将各个基础服务的实例化方法注入到容器里,还没有调用这些方法来实例化基础服务

2)container.Invoke,这里将会从容器里寻找server实例,来运行server.Run()。如果实例不存在,则调用其实例化的方法,NewServer

3)因为
NewServer(config *config.Config, service *service.PersonService) *Server
依赖于config.Config和service.PersonService,
故触发NewConfig、NewPersonService

4)NewConfig不依赖于任何实例,故可以成功返回config.Config实例

5)NewPersonService(config *config.Config, repository *repo.PersonRepository) *PersonService
依赖
config.Config和repo.PersonRepository
,继而触发repo.NewPersonRepository去实例化repo.PersonRepository

6)repo.NewPersonRepository方法依赖于db,
故触发ConnectDatabase方法,用来连接数据库,实例化db实例

7)最后递归倒推回去,完成所有实例的初始化与注入,调用server.Run()方法启动http服务。

注意,有依赖的初始化方法,需要放在前置依赖注入之后,比如container.Provide(ConnectDatabase)就放在container.Provide(NewConfig)之后。如果找不到初始化需要的依赖对象,在Invoke时就会报错。

// 构建一个DI容器
func BuildContainer() *dig.Container {
  container := dig.New()
  // 注入config的实例化方法
  container.Provide(NewConfig)
  // 注入database的实例化方法
  container.Provide(ConnectDatabase)
  // 注入repository的实例化方法
  container.Provide(repo.NewPersonRepository)
  // 注入service的实例化方法
  container.Provide(service.NewPersonService)
  // 注入server
  container.Provide(NewServer)

  return container
}

func main() {
  container := BuildContainer()
  
  err := container.Invoke(func(server *Server) {
    server.Run()
  })

  if err != nil {
    panic(err)
  }
}

5.注意点

之前我通过下面的方式去获取容器里的基础实例:

package app
// Config 配置文件
func Config() (conf *config.Config) {
 _ = container.Invoke(func(c *config.Config) {
  conf = c
 })
 return
}

// 其他package
fmt.Println(app.Config().GetString("someKey"))

这样去获取基础实例是不正确的用法,因为底层是通过一个map来管理这些实例的,我们都知道不是线程安全的,在频繁调用时会出现以下错误:
注:concurret是"同时发生的意思"

在这里插入图片描述

所以,我们在使用注入的时候,将如这样依赖和的实例函数,在main函数里通过注入进去,这样仅调用一次,保证线程安全。d

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoGo在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值