Wechat official account: Wu Qinqiang’s late night canteen

The opening

Dig and Wire are both Go dependency injection tools, so why switch from Dig to Wire when they are essentially similar frameworks?

scenario

Let’s start with the scene.

Let’s say our project hierarchy is router-> Controller -> Service -> DAO.

It looks something like this:

Now we need to expose an order service interface.

Go file on the home page.

package main import ( "fmt" "github.com/gin-gonic/gin" "github.com/wuqinqiang/digvswire/dig" "github.com/wuqinqiang/digvswire/router" ) func main() { serverStart() } func serverStart() { defer func() { if err := recover(); err ! = nil { fmt.Printf("init app err:%v\n", err) } }() e := gin.Default() di := dig.ContainerByDig() err := router.RegisterRouter(e, di) if err ! = nil { fmt.Printf("register router err:%v", err) } _ = e.Run(":8090") }Copy the code

Gin is used here to start the project. Then we look at dig.containerbydig (),

dig

package dig

import (
	"github.com/wuqinqiang/digvswire/controller"
	"github.com/wuqinqiang/digvswire/dao"
	"github.com/wuqinqiang/digvswire/server"
	"go.uber.org/dig"
)

func ContainerByDig() *dig.Container {
	d := dig.New()
	_ = d.Provide(dao.NewOrderDao)
	_ = d.Provide(server.NewOrderServer)
	_ = d.Provide(controller.NewOrderHandler)
	return d
}

Copy the code

Start by creating a DI container with dig.new (). The Provide function is used to add a service provider, and the first parameter of the Provide function is essentially a function. One that tells the container “What can I offer, and what do I need in order to provide it?” The function.

For example, if we look at the second server.neworderServer,

package server

import (
	"github.com/wuqinqiang/digvswire/dao"
)

var _ OrderServerInterface = &OrderServer{}

type OrderServerInterface interface {
	GetUserOrderList(userId string) ([]dao.Order, error)
}

type OrderServer struct {
	orderDao dao.OrderDao
}

func NewOrderServer(order dao.OrderDao) OrderServerInterface {
	return &OrderServer{orderDao: order}
}

func (o *OrderServer) GetUserOrderList(userId string) (orderList []dao.Order, err error) {
	return o.orderDao.GetOrderListById(userId)
}

Copy the code

I can Provide an OrderServerInterface service, but I need to rely on a DAO.OrderDao.

In that code,

_ = d.Provide(dao.NewOrderDao)
_ = d.Provide(server.NewOrderServer)
_ = d.Provide(controller.NewOrderHandler)

Copy the code

Since our chain of calls is Controller ->server-> DAO, they essentially rely on controller<-server< -DAO, but not on a concrete implementation, but on an abstract interface.

So you see that Provide is written in dependency order.

There is no need at all, as dig only parses these functions and extracts their parameters and return values in this step. The container structure is then organized based on the returned parameters. The functions passed in are not executed at this step, so the order before and after the Provide phase doesn’t matter, just make sure you don’t miss dependencies.

All set, we started registering a route to get the order,

err := router.RegisterRouter(e, d)

// router.go
func RegisterRouter(e *gin.Engine, dig *dig.Container) error {
	return dig.Invoke(func(handle *controller.OrderHandler) {
		e.GET("/user/orders", handle.GetUserOrderList)
	})
}

Copy the code

At this point, we call invoke to retrieve the * Controller.OrderHandler object.

Calling the Invoke method parses the parameters passed in, If there is a handle * controller.orderHandler in the argument, it will look for the function provided in the container. The return type of the function is handle * Controller.orderHandler,

I’ll be able to find,

_ = d.P rovide (controller. NewOrderHandler) / / corresponding func NewOrderHandler (server server. OrderServerInterface) * OrderHandler {  return &OrderHandler{ server: server, } }Copy the code

Found that the function of tangible and server OrderServerInterface, then go to find corresponding returns the function of this type,

_ = d.provide (server.neworderServer) // corresponding func NewOrderServer(OrderDao.OrderDao) OrderServerInterface {return &OrderServer{orderDao: order} }Copy the code

Find the parameter order DAO.OrderDao

Func NewOrderDao() OrderDao {return new(OrderDaoImpl)}Copy the code

Finally, it is found that NewOrderDao does not have dependencies, so there is no need to query dependencies. Start the function call NewOrderDao() and pass the returned OrderDao to the upper NewOrderServer(Order DAo.OrderDAO) for the function call. NewOrderServer (order dao. OrderDao) returned OrderServerInterface continue to return to the upper NewOrderHandler (server server OrderServerInterface) Invoke(func(handle * controller.orderHandler) {}, Invoke(func(handle * controller.orderHandler) {},

The whole link is open. Use a crude diagram to describe this process

The entire process at DIG uses a reflection mechanism to calculate dependencies and construct dependent objects at run time.

What’s the problem with that?

Suppose I now comment out a line of code provided, for example,

func ContainerByDig() *dig.Container {
	d := dig.New()
	//_ = d.Provide(dao.NewOrderDao)
	_ = d.Provide(server.NewOrderServer)
	_ = d.Provide(controller.NewOrderHandler)
	return d
}

Copy the code

We don’t report any errors when we compile the project, only missing dependencies at run time.

wire

Again, we use wire as our DI container.

Wire also has two core concepts: Provider and Injector.

The concept of Provider is the same as dig :” What can I provide? What I need to rely on. “”

For example, the following code in wire.go,

//+build wireinject

package wire

import (
	"github.com/google/wire"
	"github.com/wuqinqiang/digvswire/controller"
	"github.com/wuqinqiang/digvswire/dao"
	"github.com/wuqinqiang/digvswire/server"
)

var orderSet = wire.NewSet(
	dao.NewOrderDao,
	server.NewOrderServer,
	controller.NewOrderHandler)

func ContainerByWire() *controller.OrderHandler {
	wire.Build(orderSet)
	return &controller.OrderHandler{}
}

Copy the code

Among them, the dao. NewOrderDao, server. NewOrderServer and controller. NewOrderHandler is the Provider.

You’ll notice that there’s also a call to wire.NewSet to pull them together and assign to a variable orderSet.

We use the concept of ProviderSet. The idea is to package a set of related providers.

The benefits are:

  • The structure relies on clarity and is easy to read.
  • In groups, decreaseinjectorIn theBuild.

As far as the Injector is concerned, it essentially means calling the Provider’s function based on the dependency and eventually generating the object (service) we want.

For example, ContainerByWire() above is an Injector.

The whole idea of the wire.go file is to define the Injector and implement the required Provider.

Finally, after executing wire in the current wire.go folder,

If there is a problem with your dependency, an error message will be displayed. Let’s say I hide the top one right nowdao.NewOrderDao, then will appear

If there are no problems with the dependencies, a wire_gen.go file is eventually generated.

// Code generated by Wire. DO NOT EDIT. //go:generate go run github.com/google/wire/cmd/wire //+build ! wireinject package wire import ( "github.com/google/wire" "github.com/wuqinqiang/digvswire/controller" "github.com/wuqinqiang/digvswire/dao" "github.com/wuqinqiang/digvswire/server" ) // Injectors from wire.go: func ContainerByWire() *controller.OrderHandler { orderDao := dao.NewOrderDao() orderServerInterface := server.NewOrderServer(orderDao) orderHandler := controller.NewOrderHandler(orderServerInterface) return orderHandler } // wire.go: var orderSet = wire.NewSet(dao.NewOrderDao, server.NewOrderServer, controller.NewOrderHandler)Copy the code

Note the above two files. Go line 1 //+build wireinject. This build tag ensures that the wireinject file is ignored during regular compilation. The wire_gen.go //+build! Wireinject. The two opposing build tags ensure that only one of the two files is valid in any case, avoiding the “ContainerByWire() method being redefined “compilation error.

Now that we can really use injector, we replaced it with DIG in the entry file.

func serverStart() { defer func() { if err := recover(); err ! = nil { fmt.Printf("init app err:%v\n", err) } }() e := gin.Default() err := router.RegisterRouterByWire(e, wire.ContainerByWire()) if err ! = nil { panic(err) } _ = e.Run(":8090") }Copy the code
func RegisterRouterByWire(e *gin.Engine, handler *controller.OrderHandler) error {
	e.GET("/v2/user/orders", handler.GetUserOrderList)
	return nil
}

Copy the code

Everything is normal.

Of course wire has a point to note, in the first few lines of the wire.go file:

//+build wireinject

package wire

Copy the code

build tag 和 packageThere are empty lines between them, and if there are no empty lines,build tagIf the error is not recognized, the compiler will report a duplicate error:

There are many more advanced operations to learn for yourself.

conclusion

Dig and Wire are two DI tools in GO. Dig is dependency injection implemented through runtime reflection. Wire, on the other hand, generates the dependency injection code according to the custom code through commands. The dependency injection is completed at compile time without reflection mechanism. The benefits are:

  • Easy to check, if there are dependency errors, can be found at compile time. whiledigDependency errors can only be found at run time.
  • Avoid dependency inflation,wireThe generated code contains only the dependent, anddigThere can be a lot of useless dependencies.
  • Dependencies exist statically in the source code for tool analysis.

Reference

[1] github.com/google/wire

[2] github.com/uber-go/dig

[3] medium.com/@dche423/ma…

[4] www.cnblogs.com/li-peng/p/1…