使用Wire在GO中进行依赖注入

警告
本文最后更新于 2020-10-14,文中内容可能已过时。

什么是依赖注入? 依赖注入是您的组件(通常是go中的结构)在创建时应接收其依赖的想法。这与初始化期间构建其自身依赖关系的组件的关联反模式背道而驰。

依赖注入是保持软件“松耦合且易于维护”的最重要的设计原则之一。该原理已在所有类型的开发平台中广泛使用,并且有许多与之相关的出色工具。

首先,我将编写一个非常简单的程序,该程序模拟一个带有迎宾员向其发送特定消息的问候者的事件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main

import (
	"fmt"
)

type Message string

type Greeter struct {
	Message Message
}

type Event struct {
	Greeter Greeter
}

func NewMessage() Message {
	return "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()
}

上面我们做了什么:首先我们创建了一条消息,然后使用该消息创建了一个greeter,最后我们创建了一个带有该gteeter的event。完成所有初始化后,我们就可以开始活动了。

如果运行文件,将得到输出:

1
Hello there!

我们正在使用依赖注入设计原理。实际上,这意味着我们可以传递每个组件所需的任何内容。这种设计风格使其易于编写易于测试的代码,并且可以轻松地将一个依赖关系换成另一个依赖关系。

如果应用程序很小,编写我们自己的代码很容易,但是如果您的应用程序很大并且有复杂的依赖关系图,那该怎么办。如果要在该复杂的树中添加新的依赖项,并确保该依赖项向下传播到树中。这将是头脑风暴,这是一个艰巨的挑战,需要您花费时间。因此,我们使用工具简化了使用依赖注入自动连接组件的过程。

依赖注入是如此重要,以至于Golang社区中已经有很多解决方案,例如UberdigFacebookinject。它们都通过Reflection Mechanisms实现了runtime依赖项注入。

Wire与这些工具有何不同? Wire的运行没有运行时状态或反射,即使用于手写初始化,编写与Wire一起使用的代码也很有用。 Wire可以生成源代码并在编译时实现依赖项注入。

使用Wire的好处:

  1. 由于wire使用代码生成,因此生成的容器代码是显而易见的且可读的。
  2. 轻松调试。如果缺少任何依赖项或未使用任何依赖项,则在编译过程中将报告错误。

安装wire非常容易,只需运行

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

从上面的示例中,我们有一个主要功能:

1
2
3
4
5
6
7
func main() {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)

    event.Start()
}

让我们将其更改为:

1
2
3
4
func main() {
    event := InitializeEvent()
    event.Start()
}

并创建将具有InitializeEvent()wire.go文件。看起来像这样:

1
2
3
4
5
6
package main
import "github.com/google/wire"
func InitializeEvent() Event {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}
}

在这里,我们只需要一次调用wire.Build就可以传入我们要使用的初始化程序了。对于应按什么顺序传递初始化程序,没有任何规则。例如,您可以使用:

1
2
3
4
5
6
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

1
$ wire

您应该看到类似以下输出的内容:

1
2
$ wire
example: wrote ~/example/wire_gen.go

让我们看一下wire_gen.go文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 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创建的文件
1
$ go generate
  1. //+build !wireinject 在编译期间,将使用此文件,以便我们的依存关系图可以正常工作。

如果您现在build或run

1
2
3
# demo
./wire_gen.go:10:6: InitializeEvent redeclared in this block
        previous declaration at ./wire.go:5:24

Why this message? 这是因为我们必须忽略wire.go文件才能运行具有依赖项注入的wire_gen.go。 因此,要解决此问题,只需将//+build wireinject添加到您的wire.go文件中 与package main之间必须要用空行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//+build wireinject

package main

import "github.com/google/wire"

func InitializeEvent() Event {
	wire.Build(NewEvent, NewGreeter, NewMessage)
	return Event{}
}
1
2
3
4
5
6
7
8
9
//+build wireinject
package main

import "github.com/google/wire"

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

Run wire:

1
2
wire: /home/kira/go/src/demo/wire.go:8:47: undeclared name: NewPerson
wire: generate failed
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//+build wireinject

package main

import "github.com/google/wire"

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

Run wire

1
2
3
wire: /home/kira/go/src/demo/wire.go:7:1: inject InitializeEvent: no provider found for demo.Event, output of injector
wire: demo: generate failed
wire: at least one generate failure

同样,如果缺少的initializer,则该wire将引needed by some Provider的错误。如果需要,可以尝试一下。

可以产生值的函数。这些功能是普通的Go代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package foobarbaz

type Foo struct {
    X int
}

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

就像普通函数一样,必须导出提供程序函数才能从其他包中使用。

您可以在Provider中执行的操作:

  1. can specify dependencies with parameters
  2. can also return errors
  3. can be grouped into provider sets
  4. can also add other provider sets into a provider set

按依赖关系顺序调用提供程序的函数。使用Wire,您可以编写injector’s signature,然后Wire生成函数的主体。通过编写函数声明来声明注入器,该函数声明的主体是对wire.Build的调用。

1
2
3
4
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
    wire.Build(ProvideFoo, ProvideBar, ProvideBaz)
    return foobarbaz.Baz{}, nil
}

像 providers, injectors 可以在输入上对注入程序进行参数化(然后将其发送给providers),并且可以返回错误。

Wire将在wire_gen.go文件中生成注射器的实现

我想,您已经在依赖注入中学到了很多关于Wire工具的知识。除了提供者和注入者的概念之外,还有一些高级功能。其中包括 Binding Interfaces, Struct Providers, Binding Values, Cleanup functions等。您可以在这里了解这些事情

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

main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//+build wireinject

package main

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

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

Run wire

1
2
$ wire
example: wrote ~/example/wire_gen.go

wire_gen.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 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
}

因此,这一切wire与使用GO进行GO中的依赖注入。希望你学到了一些东西。当您学到一些东西时为您鼓掌。


Dependency Injection in GO with Wire

相关内容