前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang依赖注入提升开发效率!

Golang依赖注入提升开发效率!

作者头像
腾讯云开发者
发布2022-08-26 16:33:39
1.2K0
发布2022-08-26 16:33:39
举报
文章被收录于专栏:【腾讯云开发者】

导语 | 依赖注入并不是java独有的,也不是web框架独有的,本文用通俗易懂的语言讲解什么是依赖注入,为什么需要依赖注入,以及go语言如何使用依赖注入来提升开发效率。

一、什么依赖注入

依赖注入(Dependency Injection)也叫DI是软件工程的一种设计模式。

二、为什么需要依赖注入

比如我们使用go要开发一个http api服务,这个服务启动需要

  • 读取命令行
  • 读取配置
  • 连接数据库
  • 连接redis
  • 设置回调函数
  • 监听端口

这个服务关闭需要

  • 关闭端口
  • 关闭redis
  • 关闭mysql

你的代码可能看起来像是这样:

代码语言:javascript
复制
configFile := cmdFlags.get("--config")configObject := readConfig(configFile)mysqlConn ,err := mysql.Connect(configObject.mysql)userModel := NewuserModel(mysqlConn)redisConn ,err := redis.Connect(configObject.redis)cache := NewCache(redisConn)httpServer ,err := http.New(configObject.http)httpServer.Router("/user/any",NewUserControl(userModel,cache))httpServer.Start()<-sighttpServer.Stop()redisConn.Close()mysqlConn.Close()

看起来似乎没有问题,userModel使用mysql实现,cache接口使用redis实现,然后封装一个UserControl对象,这是一个简单的例子,假如再复杂一点。

user控制器需要读配置,你又要把配置对象传进去,或者直接读全局变量。

user控制器需要写日志,又要把日志对象传进去,或者读全局变量log.Info(xxx)。

user控制器要连其他HTTP API,又把NewHttpClient()传进去 或者直接干脆建一个全局包。

user控制器要调RPC,又需要一个RPC Client。

user控制器依赖很多其他对象,NewUserControl参数越来越多,于是你觉得没必要传进去,就用全局变量引用,项目慢慢变大。

代码里各种init函数,import全局变量,xx.attr=NewXXX。

不是说不可用,也没问题,就是代码依赖混乱,init加全局变量管理维护起来不是那么容易。

再来看看使用依赖注入伪代码的实现:

代码语言:javascript
复制
ioc := NewContainer()ioc.Inject(configObject)ioc.Inject(mysql.Connect,redis.Connect)ioc.Inject(NewUserModel,NewCache,NewLogger)ioc.Inject(NewUserControl,NewOtherControl)ioc.Invoke(func(httpServer,UserControl){  httpServer.Router("/user/any",UserControl)})ioc.Invoke(func(httpServer,OtherControl){  httpServer.Router("/other/any",OtherControl)})ioc.Start()<- sigioc.Stop()

这是一段伪代码大概能说明意思,ioc是一个容器Inject方法传递多个构造方法,告诉容器如何创建对象Invoke方法传递多个函数,函数参数可为容器内所有对象,告诉容器如何使用对象 这就是依赖注入,好处是:

  • 对象的创建和使用解耦(一般创建都交给了框架或者自己扩展的模块)。
  • 不用自己创建和组装,不用关心依赖创建顺序,根据使用顺序自动推导。

三、golang依赖注入开源库

facebook的inject基于反射,运行时注入。

google的wire基于AST,编译期注入。

uber的dig fx基于反射,运行时注入。

inject功能有点弱,也不维护了,wire有点抽象,没仔细研究。下面主要介绍一下fx的使用,以及如何使用fx封装一个开发框架。

四、fx framework

An application framework for Go that:

  • Makes dependency injection easy
  • Eliminates the need for global state and func init()

先来看fx的一个官方例子:

代码语言:javascript
复制
package main
import (  "context"  "log"  "net/http"  "os"  "time"
  "go.uber.org/fx"  "go.uber.org/fx/fxevent")
func NewLogger() *log.Logger {  logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)  logger.Print("Executing NewLogger.")  return logger}
func NewHandler(logger *log.Logger) (http.Handler, error) {  logger.Print("Executing NewHandler.")  return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {    logger.Print("Got a request.")  }), nil}func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
  mux := http.NewServeMux()  server := &amp;http.Server{    Addr: &nbsp; &nbsp;":8080",    Handler: mux,  }  lc.Append(fx.Hook{    OnStart: func(context.Context) error {      go server.ListenAndServe()      return nil    },    OnStop: func(ctx context.Context) error {      logger.Print("Stopping HTTP server.")      return server.Shutdown(ctx)    },  })  return mux}func Register(mux *http.ServeMux, h http.Handler) {  mux.Handle("/", h)}
func main() {  //app是一个容器  //fx.Provider告诉容器如何创建对象,内置一个fx.Lifeycle对象可注册启动和关闭回调函数  //fx.Invoke告诉容器如何使用对象  app := fx.New(    fx.Provide(      NewLogger,      NewHandler,      NewMux,    ),    fx.Invoke(Register),    fx.WithLogger(      func() fxevent.Logger {        return fxevent.NopLogger      },    ),  )
  startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)  defer cancel()  if err := app.Start(startCtx); err != nil {    log.Fatal(err)  }
  if _, err := http.Get("http://localhost:8080/"); err != nil {    log.Fatal(err)  }  stopCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)  defer cancel()  if err := app.Stop(stopCtx); err != nil {    log.Fatal(err)  }}

五、fx的使用提效viego

fx详细的使用方式可查看我基于fx,zap,cobra,viper写的一个开发框架viego源码。

viego的核心就是负责根据配置文件创建对象或模块。用户只需要配置一下配置文件就可以使用viego创建的模块进行扩展业务模块,可开发http,grpc服务,或cli命令行。

简单谈一下框架设计理念和原理

程序是由一堆对象组成的,每个对象都有自己的生命周期(启动,停止),每个对象都有自己的依赖和被别人依赖。

第一个对象肯定是静态对象(0依赖),然后其他的对象以这个根对象扩展为射线结构,最终所有对象连起来构成一幅对象图。

大多数程序的第一个对象肯定都是命令行参数

如图所示,对象创建和组装,启动关闭是一个项很费时费力的工作,viego使用fx解决了这个问题,并且扩展了一些后台开发常用的模块,每个模块提供几个有用的对象供用户直接使用,用户只需要关心业务逻辑回调绿色部分的内容即可,需要打日志就引入日志记录器,需要读配置就引入配置文件结构,要什么用什么 ,不用关心这个对象的创建和销毁过程。

内置模块列表

viego模块列表目前内置了一些基础的功能,比如:

  • 创建命令行命令
  • 读取配置文件
  • 创建日志对象
  • 创建http server
  • 创建grpc server
  • 创建http client
  • 创建grpc client
  • 创建北极星服务注册和服务发现
  • 创建mysql对象
  • 创建redis对象

比如举一个例子,你的程序需要使用打日志,只需要配置logger配置就可以config/server.yaml

代码语言:javascript
复制
logger:  #这里面的配置是zap原生的日志配置,viego读取配置创建*zap.Logger对象  default: |    level: debug    outputPaths: ["stdout"]    encoding: color    encoderConfig:       timeEncoder:         layout: 2006-01-02 15:04:05.000000

main.go

代码语言:javascript
复制
package main
import (  "git.woa.com/yangynyang/viego"  "git.woa.com/yangynyang/viego/module/logger"  "go.uber.org/zap")func main() {  viego.Run(    viego.WithName("_test"),    viego.WithOption(      fx.Invoke(func(lgdefault *zap.Logger,lgget logger.Get){        //lgdefault == lgget("default")        //lgget是一个函数可以通过配置文件logger下面的key获取不同的日志记录器对象      }),    )  }

main.go模版

代码语言:javascript
复制
func main() {  viego.Run(    viego.WithName("_test"),    viego.WithOption(      fx.Provider(自己的构造方法),      fx.Invoke(func(自己注册的对象+viego内置对象+fx内置对象...){
      }),    )  }

因为本人也不是专业开发,业务场景也比较少,在设计上肯定有很多不足,比如中间件和插件设计,一个人精力有限,欢迎有兴趣的读者使用,一起探索交流。

 作者简介

杨洋

腾讯业务运维工程师

腾讯业务运维工程师,目前负责英雄联盟手游/英雄联盟电竞经理运维和工具开发工作,擅长云原生运维开发技术。

 推荐阅读

带你畅游k8s调度器!

c++异步:asio的scheduler实现!

甜skr人!程序员专属七夕表白神器,成功率100%

浅谈函数调用!

8 月 20 日,「低代码究竟是“银弹”还是“泡沫”」TVP 低代码技术分享会,即将重磅来袭!

扫码立即参会赢好礼👇

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯云开发者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档