警告
本文最后更新于 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。完成所有初始化后,我们就可以开始活动了。
如果运行文件,将得到输出:
我们正在使用依赖注入设计原理。实际上,这意味着我们可以传递每个组件所需的任何内容。这种设计风格使其易于编写易于测试的代码,并且可以轻松地将一个依赖关系换成另一个依赖关系。
如果应用程序很小,编写我们自己的代码很容易,但是如果您的应用程序很大并且有复杂的依赖关系图,那该怎么办。如果要在该复杂的树中添加新的依赖项,并确保该依赖项向下传播到树中。这将是头脑风暴,这是一个艰巨的挑战,需要您花费时间。因此,我们使用工具简化了使用依赖注入自动连接组件的过程。
依赖注入是如此重要,以至于Golang社区中已经有很多解决方案,例如Uber
的dig
和Facebook
的inject
。它们都通过Reflection Mechanisms
实现了runtime
依赖项注入。
Wire与这些工具有何不同?
Wire的运行没有运行时状态或反射,即使用于手写初始化,编写与Wire一起使用的代码也很有用。 Wire可以生成源代码并在编译时实现依赖项注入。
使用Wire的好处:
- 由于wire使用代码生成,因此生成的容器代码是显而易见的且可读的。
- 轻松调试。如果缺少任何依赖项或未使用任何依赖项,则在编译过程中将报告错误。
安装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
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实现了我们自己编写的确切功能。
注意:
- //go:generate wire
这意味着将来我们可以通过运行以下命令来重新生成由wire创建的文件
- //+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中执行的操作:
- can specify dependencies with parameters
- can also return errors
- can be grouped into provider sets
- 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