Golang 依赖注入:入门篇

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介: Golang 依赖注入:入门篇

一、简介


当创建一个实例,并且该实例需要依赖时,就涉及到了依赖注入。 假设我们需要创建一个服务实例,该服务实例需要一个配置项实例。第一种实现思路是在初始化实例时自动创建,该操作是对创建者无感知的。


package main
import "net/http"
type Config struct {
  address string
  port    string
}
type Server struct {
  config *Config
}
func NewServer() *Server {
  return &Server{BuildConfig()}
}
func BuildConfig() *Config {
  return &Config{"127.0.0.1", "8080"}
}
func main() {
  svc := NewServer()
  http.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
    resp.Write([]byte("di"))
  })
  http.ListenAndServe(svc.config.address+":"+svc.config.port, nil)
}

虽然非常方便的创建了 Config 对象,但是存在一个问题,就是不利于扩展,如果想要自己设置 Config 实例,就需要给 BuildConfig 函数传递参数,而该参数可能需要在所有 NewServer 的位置传入。 改造之后的代码是下面这样的。


func NewServer(c *Config) *Server {
  return &Server{c}
}
c := Config{"127.0.0.1", "8080"}
svc := NewServer(&c)

这样就把创建 Config 和创建 Server 的逻辑分离了。但是如果要创建 Server 实例,必须先创建一个 Config 实例。这就形成了依赖关系,依赖图如下。image.png

在实际的应用中,依赖图可能更加复杂。


二、FindPerson 应用


下面来看一个更加复杂的案例。 假设在 mongodb 中有一个 SutraPavilion 数据库,其中有一个 Person 集合,数据格式如下:

image.png

接下来要从头构建一个 Web 应用将其中的数据以 JSON 的格式返回出去。


curl http://127.0.0.1:8080/person
[{"Id":"5fb9e8c780efe11bf021fd35","name":"达摩祖师","age":65535},{"Id":"5fb9ec1880efe11bf021fd36","name":"张三丰","age":1024}]

文件的目录结构如下。


|____mgo           // mongodb 连接方法
| |____mgo.go
|____schema        // 定义数据结构
| |____Person.go
|____controllers   // 定义控制器
| |____person.go
|____main.go       // 主方法
|____services      // 定义业务员逻辑接口
| |____impl        // 实现业务逻辑
| | |____person.go
| |____person.go

在 mgo 中定义 mongodb 的配置项结构体和连接方法。


package mgo
import (
  "context"
  "fmt"
  "go.mongodb.org/mongo-driver/mongo"
  "go.mongodb.org/mongo-driver/mongo/options"
)
type Config struct {
  Host     string
  Port     string
  Username string
  Password string
  Database string
}
func ConnectDatabase(c *Config) (*mongo.Database, error) {
  uri := fmt.Sprintf("mongodb://%s:%s@%s:%s/%s?authSource=admin", c.Username, c.Password, c.Host, c.Port, c.Database)
  clientOptions := options.Client().ApplyURI(uri)
  client, err := mongo.Connect(context.TODO(), clientOptions)
  if err != nil {
    panic(err)
  }
  db := client.Database(c.Database)
  return db, err
}

在 schema 中定义 person 的结构。


package schema
type Person struct {
  Id   string `bson:"_id"`
  Name string `json:"name"`
  Age  int    `json:"age"`
}

在 services 中定义服务接口。


package services
import (
  "../schema"
)
type Person interface {
  FindAll() []*schema.Person
}

在 impl 中实现接口。


package impl
import (
  "context"
  "log"
  "../../schema"
  "go.mongodb.org/mongo-driver/bson"
  "go.mongodb.org/mongo-driver/mongo"
)
type Person struct {
  Db *mongo.Database
}
func (p Person) FindAll() []*schema.Person {
  var result []*schema.Person
  cur, err := p.Db.Collection("person").Find(context.TODO(), bson.D{{}})
  if err != nil {
    log.Fatal(err)
  }
  for cur.Next(context.TODO()) {
    var elem schema.Person
    err := cur.Decode(&elem)
    if err != nil {
      log.Fatal(err)
    }
    result = append(result, &elem)
  }
  return result
}

在 controllers 中定义 FindAll 方法。


package controllers
import (
  "encoding/json"
  "net/http"
  "../services"
)
type Person struct {
  Service services.Person
}
func (p *Person) FindAll(w http.ResponseWriter, r *http.Request) {
  people := p.Service.FindAll()
  bytes, _ := json.Marshal(people)
  w.Header().Set("Content-Type", "application/json")
  w.WriteHeader(http.StatusOK)
  w.Write(bytes)
}

最终在 main 函数中定义 Server 结构体。


package main
import (
  "net/http"
  "./controllers"
  "./mgo"
  "./services/impl"
)
type ServerConfig struct {
  Host string
  Port string
}
type Server struct {
  serverConfig *ServerConfig
  routes       *map[string]http.HandlerFunc
}
func (svc *Server) Run() {
  for path := range *svc.routes {
    http.HandleFunc(path, (*svc.routes)[path])
  }
  http.ListenAndServe(svc.serverConfig.Host+":"+svc.serverConfig.Port, nil)
}
func main() {
  // 创建 mongodb 配置
  mgoCfg := mgo.Config{
    Host:     "127.0.0.1",
    Port:     "27017",
    Username: "admin",
    Password: "UgOnvFDYyxZa0PR3jdp2",
    Database: "SutraPavilion",
  }
  Db, _ := mgo.ConnectDatabase(&mgoCfg)
  // 创建 person service
  personService := impl.Person{
    Db: Db,
  }
  // 创建 person controller
  personController := controllers.Person{
    Service: personService,
  }
  // 创建 routes 配置
  routes := make(map[string]http.HandlerFunc)
  routes["/person"] = personController.FindAll
  // 创建 server 配置
  svcCfg := ServerConfig{
    Host: "127.0.0.1",
    Port: "8080",
  }
  svc := Server{&svcCfg, &routes}
  // 启动服务
  svc.Run()
}

可以看到,在 main 函数中创建了大量的配置项实例。依赖图如下所示:image.png

随着应用的复杂度增加,不仅需要维护这些配置项及其依赖关系,而且还可能会有新加入的其他组件。


三、使用 DI 库 Dig 优化代码


dig 是 uber 开源的一款 DI 库。 dig 的实例称为容器(container),其中存储了所有的实例。 contaienr 提供了 Provide 和 Invoke 两个 API。 Provide 需要传入一个函数,该函数的返回值,也就是依赖的实例,会被存储到 container 中。同一种类型的实例仅会被存储一次。如果一个依赖在注入时需要依赖其他实例,container 会自动将所需依赖注入进去,只需要在函数的形式参数中声明即可。 Invoke 需要传入一个函数,该函数和 Provide 类似,可以自动从 container 中获取依赖实例。但是 Invoke 不会注入依赖。


package main
import (
  "net/http"
  "./controllers"
  "./mgo"
  "./services/impl"
  "go.mongodb.org/mongo-driver/mongo"
  "go.uber.org/dig"
)
type ServerConfig struct {
  Host string
  Port string
}
type Router struct {
  routes *map[string]http.HandlerFunc
}
type Server struct {
  serverConfig *ServerConfig
  router       *Router
}
func (svc *Server) Run() {
  for path := range *svc.router.routes {
    http.HandleFunc(path, (*svc.router.routes)[path])
  }
  http.ListenAndServe(svc.serverConfig.Host+":"+svc.serverConfig.Port, nil)
}
func NewMogConfig() *mgo.Config {
  return &mgo.Config{
    Host:     "127.0.0.1",
    Port:     "27017",
    Username: "admin",
    Password: "UgOnvFDYyxZa0PR3jdp2",
    Database: "SutraPavilion",
  }
}
func NewDB(mgoCfg *mgo.Config) *mongo.Database {
  Db, _ := mgo.ConnectDatabase(mgoCfg)
  return Db
}
func NewPersonService(Db *mongo.Database) *impl.Person {
  return &impl.Person{
    Db: Db,
  }
}
func NewPersonController(personService *impl.Person) *controllers.Person {
  return &controllers.Person{
    Service: *personService,
  }
}
func NewRouter(personController *controllers.Person) *Router {
  // 创建 routes 配置
  routes := make(map[string]http.HandlerFunc)
  routes["/person"] = personController.FindAll
  return &Router{&routes}
}
func NewServerConfig() *ServerConfig {
  return &ServerConfig{
    Host: "127.0.0.1",
    Port: "8080",
  }
}
func NewServer(svcCfg *ServerConfig, router *Router) *Server {
  return &Server{svcCfg, router}
}
func BuildContainer() *dig.Container {
  container := dig.New()
  container.Provide(NewMogConfig)
  container.Provide(NewDB)
  container.Provide(NewPersonService)
  container.Provide(NewPersonController)
  container.Provide(NewRouter)
  container.Provide(NewServerConfig)
  container.Provide(NewServer)
  return container
}
func main() {
  container := BuildContainer()
  // 启动服务
  err := container.Invoke(func(server *Server) {
    server.Run()
  })
  if err != nil {
    panic(err)
  }
}

使用依赖注入,可以减少很多 init 函数和全局变量。提高代码可维护性。


四、参考链接


Dependency Injection in Go software is fun

Dependency injection in GO golangforall



相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。   相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
测试技术 Go 开发工具
100天精通Golang(基础入门篇)——第3天:Go语言的执行原理及常用命令、编码规范和常用工具
100天精通Golang(基础入门篇)——第3天:Go语言的执行原理及常用命令、编码规范和常用工具
240 1
|
1月前
|
安全 Java Go
【Golang入门】简介与基本语法学习
Golang语言入门教程,介绍了Go语言的简介、基本语法、程序结构、变量和常量、控制结构、函数、并发编程、接口和类型、导入包、作用域以及错误处理等关键概念,为初学者提供了一个全面的学习起点。
24 0
|
3月前
|
测试技术 Go 开发者
掌握Golang测试:从入门到实践
【8月更文挑战第31天】
58 0
|
5月前
|
Kubernetes Go 云计算
Golang 入门技术文档
**Golang 技术文档摘要:** Golang,由Google开发,是一种静态强类型、编译型语言,广泛应用于云计算、网络编程和分布式系统。本文档介绍了Golang的基础和特性,包括安装配置、 HelloWorld 示例、基本语法,如变量推导、函数多返回值和并发编程(goroutine、channel)。Golang的并发模型基于轻量级goroutine和channel,支持高效并发处理。此外,文档还提及了接口和多态性,展示了如何使用接口实现类型间的交互。Golang在Docker、Kubernetes等项目中得到应用,适用于后端服务开发。【6月更文挑战第9天】
60 1
|
6月前
|
Go 开发者
Golang深入浅出之-Go语言结构体(struct)入门:定义与使用
【4月更文挑战第22天】Go语言中的结构体是构建复杂数据类型的关键,允许组合多种字段。本文探讨了结构体定义、使用及常见问题。结构体定义如`type Person struct { Name string; Age int; Address Address }`。问题包括未初始化字段的默认值、比较含不可比较字段的结构体以及嵌入结构体字段重名。避免方法包括初始化结构体、自定义比较逻辑和使用明确字段选择器。结构体方法、指针接收者和匿名字段嵌入提供了灵活性。理解这些问题和解决策略能提升Go语言编程的效率和代码质量。
119 1
|
6月前
|
Go 索引
Golang深入浅出之-切片(Slices)入门:创建、操作与扩容机制
【4月更文挑战第20天】Go语言中的切片是动态数组,提供灵活的操作和自动扩容。本文介绍了切片的创建(通过`make()`、数组创建和切片字面量)、基本操作(索引访问、切片、赋值追加和遍历)以及扩容机制(首次和后续扩容策略)。此外,还强调了切片与底层数组的关系、切片越界问题、`append()`的使用以及理解切片的关键点,帮助提升Go编程效率和代码质量。
143 0
|
6月前
|
存储 Java 程序员
从Java到Golang入门杂谈
作为一名Java老兵,入门Golang的一些体验和思考记录,本篇只是一些零碎的记录,不打算写成一个Golang入门指南,如果需要入门指南请参考其他文档或者书籍。
150 0
|
Java Go C语言
100天精通Golang(基础入门篇)——第2天:学习Go语言的前世今生:一门强大的编程语言的崛起
100天精通Golang(基础入门篇)——第2天:学习Go语言的前世今生:一门强大的编程语言的崛起
224 1
|
11月前
|
Go 开发者
【Go语言入门100题】009 N个数求和 (20 分) Go语言|Golang
简介: L1-009 N个数求和 (20 分)Go语言|Golang本题的要求很简单,就是求N个数字的和。麻烦的是,这些数字是以有理数分子/分母的形式给出的,你输出的和也必须是有理数的形式。
|
Rust Java Go
100天精通Golang(基础入门篇)——第1天:学习Go语言基本概念
100天精通Golang(基础入门篇)——第1天:学习Go语言基本概念
140 0