Google/Wire is a compile-time dependency injection framework for the Go language. Like Spring IoC, Wire is intended to free developers from the creation and management of massive dependencies in their projects, but the two are implemented in very different ways.

Dependency injection in Go

In Go, we usually create objects by passing in dependencies in constructors:

func main(a) {
	NewUserStore(conf.Load(),db.InitMySQL())
}

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

This works fine on small projects, but as projects get larger, the creation of a single object often requires multiple dependencies, which often have their own dependencies, making object creation tedious and error-prone.

How does Wire accomplish dependency injection

In development, we create objects in two steps:

  1. Defines a constructor for a structure
func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error){... }Copy the code
  1. Call the constructor of the structure to instantiate
NewUserStore(conf.Load(),db.InitMySQL())
Copy the code

In the first step we declare the dependencies needed to construct the structure, and what wire does is help us “write” the code in the second step.

Rely on the statement

To generate the code in step 2, we first need to declare all constructors (or, more precisely, all dependencies to inject) and pass them to the wire.build method:

func setup(ctx context.Context) (sv *server.Server, clean func(a).err error) {
	wire.Build(
		conf.Load,
		db.InitMySQL,
		userstore.NewUserStore,
	)
	return nil.nil.nil
}
Copy the code

The contents of the setup function are eventually replaced by wire with the following implementation (explained next):

func setup(ctx context.Context) (sv *server.Server, clean func(a).err error) {
	config := conf.Load()
	engine := db.InitMySQL()
	userstoreHandler, clean1, err := userstore.NewUserStore(config,engine)
	iferr ! =nil {
		clean()
		return nil.nil, err
	}
	sv := server.New(userstoreHandler)
	return sv, func(a) { clean1() }, nil
}
Copy the code

You can see that the code wire “writes” for us is actually the same code we would write ourselves, and it is easy to read. But as the dependencies increase, this code increases. Imagine a project with hundreds of dependencies and hundreds of lines of New… (…). , if err ! = nil {… } code.

Use Go Generate to generate dependency injection code

Wire does just that: it generates all the object creation code we need, and at development time we just declare what we need in the constructor of the structure.

How to use Wire in actual projects:

  1. throughwire.BindMethod, assuming this part of the code is placed ininject.goIn the file
  2. Use the go generate command to generate code

Go generate needs to be used with the //go:generate comment, create the wire_gen.go file and add the comment:

//go:generate wire
Copy the code
  1. Execute in the project directorygo generateCommand.

When you run this command, it scans the source code files associated with the current package, finds any special comments that contain //go:generate, and extracts and executes the commands that follow that special comment.

Func setup(CTX context.context) (SV * server.server, clean func(), err Error) method generated in wire_gen.go The method body is dependency injection (object creation) code.

  1. The last step is called in the main methodsetupMethod (here we assume that our project is an HTTP service that provides a RESTful interface)
package main

func main(a) {
	ctx := context.Background()
	srv, cleanup, err := setupGCP(ctx)
	iferr ! =nil {
		log.Fatalf("failed to setup the server, got error: %s", err)
	}
	defer cleanup()

	log.Fatal(srv.ListenAndServe(": 8080"))}Copy the code

Note that if inject. Go and wire_gen.go are in the same package, the IDE will raise a syntax error because there are two methods with the same method signature in both files. At this point, you can exclude one of the files using the //+build go build build constraint.

First add the following build tags to inject.go:

//+build wireinject
Copy the code

Then add a build constraint to wire_gen.go to exclude files with wireinject tags when go builds:

//+build ! wireinject
Copy the code

Use methods like wire.Bind, wire.Value to declare and organize dependencies

Wire extracts project dependencies from wire.Bind and generates dependency injection code for us when go generate scan code. How can we more efficiently and clearly “tell” wire about dependencies?

Wire provides several functions to help us organize and declare dependencies in a project:

  • wire.Bind: Binds an interface to its implementation
  • wire.Value: Wraps values (instances) as dependencies

.

More detailed documentation can be viewed here.