欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

在Go语言中,如何利用wire工具进行依赖注入

最编程 2024-07-25 18:23:14
...

这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战

本文为译文,原文链接:medium.com/wesionary-t…

image.png

依赖注入

依赖注入是指你的组件(通常在go中是结构体)在创建时应该接收到它们的依赖项。这与组件在初始化期间构建自己依赖关系的相关反模式背道而驰。

依赖注入是保持软件“松耦合和易于维护”的最重要的设计原则之一。这一原则被广泛应用于各种开发平台,并且有许多与之相关的优秀工具。

go中的依赖注入

首先,我将编写一个非常简单的程序,它模拟一个事件,一个迎宾员用特定的消息欢迎客人。

package mainimport (
 "fmt"
)
type Message string

func NewMessage() Message {
   return Message("Hello there!")
}
func NewGreeter(m Message) Greeter {
   return Greeter{Message: m}
}
func (g Greeter) Greet() Message {
   return g.Message
}
func NewEvent(g Greeter) Event {
   return Event{Greeter: g}
}
func (e Event) Start() {
   msg := e.Greeter.Greet()
   fmt.Println(msg)
}
func main() {
   message := NewMessage()
   greeter := NewGreeter(message)
   event := NewEvent(greeter)

   event.Start()
}

我们在上面所做的:首先我们创建了一条消息,然后我们用这条消息创建了一个欢迎者,最后我们用这条欢迎者创建了一个事件。完成所有的初始化之后,我们就可以开始事件了。

如果你运行这个文件,你将会得到这个输出:

Hello there!

我们使用的是依赖注入设计原则。在实践中,这意味着我们传入每个组件需要的任何内容。这种设计风格适合于编写易于测试的代码,并且可以很容易地将一个依赖项替换为另一个依赖项。

如果应用程序很小,编写我们自己的代码很容易,但是如果你有一个大的应用程序,并且有复杂的依赖关系图,那该怎么办呢?如果你想在这棵复杂的树中添加一个新的依赖,并确保这个依赖向下传播到树中。这将是一次头脑风暴,是一个巨大的挑战,将占用你的时间。出于这个原因,我们使用工具来简化使用依赖注入自动连接组件的过程。

依赖注入非常重要,Golang社区已经有很多解决方案,比如UberdigFacebookinject。它们都是通过反射机制实现runtime运行时依赖注入的。

wire是如何不同于这些工具的?

Wire操作时没有运行时状态或反射,用Wire编写的代码甚至可以用于手写的初始化。Wire可以在编译时生成源代码并实现依赖注入。

使用wire的优势

  1. 由于wire使用代码生成,因此生成的容器代码是明显且可读的。
  2. 调试简单。如果任何依赖项缺失或未使用,将在编译期间报告一个错误。

安装

安装wire非常简单,只需要运行:

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

用法 从上面的例子中,我们有一个主函数:

func main() {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)

    event.Start()
}

我们改成这样:

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

然后创建wire.go文件,将有InitializeEvent() 方法。它看起来就像这样:

package main

import "github.com/google/wire"

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

这里,我们只有一个调用到wire.Build传入我们想要使用的初始化式。没有规定传递初始化器的顺序。例如,你可以使用:

wire.Build(NewEvent, NewGreeter, NewMessage)
// OR
wire.Build(NewMessage, NewGreeter, NewEvent)
// OR
wire.Build(NewMessage, NewEvent, NewGreeter)

// So just pass the initializers that you need

现在,是时候运行wire了

$ wire

你应该会看你到一些像这样的输出:

$ wire\
example: wrote ~/example/wire_gen.go

我们看看wire_gen.go

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main
// Injectors from wire.go:

func InitializeEvent() Event {
 message := NewMessage()
 greeter := NewGreeter(message)
 event := NewEvent(greeter)
 return event
}

您可以看到,wire实现了我们自己应该编写的函数。

注意:

  1. //go:generate wire

这意味着未来我们可以简单通过运行以下的指令来重新生成由wire创建的文件

$ go generate

如果我们改变依赖关系图,那么我们可以重新生成文件。

  1. //+build !wireinject

在编译期间,将使用这个文件,以便我们的依赖关系图能够正常工作。

如果你现在build或者run

$ go build
./wire_gen.go:10:6: InitializeEvent redeclared in this block
previous declaration at ./wire.go:5:24

为什么是这个信息?

因为我们得忽略wire.go文件来运行我们的wire_gen.go,它有wire依赖注入。

所以为了解决这个问题,只需要添加 //+build wireinject 到你的wire.go文件中。

使用wire捕获错误

我们传输一个NewPerson初始化器,这不是这个依赖图的一部分。

wire.go

package main

import "github.com/google/wire"

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

运行wire:

//if NewPerons is undeclared
wire: ./wire.go:6:50: undeclared name: NewPerson
wire: generate failed

//if NewPerson is declared
wire: ./wire.go:5:1: inject InitializeEvent: unused provider "main.NewPerson"
wire: generate failed

如果我们忘记传入任何一个初始化器呢?

wire.go

package main

import "github.com/google/wire"

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

运行wire:

wire: ./wire.go:5:1: inject InitializeEvent: no provider found for _/home/cyantosh/Desktop/go-learn.Event, output of injector
wire: _/home/cyantosh/Desktop/go-learn: generate failed
wire: at least one generate failure

此外,如果缺少的初始化器必须被用于另一个Provider,wire将抛出某些Provider所需要的错误。如果你想,你可以自己试试。

了解Wire中使用的两个核心概念:

  1. Provider:可以生产一个值的一个函数。这些函数普遍是go代码。
package foobarbaz

type Foo struct {
    X int
}

// ProvideFoo returns a Foo.
func ProvideFoo() Foo {
    return Foo{X: 42}
}

提供者函数必须导出型的(大写开头),才能从其他包中使用,就像普通函数一样。

在Provider中你可以做的事情:

  • 可以用参数指定依赖
  • 也可以返回errors
  • 可以被分组到provider集合
  • 也可以添加另一个provider到一个provider集合中
  1. Injectors:按照依赖顺序调用providers的一个函数。使用wire,你写出injector的签名,然后Wire生成函数的主体。injector是通过编写函数声明来声明的,函数声明的主体是对wire.Build的调用。
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
    wire.Build(ProvideFoo, ProvideBar, ProvideBaz)
    return foobarbaz.Baz{}, nil
}

像providers一样,injector可以在输入(然后被发送给providers)时被参数化,并且可以返回错误。

wire将会在一个文件wire_gen.go生成一个injector的实现。

我猜,你已经在go中学习了很多关于依赖注入的Wire工具。在provider和injector的概念之上,仍然有一些高级特性。这些包括Binding Interfaces, Struct ProvidersBinding ValuesCleanup functions等等。你可以在这里了解这些事情github.com/google/wire…

让我们编写更多关于在依赖注入中使用wire来实现GO的代码

假设我们有一个简单的系统,它将获取一个url列表,对每个url执行HTTP GET,最后将这些请求的结果连接在一起。

main.go

package main

import (
"bytes"
"fmt"
)

type Logger struct{}

func (logger *Logger) Log(message string) {
fmt.Println(message)
}

type HttpClient struct {
logger *Logger
}

func (client *HttpClient) Get(url string) string {
client.logger.Log("Getting " + url)

// make an HTTP request
return "my response from " + url
}

func NewHttpClient(logger *Logger) *HttpClient {
return &HttpClient{logger}
}

type ConcatService struct {
logger *Logger
client *HttpClient
}

func (service *ConcatService) GetAll(urls ...string) string {
service.logger.Log("Running GetAll")

var result bytes.Buffer

for _, url := range urls {
result.WriteString(service.client.Get(url))
}

return result.String()
}

func NewConcatService(logger *Logger, client *HttpClient) *ConcatService {
return &ConcatService{logger, client}
}

func main() {
service := CreateConcatService()

result := service.GetAll(
"http://example.com",
"https://drewolson.org",
)

fmt.Println(result)
}

container.go

//+build wireinject

package main

import (
"github.com/google/wire"
)

func CreateConcatService() *ConcatService {
    panic(wire.Build(
    wire.Struct(new(Logger), "*"),
    NewHttpClient,
    NewConcatService,
    ))
}

允许wire $ wire example: wrote ~/example/wire_gen.go

wire_gen.go

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

// Injectors from container.go:

func CreateConcatService() *ConcatService {
    logger := &Logger{}
    httpClient := NewHttpClient(logger)
    concatService := NewConcatService(logger, httpClient)
    return concatService
}

这就是GO中的依赖注入。希望你学到了一些东西。当你学到一些东西时为你鼓掌。

推荐阅读