wire是Google开源的一款代码生成工具,通过依赖注入来自动链接不同的组件。

依赖注入 是用来构建地耦合,灵活易维护地代码的标注技术。在wire中,组件间的依赖关系表示为函数参数(function parameters),通过显示的初始化而不是全局变量。wire没有使用运行时状态或反射来实现依赖注入,而是使用代码生成。

像Wire这样的依赖注入工具旨在简化初始化代码的管理。可以将服务及其依赖关系描述为代码或配置,Wire计算出依赖关系图,并确认如何传递每个服务所需的内容。通过更改函数签名、添加或删除初始化项来更改应用程序的依赖关系,然后Wire为整个依赖关系图生成初始化代码。

Golang社区中其他的依赖注入框架,如来自Uber的dig,Facebook的inject。他们都通过反射机制实现运行时依赖注入。Wire主要灵感来自于Dagger 2,使用代码生成而不是反射或service locators。这样做带来的好处:

  • 当依赖关系更加复杂时,运行时依赖注入会变得难以跟踪和调试。使用代码生成意味着在运行时执行的初始化代码是常规的,惯用的Go代码,易于理解和调试。特别是像忘记依赖之类的问题,将变成编译时错误而不是运行时错误
  • 因为不需要 Service Locators, 所以对命名没有特殊要求
  • 避免依赖膨胀变得更简单。Wire生成的代码仅仅只包含需要的依赖,所以构建出的程序中不会含有没有使用过的依赖。而运行时依赖注入直到执行时,才能识别出没有使用过的依赖
  • 依赖关系静态存于源码之中, 便于工具分析与可视化

基本概念

Wire的两个核心概念是:providersinjectors

provider

provider是Wire的核心机制,它是一个可以返回一个值的普通Go函数(function),如:

 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函数必须要可以是可以导出的,同时provider函数可以使用参数来指定依赖:

1
2
3
4
// ProvideBar returns a Bar: a negative Foo.
func ProvideBar(foo Foo) Bar {
    return Bar{X: -foo.X}
}

provider也可以返回error:

1
2
3
4
5
6
7
// ProvideBaz returns a value if Bar is not zero.
func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) {
    if bar.X == 0 {
        return Baz{}, errors.New("cannot provide baz when bar is zero")
    }
    return Baz{X: bar.X}, nil
}

通过ProviderSet可以将一组provider组合到一起,实践中,通常将一些一起使用的provider放到一起组成一个ProviderSet:

1
var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)

provider set 可以再放入到另一个provider set 中:

1
var MegaSet = wire.NewSet(SuperSet, pkg.OtherSet)

Injectors

injector是由Wire工具自动生成的函数,它将按照依赖顺序调用相关的provider。

通过编写一个函数来声明一个injector,在这个函数体中,将provider作为参数调用wire.Build。wire只关注这个定义函数的返回类型,wire在生成代码时会忽略函数的返回值。

在定义injector的函数中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// +build wireinject
// The build tag makes sure the stub is not built in the final build.

package main

import (
    "context"

    "github.com/google/wire"
    "example.com/foobarbaz"
)

func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
    wire.Build(foobarbaz.MegaSet)
    return foobarbaz.Baz{}, nil
}

在包目录上执行wire命令,生成injector的代码会放到wire_gen.go文件中。如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//+build !wireinject

package main

// Injectors from wire.go:

func InitializeEvent() (Event, error) {
	message := NewMessage()
	greeter := NewGreeter(message)
	event, err := NewEvent(greeter)
	if err != nil {
		return Event{}, err
	}
	return event, nil
}

高级功能

接口绑定

Struct Providers

值绑定

结构的字段熟悉作为Provider

清理函数

参考资源