[wire] Meilleures pratiques pour le fil de la bibliothèque d'injection de dépendances Go

original-59266f4dea1c2aa43f2064cc0f3b165a.webp

Introduction au fil

Wire est un outil de génération de code qui utilise l'injection de dépendances pour automatiser la connexion des composants. Les dépendances entre les composants sont reflétées sous forme de paramètres de fonctions dans Wire. Wire encourage l'initialisation explicite des paramètres plutôt que de définir des variables globales. Étant donné que Wire ne s'appuie pas sur l'état d'exécution ni sur la réflexion au moment de l'exécution, le code écrit à l'aide de Wire peut même remplacer le code d'initialisation manuscrit.

Document d'introduction officiel : article de blog d'introduction .

Installer

Installez à l'aide de la commande suivante :

shell go get github.com/google/wire/cmd/wire

document

démarrage rapide

Tout d'abord, vous devez créer un fichier wire.go dans le répertoire du projet ou ailleurs (de préférence au même niveau que le fichier main.go), puis // +build wireinjectmarquer ce fichier avec pour indiquer que le compilateur ignorera ce fichier et ne compilera pas. il.

Déclarez les structures suivantes dans main.go, et il existe des dépendances entre elles :

```aller au paquet principal

importer "fmt"

tapez Chaîne de message

tapez Greeter struct { Message Message }

func NewGreeter(message Message) Greeter { return Greeter{Message : message} }

func (g Greeter) Greet() { fmt.Println(g.Message) }

tapez Structure d'événement { Greeter Greeter }

func NewEvent(greeter Greeter) Événement { return Event{Greeter: greeter} }

func (e Event) Start() { e.Greeter.Greet() } ```

在这个文件中,我们定义了 Greeter 结构体和 Event 结构体。Greeter 结构体依赖于 MessageEvent 结构体依赖于 Greeter。通过 NewGreeterNewEvent 函数来创建相应的实例。

从上面的代码可以看出,一个结构体的构造函数依赖于另一个结构体对象,然后进行组合赋值,一层嵌套一层。如果手动编写初始化函数的话,在结构体比较多的情况下会非常繁琐。

这时候就需要代码生成器来解决这个问题了。

在 wire.go 文件中声明以下函数:

```go // +build wireinject

package main

import "github.com/google/wire"

func InitializeEvent() Event { wire.Build(NewEvent, NewGreeter, NewMessage) return Event{} } ```

在这个文件中,我们定义了一个 InitializeEvent 函数,用于初始化依赖关系。使用 wire.Build 函数来声明依赖关系,并指定需要注入的结构体。

使用 wire 命令来生成依赖注入的代码。运行该命令后,会自动生成一个名为 wire_gen.go 的文件,其中包含了自动生成的依赖注入代码。最后,创建一个 main.go 文件,使用生成的依赖注入代码来初始化依赖关系并执行相关操作:

```go package main

func main() { event := InitializeEvent() event.Start() } ```

通过运行 go run main.go,你将会看到程序输出了预定义的消息。

最佳实践

创建工程

下面介绍如何将 wire 与 工程化实践结合,将 wire 与 HTTP 服务器程序整合到一起。首先,创建一个工程设置好 go mod 属性。

安装好 wire 依赖:

shell go get github.com/google/wire

创建 main.go 文件,现在可以什么都不写。在项目根目录下,创建一个 wire 目录,在里面创建一个 wire.go 文件(别问我为什么,因为这是开发惯例),wire.go 文件中编写以下内容:

```go //go:build wireinject // +build wireinject

package wire

import ( "net/http"

"github.com/google/wire" "github.com/spf13/viper" "go.uber.org/zap"

"wire-first/provider" )

// wire.go 初始化模块 func NewApp(viper.Viper, *zap.Logger) (http.Server, error) { panic(wire.Build( provider.ServerSet, provider.HandlerSet, provider.ServiceSet, provider.DaoSet, )) } ```

这时候我们还没有安装日志库 zap 和配置库 viper,输入以下命令安装:

```shell go get go.uber.org/zap

go get github.com/spf13/viper ```

可以看到项目还缺少 provider 包,这是项目内置的包。

创建provider包

接着上一节,在项目根目录下创建 provier 包,包内创建 provider.go 文件,编写以下内容:

```go package provider

import ( "github.com/google/wire"

"wire-first/dao" "wire-first/handler" "wire-first/server" "wire-first/service" )

var ServerSet = wire.NewSet(server.NewServerHttp)

var HandlerSet = wire.NewSet(handler.NewHandler, handler.NewUserController, handler.NewRoleController)

var ServiceSet = wire.NewSet(service.NewService, service.NewUserService, service.NewRoleService)

var DaoSet = wire.NewSet(dao.NewDao, dao.NewUserDao, dao.NewRoleDao) ```

可能到这里你会觉得有疑问,NewSet 函数是什么?NewSet 函数是 wire 库提供的一个类似于分组的函数,将一些依赖注入项分组便于管理。上面代码可以清晰地看出分成了四组。第一个是 ServerSet 就是服务器层的依赖注入,第二个是处理器层的组,第三个是服务层的组,第四个是持久层的组,这是典型的业务层划分。

创建分层的包

现在开始创建对应的层次的包然后创建对应的文件。先创建 handler 包,创建 handler.go 和 user_handler.go 文件,其他 handler 文件就不演示了。

handler.go

```go package handler

import ( "github.com/spf13/viper" "go.uber.org/zap" )

type Handler struct { conf *viper.Viper logger *zap.Logger }

func NewHandler(conf *viper.Viper, logger *zap.Logger) *Handler { return &Handler{conf: conf, logger: logger} } ```

handler.go 中声明一了 Handler 结构体这是为了让其他 Handler 都继承它,这样就可以共用全局配置和日志对象了,这点就是为了弥补没有像 SpringBoot 那样的操作字节码运行时注入功能了。

user_handler.go

```go package handler

import "wire-first/service"

type UserHandler struct { *Handler userService *service.UserService }

func NewUserController(handler *Handler, userService *service.UserService) *UserHandler { return &UserHandler{ Handler: handler, userService: userService, } } ```

这样 UserHandler 就可以使用全局配置和日志对象了。这也可以看出 wire 是通过编译时构造器注入的。UserHandler 内部依赖了 UserService 结构体的指针,是不是很熟悉这个老味道?

完成之后开始创建 service 包,创建 service.go 和 user_service.go 文件。

service.go

```go package service

import ( "github.com/spf13/viper" "go.uber.org/zap" )

type Service struct { conf *viper.Viper logger *zap.Logger }

func NewService(conf *viper.Viper, logger *zap.Logger) *Service { return &Service{conf: conf, logger: logger} } ```

同样是内部依赖了全局配置和日志对象,这就是为了处处使用这些对象。

user_service.go

```go package service

import "wire-first/dao"

type UserService struct { *Service userDao *dao.UserDao }

func NewUserService(service *Service, userDao *dao.UserDao) *UserService { return &UserService{ Service: service, userDao: userDao, } } ```

UserService 继承了 Service 结构体,内部还依赖 UserDao 指针。

开始创建 dao.go 和 user_dao.go 文件。

dao.go

```go package dao

import ( "github.com/spf13/viper" "go.uber.org/zap" )

type Dao struct { conf *viper.Viper logger *zap.Logger }

func NewDao(conf *viper.Viper, logger *zap.Logger) *Dao { return &Dao{conf: conf, logger: logger} } ```

Dao 结构体内部还可以依赖其他数据库操作对象,比如说 MySQL、Redis、MongoDB...按照业务需求添加即可。

user_dao.go

```go package dao

type UserDao struct { *Dao }

func NewUserDao(dao *Dao) *UserDao { return &UserDao{Dao: dao} } ```

UserDao 继承了 Dao,就可以使用它内部的事先注入好的对象。

创建server包

在项目的根目录里面创建一个 server 包,创建一个 server.go 文件,在里面编写有关创建 HTTP 服务器的代码:

```go package server

import ( "fmt" "net/http"

"github.com/spf13/viper" "go.uber.org/zap"

"wire-first/handler" )

func NewServerHttp( conf *viper.Viper, logger *zap.Logger, userHandler *handler.UserHandler, ) *http.Server { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("你好世界!")) }) server := http.Server{ Handler: mux, Addr: fmt.Sprintf(":%s", conf.GetString("app.port")), } return &server } ```

可以看到,UserHandler 通过参数传进来了,其他全局配置和日志对象也是通过参数传进来了。到这里的时候,就可以利用传进来的 conf 和 handler 进行对 Server 的配置以及路由的设置。

最终返回 http.Server 对象。这里的 http 框架你也可以换成别的框架,比如说 Gin、Fiber、Echo、Beego 等,我这里演示的是最原始的 net/http 包以及它的多路复用器。

创建config包

现在需要在项目中创建 config 包用于做全局配置对象 viper 的初始化。

config/config.go

``` package config

import ( "github.com/spf13/viper" )

func NewConfig(path string) *viper.Viper { conf := viper.New() conf.SetConfigFile(path) err := conf.ReadInConfig() if err != nil { panic(err) } return conf } ```

最后还需要创建一个 app.yml 项目的配置文件:

yml app: port: 8080

完善main.go文件

现在开始编写 main 函数:

```go package main

import ( "github.com/alecthomas/kingpin/v2" "go.uber.org/zap"

"wire-first/config" "wire-first/wire" )

var ( cfgPath = kingpin.Flag("config", "the path of the config file").Default("app.yml").String() )

func main() { kingpin.Parse() conf := config.NewConfig(*cfgPath) // 创建logger logger, err := zap.NewDevelopment() if err != nil { panic(err) } defer logger.Sync() app, err := wire.NewApp(conf, logger) if err != nil { logger.Error("Initialization failed", zap.Error(err), ) }

logger.Info("Le serveur est en cours d'exécution", zap.String("adresse", app.Addr)) if err := app.ListenAndServe(); err != nil { logger.Error("Erreur du serveur", zap.String("clé", "valeur"), zap.Error(err), ) } } ```

Vous pouvez voir que le chemin du fichier de configuration est obtenu via les paramètres de ligne de commande. L'initialisation de logger et viper est effectuée dans la fonction principale puis transmise à la fonction NewApp pour renvoyer l'objet http.Server, et enfin le serveur est démarré.

Le projet backend Web d'intégration générale de Wire est presque comme ceci, mais vous pouvez également le personnaliser en fonction de vos besoins, pas nécessairement en fonction de cela.

Bon piratage ! Gopher!

Plan du catalogue

Je suppose que tu aimes

Origine blog.csdn.net/weixin_45254062/article/details/131679092
conseillé
Classement