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, decrease
injector
In 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
和 package
There are empty lines between them, and if there are no empty lines,build tag
If 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. while
dig
Dependency errors can only be found at run time. - Avoid dependency inflation,
wire
The generated code contains only the dependent, anddig
There 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…