Внедрение зависимостей Golang: копать

задняя часть Go
Внедрение зависимостей Golang: копать

Братья этой статьи:Внедрение зависимостей Golang: начало работы

1. Введение

Вопрос о том, нужна ли go библиотека внедрения зависимостей, раньше был предметом многочисленных споров. Требуется ли на самом деле внедрение зависимостей, зависит от стиля программирования. Внедрение зависимостей — это шаблон программирования. больше подходит для объектно-ориентированного программирования,В функциональном программировании вам не нужно. go — это язык, поддерживающий мультипарадигмальное программирование, поэтому при использовании объектно-ориентированногобольшой проект, рекомендуется решить, следует ли использовать режим внедрения зависимостей в соответствии с реальной ситуацией.

2. Основные библиотеки внедрения зависимостей

Внедрение зависимостей реализует очень небольшую вещь, поэтому пакет, который просто реализует внедрение зависимостей, нельзя назвать фреймворком, а можно назвать только библиотекой. Существует множество основных библиотек golang, таких как dig с открытым исходным кодом uber, dingo с открытым исходным кодом elliotchance, di с открытым исходным кодом sarulabs, провод с открытым исходным кодом google, инжект с открытым исходным кодом facebook и т. д. Самые популярные из них — dig и wire.Эта статья в основном знакомит с использованием dig.

3. Основное использование

Создать контейнер

container := dig.New()

Контейнеры используются для управления зависимостями.

внедрить зависимости

Вызовите метод Provide контейнера и передайте фабричную функцию, контейнер автоматически вызовет фабричную функцию, чтобы создать зависимости и сохранить их в контейнере.

type DBClient struct {}

func NewDBClient() {
    return *DBClient{}
}

func InitDB() *DBClient {
    return NewDBClient()
}

container.Provide(InitDB)

Внедренные зависимости будут управляться dig, и каждый тип объекта будет создан только один раз, что можно понимать как синглтон. Если будет введена другая зависимость того же типа, фабричная функция не будет выполнена.

type DBClient struct {}

func NewDBClient() {
    return *DBClient{}
}

func InitDB() *DBClient {
    return NewDBClient()
}

func InitDB2() *DBClient {
    return NewDBClient()
}

container.Provide(InitDB)
container.Provide(InitDB2)// 不会执行

использовать зависимости

Если вам нужно использовать зависимости, используйте метод Invoke контейнера. И определите параметры в формальных параметрах функции, контейнер автоматически внедрит синглтон.

func UseOption(db *DBClient){
	
}

container.Invoke(UseOption)

В-четвертых, объект параметра

Когда функции требуется слишком много зависимостей, параметры можно получить в виде объектов. Если служба запущена, требуются такие параметры, как ServerConfig, MySQL, Redis и Mongodb.

container.Provide(func InitHttpServer(svcfg *ServerConfig, mysql *MySQL, redis *Redis, mongodb *Mongodb) *Server{
    // 业务逻辑
    return Server.Run()
})

В это время читабельность кода станет плохой, а 4 зависимости, от которых зависит InitHttpServer, упаковываются в один объект через dig.In.

type ServerParams {
    dig.In
    
    svcfg   *ServerConfig
    mysql   *MySQL
    redis   *Redis
    mongodb *Mongodb
}

container.Provide(func InitHttpServer(p ServerParams) *Server{
    // 业务逻辑
    return Server.Run()
})

5. Объекты результата

Подобно объектам параметров, та же проблема возникает, если фабричная функция возвращает несколько зависимостей. Однако это относительно редко. Предположим, есть функция, которая возвращает все зависимости, необходимые для запуска InitHttpServer.

container.Provide(func BuildServerdependences() (*ServerConfig, *MySQL, *Redis, *Mongodb){
    // 业务逻辑
    return svcfg, mysql, redis, mongodb
})

Способ решения этого явления аналогичен dig.In, а также есть dig.Out, который используется таким же образом.

type BuildServerdependences struct {
	dig.Out
    
    ServerConfig  *ServerConfig
    MySQL         *MySQL
    Redis         *Redis
    Mongodb       *Mongodb
}

container.Provide(func BuildServerdependences() (*ServerConfig, *MySQL, *Redis, *Mongodb){
    // 业务逻辑
    return BuildServerdependences{
        ServerConfig: svcfg,
        MySQL:        mysql,
        Redis:        redis, 
        Mongodb:      mongodb,
    }
})

6. Необязательные зависимости

dig выдаст исключение, если требуемая зависимость в функции Provide factory или функции Invoke не существует. Предполагая, что в контейнере нет зависимостей типа Mongo.Config, будет выброшено исключение.

func InitDB(cfg *Mongo.Config) *Mongo.Client{
    return Mongo.NewClient(cfg)
}

container.Invoke(InitDB)// 抛出异常

Зависимости можно разрешить не существовать, пометив option как true после тега и передав nil.

type InitDBParams struct {
	dig.In
    
    mongoConfig *Mongo.Config `optional:"true"`
}

func InitDB(p InitDBParams) *Mongo.Client {
    // p.mongoConfig 是 nil
    return Mongo.NewClient(cfg)
}

container.Invoke(InitDB)// 继续执行

7. Именование

Внедрить именованные зависимости

Поскольку по умолчанию используется singleton, что, если потребуются два экземпляра одного типа? Например, сейчас требуются два клиента MongoDB. dig предоставляет функцию именования объектов, которую можно отличить, передав второй параметр при вызове Provide.

type MongodbClient struct {}

func NewMongoClient(cfg *Mongo.Client) *MongodbClient{
    return ConnectionMongo(cfg)
}

container.Provide(NewMongoClient, dig.Name("mgo1"))
container.Provide(NewMongoClient, dig.Name("mgo2"))

В дополнение к передаче параметра dig.Name, если используется объект результата, это может быть достигнуто путем установки тега имени, и эффект тот же.

type MongodbClients struct {
	dig.In
    
    Mgo1 *MongodbClient `name:"mgo1"`
    Mgo2 *MongodbClient `name:"mgo2"`
}

Использовать именованные зависимости

Независимо от того, является ли это Provide или Invoke, именованные зависимости могут использоваться только в форме объектов параметров. Метод использования — через тег, что согласуется с инъекцией.

type MongodbClients struct {
	dig.Out
    
    Mgo1 *MongodbClient `name:"mgo1"`
    Mgo2 *MongodbClient `name:"mgo2"`
}

container.Invoke(func (mcs MongodbClients) {
    mcs.Mgo1
    mcs.Mgo2
})

Меры предосторожности

Все поля структуры вложенного dig.In должны существовать в контейнере, иначе функция, переданная в Invoke, вызываться не будет. Пример ошибки:

type MongodbClients struct {
	dig.Out
    
    Mgo1 *MongodbClient `name:"mgo1"`
    Mgo2 *MongodbClient `name:"mgo2"`
}

container.Provide(NewMongoClient, dig.Name("mgo1"))// 只注入了 mgo1

container.Invoke(func (mcs MongodbClients) {// 需要依赖 mgo1 和 mgo2,所以不会执行
    mcs.Mgo1
    mcs.Mgo2
})

восьмерка, группа

Помимо именования зависимостей, вы также можете группировать зависимости, и зависимости одного типа будут помещены в один и тот же слайс. Однако после группировки вы не можете получить доступ к зависимостям по именованию, то есть именование и группировка могут использоваться только одним способом одновременно. Еще приведенный выше пример.

Использовать параметры

type MongodbClient struct {}

func NewMongoClient(cfg *Mongo.Client) *MongodbClient{
    return ConnectionMongo(cfg)
}

container.Provide(NewMongoClient, dig.Group("mgo"))
container.Provide(NewMongoClient, dig.Group("mgo"))

использовать объект результата

type MongodbClientGroup struct {
	dig.In
    
    Mgos []*MongodbClient `group:"mgo"`
}

использовать группу

Группы могут использоваться только в качестве параметров объекта.

container.Invoke(func(mcg MongodbClientGroup) {
    for _, m := range mcg {
        // 业务逻辑
    }
})

Меры предосторожности

Как и в случае с именованными зависимостями, все поля с тегами группы в структуре вложенных dig.In должны существовать хотя бы одно в контейнере, иначе функция, переданная в Invoke, не будет вызвана. Кроме того, срез, возвращаемый группой, не гарантирует порядок зависимостей при внедрении, то есть срез зависимостей неупорядочен.

9. Запустите несколько http-сервисов, используя группы и имена

Теперь, когда я изучил все API-интерфейсы dig, давайте немного попрактикуемся и запустим несколько сервисов через группу зависимостей dig.

package main

import (
	"errors"
	"fmt"
	"net/http"
	"strconv"

	"go.uber.org/dig"
)

type ServerConfig struct {
	Host string // 主机地址
	Port string // 端口号
	Used bool   // 是否被占用
}

type ServerGroup struct {
	dig.In

	Servers []*Server `group:"server"`
}

type ServerConfigNamed struct {
	dig.In

	Config1 *ServerConfig `name:"config1"`
	Config2 *ServerConfig `name:"config2"`
	Config3 *ServerConfig `name:"config3"`
}

type Server struct {
	Config *ServerConfig
}

func (s *Server) Run(i int) {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(write http.ResponseWriter, req *http.Request) {
		write.Write([]byte(fmt.Sprintf("第%s个服务,端口号是: %s", strconv.FormatInt(int64(i), 10), s.Config.Port)))
	})
	http.ListenAndServe(s.Config.Host+":"+s.Config.Port, mux)
}

func NewServerConfig(port string) func() *ServerConfig {
	return func() *ServerConfig {
		return &ServerConfig{Host: "127.0.0.1", Port: port, Used: false}
	}
}

func NewServer(sc ServerConfigNamed) *Server {
	if !sc.Config1.Used {
		sc.Config1.Used = true
		return &Server{Config: sc.Config1}
	} else if !sc.Config2.Used {
		sc.Config2.Used = true
		return &Server{Config: sc.Config2}
	} else if !sc.Config3.Used {
		sc.Config3.Used = true
		return &Server{Config: sc.Config3}
	}
	panic(errors.New(""))
}

func ServerRun(sg ServerGroup) {
	for i, s := range sg.Servers {
		go s.Run(i)
	}
}

func main() {
	container := dig.New()
	// 注入 3 个服务配置项
	container.Provide(NewServerConfig("8199"), dig.Name("config1"))
	container.Provide(NewServerConfig("8299"), dig.Name("config2"))
	container.Provide(NewServerConfig("8399"), dig.Name("config3"))
	// 注入 3 个服务实例
	container.Provide(NewServer, dig.Group("server"))
	container.Provide(NewServer, dig.Group("server"))
	container.Provide(NewServer, dig.Group("server"))
	// 使用缓冲 channel 卡住主协程
	serverChan := make(chan int, 1)
	container.Invoke(ServerRun)
	<-serverChan
}

Чтобы запустить файл, к нему можно получить доступ с помощьюhttp://127.0.0.1:8199,http://127.0.0.1:8299,http://127.0.0.1:8399Проверьте эффект. Приведенный выше пример не подходит для использования именованных зависимостей, но для демонстрации фактического использования API используются именованные зависимости. Ни один API не является лучшим. В реальной разработке, в соответствии с конкретным бизнесом, используйте API, который наиболее подходит для сцены, и используйте его гибко.

10. Справочные ссылки

pak dig pkg.go.dev

Dependency Injection in Go software is fun

Внедрение зависимостей — это не патент Java, в Golang он тоже естьстарые деньги Чжиху

uber dig github

Ежедневно копайтесь в библиотекеТэджун