В предыдущей серии статей о модульном тестировании я кратко представилинфраструктура внедрения зависимостей проводов. Но Wire в то время все еще находился в стадии альфа-тестирования, но недавно Wire выпустил первую бета-версию, API претерпел некоторые изменения, и он также обещает, что если в этом нет крайней необходимости, он не нарушит совместимость API. В предыдущей статье были представлены некоторые основные обзоры проволоки, которые не будут повторяться в этой статье.Заинтересованные друзья могут оглянуться назад:Get Go Unit Testing (4) — Платформа внедрения зависимостей (проводная версия). В этой статье будет подробно рассказано об использовании проводов и некоторых передовых методах.
Полный пример кода в этом посте можно найти здесь:wire-examples
Installing
go get github.com/google/wire/cmd/wire
Quick Start
Давайте сначала пройдем простой пример, пусть друзья знаютwire
Есть интуитивное понимание. В следующем примере показан простойwire
Пример внедрения зависимостей:
$ ls
main.go wire.go
main.go
package main
import "fmt"
type Message struct {
msg string
}
type Greeter struct {
Message Message
}
type Event struct {
Greeter Greeter
}
// NewMessage Message的构造函数
func NewMessage(msg string) Message {
return Message{
msg:msg,
}
}
// NewGreeter Greeter构造函数
func NewGreeter(m Message) Greeter {
return Greeter{Message: m}
}
// NewEvent Event构造函数
func NewEvent(g Greeter) Event {
return Event{Greeter: g}
}
func (e Event) Start() {
msg := e.Greeter.Greet()
fmt.Println(msg)
}
func (g Greeter) Greet() Message {
return g.Message
}
// 使用wire前
func main() {
message := NewMessage("hello world")
greeter := NewGreeter(message)
event := NewEvent(greeter)
event.Start()
}
/*
// 使用wire后
func main() {
event := InitializeEvent("hello_world")
event.Start()
}*/
wire.go
// +build wireinject
// The build tag makes sure the stub is not built in the final build.
package main
import "github.com/google/wire"
// InitializeEvent 声明injector的函数签名
func InitializeEvent(msg string) Event{
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{} //返回值没有实际意义,只需符合函数签名即可
}
перечислитьwire
Команда для создания файлов зависимостей:
$ wire
wire: github.com/DrmagicE/wire-examples/quickstart: wrote XXXX\github.com\DrmagicE\wire-examples\quickstart\wire_gen.go
$ ls
main.go wire.go 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(msg string) Event {
message := NewMessage(msg)
greeter := NewGreeter(message)
event := NewEvent(greeter)
return event
}
Перед использованием VS После использования
...
/*
// 使用wire前
func main() {
message := NewMessage("hello world")
greeter := NewGreeter(message)
event := NewEvent(greeter)
event.Start()
}*/
// 使用wire后
func main() {
event := InitializeEvent("hello_world")
event.Start()
}
...
использоватьwire
После этого просто вызовите метод инициализации, чтобы получитьEvent
Теперь, по сравнению с тем, что было до использования, не только на три строчки кода меньше, но и нет необходимости заботиться о порядке инициализации между зависимостями.
Пример портала:quickstart
Provider & Injector
provider
иinjector
даwire
два основных понятия.
provider: a function that can produce a value. These functions are ordinary Go code.
injector: a function that calls providers in dependency order. With Wire, you write the injector's signature, then Wire generates the function's body.
GitHub.com/Google/wire…
предоставляяprovider
функция, пустьwire
Знайте, как генерировать эти зависимые объекты.wire
По нашему определениюinjector
сигнатура функции, генерирующая полнуюinjector
функция,injector
Функция — это та, которая нам нужна в конце, и она будет вызываться в порядке зависимостей.provider
.
На примере QuickStartNewMessage,NewGreeter,NewEvent
обаprovider
,wire_gen.go
серединаInitializeEvent
функцияinjector
, можно увидетьinjector
путем вызова в порядке зависимостиprovider
для создания нужных нам объектовEvent
.
Приведенный выше пример находится вwire.go
определено вinjector
Подпись функции, обратите внимание на добавление первой строки файла
// +build wireinject
...
Используется, чтобы сообщить компилятору, что файл не нужно компилировать. существуетinjector
в функции определения подписи, вызвавwire.Build
метод, указывающий метод, используемый для создания зависимостейprovider
:
// InitializeEvent 声明injector的函数签名
func InitializeEvent(msg string) Event{
wire.Build(NewEvent, NewGreeter, NewMessage) // <--- 传入provider函数
return Event{} //返回值没有实际意义,只需符合函数签名即可
}
Возвращаемое значение этого метода не имеет практического значения, оно должно только соответствовать требованиям сигнатуры функции.
Расширенные возможности
Пример быстрого запуска показываетwire
Основные функции , в этом разделе представлены некоторые дополнительные возможности.
привязка интерфейса
В соответствии с принципом инверсии зависимостей объекты должны зависеть от интерфейса, а не напрямую от конкретной реализации.
Абстрагирование зависимостей интерфейса более полезно для модульного тестирования!
Приступайте к модульному тестированию (1) — основные принципы
Get Go Unit Testing (2) — Mock Framework (gomock)
Все зависимости в примере с кратким руководством являются конкретными реализациями. Теперь давайте взглянем наwire
Как обрабатывать зависимости интерфейса в:
// UserService
type UserService struct {
userRepo UserRepository // <-- UserService依赖UserRepository接口
}
// UserRepository 存放User对象的数据仓库接口,比如可以是mysql,restful api ....
type UserRepository interface {
// GetUserByID 根据ID获取User, 如果找不到User返回对应错误信息
GetUserByID(id int) (*User, error)
}
// NewUserService *UserService构造函数
func NewUserService(userRepo UserRepository) *UserService {
return &UserService{
userRepo:userRepo,
}
}
// mockUserRepo 模拟一个UserRepository实现
type mockUserRepo struct {
foo string
bar int
}
// GetUserByID UserRepository接口实现
func (u *mockUserRepo) GetUserByID(id int) (*User,error){
return &User{}, nil
}
// NewMockUserRepo *mockUserRepo构造函数
func NewMockUserRepo(foo string,bar int) *mockUserRepo {
return &mockUserRepo{
foo:foo,
bar:bar,
}
}
// MockUserRepoSet 将 *mockUserRepo与UserRepository绑定
var MockUserRepoSet = wire.NewSet(NewMockUserRepo,wire.Bind(new(UserRepository), new(*mockUserRepo)))
В этом примереUserService
полагатьсяUserRepository
интерфейс, которыйmockUserRepo
даUserRepository
Реализация , поскольку в лучших практиках Go рекомендуется возвращать конкретную реализацию, а не интерфейс. такmockUserRepo
изprovider
Функция возвращает*mockUserRepo
этот конкретный тип.wire
Невозможно автоматически связать конкретные реализации с интерфейсами, нам нужно явно объявить связь между ними. пройти черезwire.NewSet
иwire.Bind
будет*mockUserRepo
иUserRepository
Связывать:
// MockUserRepoSet 将 *mockUserRepo与UserRepository绑定
var MockUserRepoSet = wire.NewSet(NewMockUserRepo,wire.Bind(new(UserRepository), new(*mockUserRepo)))
определениеinjector
подпись функции:
...
func InitializeUserService(foo string, bar int) *UserService{
wire.Build(NewUserService,MockUserRepoSet) // 使用MockUserRepoSet
return nil
}
...
Пример портала:binding-interfaces
вернуть ошибку
В предыдущем примере нашprovider
Функции имеют только одно возвращаемое значение, но в некоторых случаяхprovider
Функция может проверять входные параметры, если параметр неверен, она должна вернутьerror
.wire
Эта ситуация также рассматривалась,provider
Функция может установить второй параметр возвращаемого значения равнымerror
:
// Config 配置
type Config struct {
// RemoteAddr 连接的远程地址
RemoteAddr string
}
// APIClient API客户端
type APIClient struct {
c Config
}
// NewAPIClient APIClient构造函数,如果入参校验失败,返回错误原因
func NewAPIClient(c Config) (*APIClient,error) { // <-- 第二个参数设置成error
if c.RemoteAddr == "" {
return nil, errors.New("没有设置远程地址")
}
return &APIClient{
c:c,
},nil
}
// Service
type Service struct {
client *APIClient
}
// NewService Service构造函数
func NewService(client *APIClient) *Service{
return &Service{
client:client,
}
}
сродни,injector
Когда функция определена, вам также необходимо установить второе возвращаемое значение равнымerror
:
...
func InitializeClient(config Config) (*Service, error) { // <-- 第二个参数设置成error
wire.Build(NewService,NewAPIClient)
return nil,nil
}
...
наблюдатьwire
Сгенерированоinjector
:
func InitializeClient(config Config) (*Service, error) {
apiClient, err := NewAPIClient(config)
if err != nil { // <-- 在构造依赖的顺序中如果发生错误,则会返回对应的"零值"和相应错误
return nil, err
}
service := NewService(apiClient)
return service, nil
}
Если ошибка возникает в порядке построения зависимостей, возвращается соответствующее «нулевое значение» и соответствующая ошибка.
Пример портала:return-error
Cleanup functions
когдаprovider
Сгенерированные объекты нуждаются в некоторой обработке очистки, такой как закрытие файлов, закрытие соединений с базой данных и т. д., вы все равно можете установитьprovider
Возвращаемое значение для достижения этого эффекта:
// FileReader
type FileReader struct {
f *os.File
}
// NewFileReader *FileReader 构造函数,第二个参数是cleanup function
func NewFileReader(filePath string) (*FileReader, func(), error){
f, err := os.Open(filePath)
if err != nil {
return nil,nil,err
}
fr := &FileReader{
f:f,
}
fn := func() {
log.Println("cleanup")
fr.f.Close()
}
return fr,fn,nil
}
Подобно возврату ошибки,provider
Второй возвращаемый параметр установлен вfunc()
Используется для возврата функции очистки, которая возвращается в третьем параметре в приведенном выше примере.error
, но это необязательно:
Wire указывает количество и порядок возвращаемых значений провайдера:
- Первый параметр — это объект зависимости, который необходимо сгенерировать.
- Если возвращаются 2 возвращаемых значения, второй параметр должен быть func() или error
- Если возвращаются 3 возвращаемых значения, второй параметр должен быть func(), а третий параметр должен быть ошибкой.
Пример портала:cleanup-functions
Provider set
когда некоторыеprovider
Обычно при совместном использовании вы можете использоватьprovider setЧтобы организовать их, используйте пример быстрого запуска в качестве шаблона с небольшими изменениями:
// NewMessage Message的构造函数
func NewMessage(msg string) Message {
return Message{
msg:msg,
}
}
// NewGreeter Greeter构造函数
func NewGreeter(m Message) Greeter {
return Greeter{Message: m}
}
// NewEvent Event构造函数
func NewEvent(g Greeter) Event {
return Event{Greeter: g}
}
func (e Event) Start() {
msg := e.Greeter.Greet()
fmt.Println(msg)
}
// EventSet Event通常是一起使用的一个集合,使用wire.NewSet进行组合
var EventSet = wire.NewSet(NewEvent, NewMessage, NewGreeter) // <--
В приведенном выше примереEvent
и его зависимости черезwire.NewSet
комбинированный, в целом вinjector
В определении сигнатуры функции используются:
func InitializeEvent(msg string) Event{
//wire.Build(NewEvent, NewGreeter, NewMessage)
wire.Build(EventSet)
return Event{}
}
Тогда просто поставьEventSet
входящийwire.Build
Вот и все.
Пример портала:provider-set
поставщик структуры
В дополнение к функциям структуры также могут выполнять функции.provider
роль, похожая наsetter
впрыск:
type Foo int
type Bar int
func ProvideFoo() Foo {
return 1
}
func ProvideBar() Bar {
return 2
}
type FooBar struct {
MyFoo Foo
MyBar Bar
}
var Set = wire.NewSet(
ProvideFoo,
ProvideBar,
wire.Struct(new(FooBar), "MyFoo", "MyBar"))
пройти черезwire.Struct
Чтобы указать, какие поля должны быть введены в структуру, если это все поля, это также можно сократить как:
var Set = wire.NewSet(
ProvideFoo,
ProvideBar,
wire.Struct(new(FooBar), "*")) // * 表示注入全部字段
Сгенерированоinjector
функция:
func InitializeFooBar() FooBar {
foo := ProvideFoo()
bar := ProvideBar()
fooBar := FooBar{
MyFoo: foo,
MyBar: bar,
}
return fooBar
}
Пример портала:struct-provider
Best Practices
Отличить тип
так какinjector
В функции не допускаются повторяющиеся типы параметров, иначеwire
Не будет никакого способа различить эти же типы параметров, такие как:
type FooBar struct {
foo string
bar string
}
func NewFooBar(foo string, bar string) FooBar {
return FooBar{
foo: foo,
bar: bar,
}
}
injector
Определение сигнатуры функции:
// wire无法得知入参a,b跟FooBar.foo,FooBar.bar的对应关系
func InitializeFooBar(a string, b string) FooBar {
wire.Build(NewFooBar)
return FooBar{}
}
Если вы используете вышеперечисленноеprovider
генерироватьinjector
,wire
Будет сообщена следующая ошибка:
provider has multiple parameters of type string
Поскольку все входные параметры являются строковыми, wire не может знать соответствие между входными параметрами a, b и FooBar.foo, FooBar.bar. Поэтому мы используем разные типы, чтобы избежать конфликтов:
type Foo string
type Bar string
type FooBar struct {
foo Foo
bar Bar
}
func NewFooBar(foo Foo, bar Bar) FooBar {
return FooBar{
foo: foo,
bar: bar,
}
}
injector
Определение сигнатуры функции:
func InitializeFooBar(a Foo, b Bar) FooBar {
wire.Build(NewFooBar)
return FooBar{}
}
Среди них базовые типы и общие типы интерфейсов чаще всего конфликтуют, если они находятся вprovider
появляется в функции, лучше всего создать новый псевдоним для его замены (хотя конфликта нет), например:
type MySQLConnectionString string
type FileReader io.Reader
пример порталаdistinguishing-types
Options Structs
еслиprovider
Метод содержит множество зависимостей, которые можно поместить в структуру параметров, чтобы избежать слишком большого количества параметров для конструктора:
type Message string
// Options
type Options struct {
Messages []Message
Writer io.Writer
Reader io.Reader
}
type Greeter struct {
}
// NewGreeter Greeter的provider方法使用Options以避免构造函数过长
func NewGreeter(ctx context.Context, opts *Options) (*Greeter, error) {
return nil, nil
}
// GreeterSet 使用wire.Struct设置Options为provider
var GreeterSet = wire.NewSet(wire.Struct(new(Options), "*"), NewGreeter)
Сигнатура функции инжектора:
func InitializeGreeter(ctx context.Context, msg []Message, w io.Writer, r io.Reader) (*Greeter, error) {
wire.Build(GreeterSet)
return nil, nil
}
пример порталаoptions-structs
Некоторые недостатки и ограничения
дополнительные определения типов
так какwire
собственные ограничения,injector
Типы переменных не могут быть продублированы, что требует определения многих дополнительных псевдонимов базовых типов.
фиктивная поддержка временно недостаточно дружелюбна
В настоящее времяwire
команда еще не распознана_test.go
в конечном файлеprovider
функция, а это значит, что при необходимости она также используется в тестеwire
Чтобы внедрить наш фиктивный объект, нам нужно внедрить фиктивный объектprovider
, который навязчив к обычному коду, но чиновник, кажется, заметил эту проблему.Заинтересованные партнеры могут обратить внимание на этот вопрос:GitHub.com/Google/wire…
больше ссылок
Официальный README.md
Официальный гид.md
Официальные лучшие практики.md