当前位置:网站首页>Go dependency injection -- Google open source library wire

Go dependency injection -- Google open source library wire

2022-07-05 08:22:00 CK continues to grow

Catalog

1. wire The benefits of using

2. install wire Tools

3. wire How does it work

3. How do we use it wire

4. wire Advanced features of

5. Reference material


 

If used java Your little partner must be injecting dependency ( dependency injection) Familiar with the , Dependency injection is a standard technology , By explicitly providing components with all the dependencies they need to work , To generate flexible and loosely coupled code . There are many dependency injection frameworks ,go Language anti haze , There is Uber Of dig and Facebook Of inject Both use reflection for runtime dependency injection .Wire Mainly by Java Dagger 2 Inspired by the , Use code generation instead of reflection or service locator .

Today we mainly talk about go Dependency injection tool in google Of wire.wire yes google Open source library go-cloud The dependency injection tool used

1. wire The benefits of using

stay Go in , This usually takes the form of passing dependencies to constructors :

func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}

This way of passing depends on the constructor , It works well when the application is small , If in a large application , Dependency diagrams can be complex , When a lot of initialization code depends on the dependency order of the constructor , At this time, it is not so easy to deal with . And when a dependency , To be dependent on multiple constructors , Each has to create objects manually , Afferent structure , It seems that the code is not so neat . Another problem is , If in a complex dependency graph , When a service is to be replaced by another service, it will be a headache . Because we need to be in a complex dependency graph , Find all classes that rely on old Services , Then replace it with a new service constructed , This kind of modification of initialization code is cumbersome and slow .

While using wire The advantage of relying on injection tools to manage initialization code is presented here . We can describe services and their dependencies as code or configuration , then wire The dependency graph will be automatically constructed , Then pass the dependencies needed to construct the service for each service . When the requirement modifies the dependency signature or deletes the dependency of the added service , Just modify the corresponding code and configuration ,wire It will automatically complete the injection of these service dependencies for us .

wire Several advantages given by the official :

  • When the dependency graph becomes complex , Runtime dependency injection can be difficult to track and debug . Using code generation means that the initialization code executed at run time is routine 、 conventional Go Code , Easy to understand and debug . Nothing can be done by one “ Magic ” Is confused by the intervention framework . especially , Problems like forgetting dependencies become compile time errors , Not runtime errors .
  • Different from the service locator , The registration service does not need to write any name or key .Wire Use Go Types connect components to their dependencies .
  • It's easier to avoid dependency inflation .Wire The generated code will only import the dependencies you need , Therefore, your binaries will not have unused imports . The runtime dependency injector does not recognize unused dependencies until runtime .
  • Wire The dependency graph of is statically knowable , This provides opportunities for tools and visualization .

2. install wire Tools

install wire It needs to be installed before go We won't go into details here .

install wire Tools

go install github.com/google/wire/cmd/[email protected]

You can go to $GOPATH/bin Look at the table of contents , Make sure wire The installation to bin The execution directory of .

3. wire How does it work

Wire There are two basic concepts : Provider (providers) and injector (injectors).

Provider (providers) It's a common go function , It provides the value of a given dependency , These values can be simply described as function parameters . Next, we define three program sample codes with dependencies :

//  Generate UserStore object , Depends on configuration Config and DB
func NewUserStore(cfg *Config, db *gorm.DB) (*UserStore, error) {...}

//  Provided Config Generate , There are no dependent parameters 
func NewDefaultConfig() *Config {...}

//  Provide build DB Example , Simultaneous dependence ConnectionInfo Parameters 
func NewDB(info *ConnectionInfo) (*gorm.DB, error) {...}

Here, the initialization function requires the instance return value to provide the values that other functions depend on , That is, the dependent provider .

injector (injectors) Is to call the generator function of the provider in dependent order . You write the signature of the injector , Include any required inputs as parameters , And insert pair wire Call to . Build with the list of providers or provider sets needed to build the final result :

var UserStoreSet = wire.NewSet(NewUserStore, NewDefaultConfig)

wire Provide function signatures that will need to be relied on , Build a list of provider assemblies as parameters . such wire The assemblies can be provided in the order , To actually build the function initialization process code .

Finally through wire.go Of build Function provides external calling function , Generate actual code :

func InitUserStore(info *ConnectionInfo) (*UserStore, error) {
		wire.Build(UserStoreSet, NewDB)
		return nil, nil
}

And then in wire.go Under the directory of wire command :

wire

perform wire After the instruction , stay wire.go The directory of will generate a wire_gen.go The file of , When we open this file, we can see wire What did you do for us

// Code generated by Wire. DO NOT EDIT.

// Code generated by Wire. DO NOT EDIT.

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

package user

// Injectors from wire.go:

func InitUserStore(info *ConnectionInfo) (*UserStore, error) {
	config := NewDefaultConfig()
	db, err := NewDB(info)
	if err != nil {
		return nil, err
	}
	userStore, err := NewUserStore(config, db)
	if err != nil {
		return nil, err
	}
	return userStore, nil
}

I can see wire According to our wire.go Initialization function signature in , According to the actual construction order , Generates the same initialization function that actually initializes each dependency . Directly call the initialization constructor externally .

This is a simple example with only three components , So writing initializers by hand won't be too painful , but Wire It saves a lot of manual work for components and applications with more complex dependency diagrams .

4. How do we use it wire

According to the above wire Introduction to wire Is how it works , We have a general understanding of wire How to use , Now let's look at the implementation code .

First, we will build the corresponding structure according to the above example :

// user_service.go

package user

import (
	"github.com/google/wire"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

//  Configuration class 
type Config struct {

}

//  Back to the user UserStore object , It is actually returned by querying data model
type UserStore struct {
	Name string
	Age int32
}

//  structure UserStore Method , It should be db Query data 
func NewUserStore(cfg *Config, db *gorm.DB) (*UserStore, error) {
	// todo  from db Query data from 
	return &UserStore{}, nil
}

//  Default construction Config Methods 
func NewDefaultConfig() *Config {
	return &Config{}
}

//  Database connection information structure 
type ConnectionInfo struct {
	Driver string
	Source string
}

// DB Construction object of 
func NewDB(info *ConnectionInfo) (*gorm.DB, error) {
	return gorm.Open(mysql.Open(info.Source))
}

// wire Construction provided Set Set 
var UserStoreSet = wire.NewSet(NewUserStore, NewDefaultConfig)

Let's implement wire.go The file of , The function used to generate the corresponding call

// +build wireinject

package user

import "github.com/google/wire"

func InitUserStore(info *ConnectionInfo) (*UserStore, error) {
	wire.Build(UserStoreSet, NewDB)
	return nil, nil
}

wire Construction file of ,“ // +build wireinject” This has to be added with , Otherwise, it will be generated later wire_gen.go Your methods will conflict .

Actually implement wire After execution , Will generate wire.gen The file of

package user

// Injectors from wire.go:

func InitUserStore(info *ConnectionInfo) (*UserStore, error) {
	config := NewDefaultConfig()
	db, err := NewDB(info)
	if err != nil {
		return nil, err
	}
	userStore, err := NewUserStore(config, db)
	if err != nil {
		return nil, err
	}
	return userStore, nil
}

Then call it directly :

// main.go

func main() {
		info := &user.ConnectionInfo{
			Driver: "mysql",
			Source: "root:[email protected](127.0.0.1:3333)/user?charset=utf8mb4&parseTime=True&loc=Local",
		}
		userStore, _ := user.InitUserStore(info)
		log.Printf("user store result:%v\n", userStore)
}

5. wire Advanced features of

  • ProviderSet Nested calls to

In the above example , Use wire.NewSet Tectonic Set Set as follows :

// wire Construction provided Set Set 
var UserStoreSet = wire.NewSet(NewUserStore, NewDefaultConfig)

Here in addition to using the constructor in front , We can also pass in other default wire.NewSet Tectonic Set aggregate , Equivalent to that we can nest constructs . for example , If NewDefaultConfig It's also a wire.NewSet Constructed set , as follows :

// config.go
var ProviderConfigSet = wire.NetSet(NewConfig, NewOptions)

Then the above use wire structure UserStoreSet Of can be written as :

// wire Construction provided Set Set 
var UserStoreSet = wire.NewSet(NewUserStore, ProviderConfigSet)

This nesting method can package multiple constructors with the same dependencies of multiple types into a collection , You only need to use this set later .

alike , stay wire.Build We can also directly pass in the corresponding UserStoreSet

  • Use wire.Struct take Provider Inject struc in
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"))

have access to wire.Sturct structure , take FooBar Structure dependent MyFoo and MyBar Field , adopt ProvideFoo and ProvideBar The provided value is injected into FooBar structure , Through the following wire Construction file of , We can look at the specific implementation :

// wire.go
func CreateFoobar() (*FooBar, error) {
	wire.Build(Set)
	return nil, nil
}

Generated wire_gen.go file , You can see the concrete implementation :

// wire_gen.go
func CreateFoobar() (*FooBar, error) {
	foo := ProvideFoo()
	bar := ProvideBar()
	fooBar := &FooBar{
		MyFoo: foo,
		MyBar: bar,
	}
	return fooBar, nil
}
  • Use wire Of Value Bind injection value

Use wire.Value Bind an actual dependent value , In the following code , We can see through Value To construct a Foo struct Value .

type Foo struct {
    X int
}

func injectFoo() Foo {
    wire.Build(wire.Value(Foo{X: 42}))
    return Foo{}
}

​ Use InterfaceValue Binding implementation interface Value

func CreateReader() io.Reader {
    wire.Build(wire.InterfaceValue(new(io.Reader), os.Stdin))
    return nil
}

​ take io.Reader The interface implementation of is bound to os.Stdin Input implementation , Used for construction io.Reader The dependencies of .

​ notes : wire The specific implementation code can be viewed by referring to the above

6. Reference material

wire Blog :https://go.dev/blog/wire

official guide: https://github.com/google/wire/blob/main/docs/guide.md

 

原网站

版权声明
本文为[CK continues to grow]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/186/202207050813286351.html