这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战
本文为译文,原文链接:medium.com/wesionary-t…
依赖注入?
依赖注入是指你的组件(通常在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社区已经有很多解决方案,比如Uber的dig和Facebook的inject。它们都是通过反射机制实现runtime运行时依赖注入的。
wire是如何不同于这些工具的?
Wire操作时没有运行时状态或反射,用Wire编写的代码甚至可以用于手写的初始化。Wire可以在编译时生成源代码并实现依赖注入。
使用wire的优势
- 由于wire使用代码生成,因此生成的容器代码是明显且可读的。
- 调试简单。如果任何依赖项缺失或未使用,将在编译期间报告一个错误。
安装
安装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实现了我们自己应该编写的函数。
注意:
- //go:generate wire
这意味着未来我们可以简单通过运行以下的指令来重新生成由wire创建的文件
$ go generate
如果我们改变依赖关系图,那么我们可以重新生成文件。
- //+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中使用的两个核心概念:
- 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集合中
- 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 Providers, Binding Values, Cleanup 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中的依赖注入。希望你学到了一些东西。当你学到一些东西时为你鼓掌。