Why Wire is different ?

Dependency injection is so important, that there are quite a few solutions for this in the Golang community already, such as dig from Uber and inject from Facebook. Both of them implement runtime dependency injection through Reflection Mechanisms.

Why did the Go Cloud team reinvent the wheel? Because in their opinion none of the above libraries conform to Go’s philosophy:

As a code generation tool, Wire can generate source code and implement dependency injection at compile time. It does not require Reflection or Service Locators. As you will see later, the codes generated by Wire have no difference from those written by hand. This approach brings some benefits:

Easy debug. If any dependency is missing, an error will be reported during compiling Since no service locators are needed, there are no special requirements for naming Avoid dependency bloat. The generated code will only import the dependency you need, while runtime dependency injection cannot identify unused dependencies until runtime. Dependency graph is stored in source code statically, which make tooling and visualization easier

The detail trade-off of designing Wire can be found on Go Blog.

Although the latest release of Wire is just v0.4.0, it has achieved the goals set by the team and is quite mature . No major changes are expected in the future. This can be seen from the team’s statement:

It works well for the tasks it was designed to perform, and we prefer to keep it as simple as possible. We’ll not be accepting new features at this time, but will gladly accept bug reports and fixes. — Wire team

Getting Started

Installing wire is quite easy, just run

go get github.com/google/wire/cmd/wire

you’ll get wire command line tool installed at $GOPATH/bin , make sure $GOPATH/bin is in $PATH , then you can run wire at any directory.

Before going further, we need to explain two core concepts in Wire: Provider and Injector.

Provider: plain function for creating components. These methods take the required dependencies as parameters, create a component and return it.

A component can be an object or function, in fact it can be of any type. the only limit is: one type can only have a single provider in the entire dependency graph. So a provider returning int is not a good idea. In this case, you can solve it by defining a type alias. For example, first define type Category int and then let the provider return the Category type

Here are the typical provider examples:

// DefaultConnectionOpt provide default connection option

func DefaultConnectionOpt()*ConnectionOpt{...}// NewDb provide an Db object

func NewDb(opt *ConnectionOpt)(*Db, error){...}// NewUserLoadFunc provide a function which can load user

func NewUserLoadFunc(db *Db)(func(int) *User, error){...}

In practice, a group of related providers are often put together and organized into a ProviderSet to facilitate maintenance and switching.

var DbSet = wire.NewSet(DefaultConnectionOpt, NewDb)

Injector: A function generated by wire, that call providers in dependency order.

In order to generate injector, we define the injector function signature in wire.go (file name is not mandatory, but this is generally the case) . Then call wire.Build in the function body with provider as parameter (regardless of the order).

Since the functions in wire.go do not really return a value, in order to avoid compiler errors, simply wrap them with panic functions. Don’t worry about runtime error, because it will not actually execution, it is just the hint for generating real code. A simple wire.go example:

// +build wireinject



package main



import "github.com/google/wire"



func UserLoader()(func(int)*User, error){

panic(wire.Build(NewUserLoadFunc, DbSet))

}



var DbSet = wire.NewSet(DefaultConnectionOpt, NewDb)

Having this code done, run command wire will generate file wire_gen.go , which holds the actual implementation of the injector function. Any non-injector code in wire.go will be copied to wire_gen.go as is (although technically allowed, this is not recommended). The generated code is as follows:

// Code generated by Wire. DO NOT EDIT.



//go:generate wire

//+build !wireinject



package main



import (

"github.com/google/wire"

)



// Injectors from wire.go:



func UserLoader() (func(int) *User, error) {

connectionOpt := DefaultConnectionOpt()

db, err := NewDb(connectionOpt)

if err != nil {

return nil, err

}

v, err := NewUserLoadFunc(db)

if err != nil {

return nil, err

}

return v, nil

}



// wire.go:



var DbSet = wire.NewSet(DefaultConnectionOpt, NewDb)

There are two interesting points in the above code:

The first line of wire.go // + build wireinject , this build tag ensures that the wire.go file is ignored during regular compilation (because the wireinject tag is not specified during regular compilation). And In contrast, the 4th line of wire_gen.go // + build! Wireinject . These two sets of opposing build tags guarantee that in any case, only one file of wire.go and wire_gen.go takes effect, avoiding compilation errors “function UserLoader has been defined” The automatically generated function UserLoader contains error handling. It’s almost the same as handwritten code. For such a simple initialization process, handwriting is acceptable， but when the number of components reaches tens, hundreds or even more, the advantages of automatic generation will be showed up.

There are two ways to trigger the “generate” action: go generate or wire .

The former is only valid if wire_gen.go already exists (because the third line of wire_gen.go // go: generate wire ).

While the latter can be executed at any time. And the latter supports more arguments to fine-tune the generation behavior, so it is recommended to always use the wire command.

Then we can use the real injector, for example:

package main



import "log"



func main() {

fn, err := UserLoader()

if err != nil {

log.Fatal(err)

}

user := fn(123)

...

}

If you accidentally forget a certain provider, wire will report specific errors to help developer resolve the problem quickly. For example, we modify wire.go to remove NewDb

// +build wireinject



package main



import "github.com/google/wire"



func UserLoader()(func(int)*User, error){

panic(wire.Build(NewUserLoadFunc, DbSet))

}



var DbSet = wire.NewSet(DefaultConnectionOpt) //forgot add Db provider

execute wire command, then an explicit error will be reported: “ no provider found for * example.Db ”

wire: /usr/example/wire.go:7:1: inject UserLoader: no provider found for *example.Db

needed by func(int) *example.User in provider "NewUserLoadFunc" (/usr/example/provider.go:24:6)

wire: example: generate failed

wire: at least one generate failure

Similarly, if unused providers are written in wire.go, there will be explicit error messages.