Preface
In daily project development, we often use the design pattern of dependency injection, with the purpose of reducing the coupling between code components and improving the maintainability, scalability and testability of the code.
But as the project size grows, the dependencies between components become complicated, and manually managing their dependencies can be cumbersome. To simplify this process, we can leverage the dependency injection code generation tool, which automatically generates the required code for us, thus easing the burden of manually handling dependency injection.
Go
Language has many dependency injection tools, and this article will dive into a popular oneGo
Language Dependency Injection Tool—Wire
。
Ready? Prepare a cup of your favorite coffee or tea and find out with this article.
Wire
Wire
is a dedicated dependency injection (Dependency Injection
) Designed code generation tool that automatically generates code for initializing various dependencies, helping us manage and inject dependencies more easily.
Wire installation
We can execute the following command to installWire
tool:
go install /google/wire/cmd/wire@latest
Please make sure you have already installed it before installation$GOPATH/bin
Add to environment variables$PATH
inside.
Basic use of Wire
Pre-code preparation
Although we have passedgo install
The command has been installedWire
Command line tool, but in specific projects, we still need to install the required project through the following commandWire
Depend on to combineWire
Tool generation code:
go get /google/wire@latest
Next, let's simulate a simpleweb
Blog project, write relevant code for querying article interfaces, and useWire
Tools generate code.
First, we define the relevant types and methods and provide the correspondingInitialize function:
definitionPostHandler
Structure, method to create a registered routeRegisterRoutes
and query article routing methodsGetPostById
and initialized functionsNewPostHandler
, and it depends onIPostService
interface:
package handler import ( "chenmingyong0423/blog/tutorial-code/wire/internal/post/service" "/gin-gonic/gin" "net/http" ) type PostHandler struct { serv } func (h *PostHandler) RegisterRoutes(engine *) { ("/post/:id", ) } func (h *PostHandler) GetPostById(ctx *) { content := (ctx, ("id")) (, content) } func NewPostHandler(serv ) *PostHandler { return &PostHandler{serv: serv} }
definitionIPostService
Interface and provides a specific implementationPostService
, then createGetPostById
Method, used to process the logic of querying articles, and then provide the initialization functionNewPostService
, this function returnsIPostService
Interface type:
package service import ( "context" "fmt" ) type IPostService interface { GetPostById(ctx , id string) string } var _ IPostService = (*PostService)(nil) type PostService struct { } func (s *PostService) GetPostById(ctx , id string) string { return ("Welcome to follow this Nuggets, author: Chen Mingyong") } func NewPostService() IPostService { return &PostService{} }
Define an initializationfunction
NewGinEngineAndRegisterRoute
, this function depends on*
Type, function internal call relatedhandler
Structure method creates a route:
package ioc import ( "chenmingyong0423/blog/tutorial-code/wire/internal/post/handler" "/gin-gonic/gin" ) func NewGinEngineAndRegisterRoute(postHandler *) * { engine := () (engine) return engine }
Generate code using Wire tools
The pre-code is ready, and we will write the core code so thatWire
Tools can generate corresponding dependency injection code.
First we need to create awire
The configuration file is usually named. In this file, we need to define one or more injector functions (
Injector
function, which will be explained in the following content) to guide theWire
Tools generate code.
//go:build wireinject package wire import ( "chenmingyong0423/blog/tutorial-code/wire/internal/post/handler" "chenmingyong0423/blog/tutorial-code/wire/internal/post/service" "chenmingyong0423/blog/tutorial-code/wire/ioc" "/gin-gonic/gin" "/google/wire" ) func InitializeApp() * { ( , , , ) return &{} }
In the above code, we define a for initializationInside the injector function, we use
Methods to declare dependencies, including
PostHandler
、PostService
andInitGinEngine
As a constructor for dependencies.
The purpose of this is to connect or bind all the initialization functions we defined earlier. When we run
wire
When a tool generates code, it automatically creates and injects the required instances based on these dependencies.
Note: The first line of the file must be added//go:build wireinject
or// +build wireinject
(go 1.18
Previous versions used) comments, the function is to only usewire
This part of the code will only be compiled when the tool is used, and will be ignored in other cases.
NextExecute the file directory
wire
Command, generatewire_gen.go
The file, the content is as follows:
// Code generated by Wire. DO NOT EDIT. //go:generate go run /google/wire/cmd/wire //go:build !wireinject // +build !wireinject package wire import ( "chenmingyong0423/blog/tutorial-code/wire/internal/post/handler" "chenmingyong0423/blog/tutorial-code/wire/internal/post/service" "chenmingyong0423/blog/tutorial-code/wire/ioc" "/gin-gonic/gin" ) // Injectors from : func InitializeApp() * { iPostService := () postHandler := (iPostService) engine := (postHandler) return engine }
The generated code is not much different from our handwriting. When we have many components and complex dependencies, we will feel thatWire
The benefits of tools.
Wire's core concept
Wire
There are two core concepts: provider (providers
) and injector (injectors
)。
Wire providers
Provider: A function that can produce values, that is, a function that has a return value. For example, in the beginner codeNewPostHandler
function:
func NewPostHandler(serv ) *PostHandler { return &PostHandler{serv: serv} }
The return value is not limited to one, if necessary, you can add one extraerror
return value.
If there are too many providers, we can also connect in group form, for examplepost
Relatedhandler
andservice
Make combinations:
package handler var PostSet = (NewPostHandler, )
useThe function groups the provider, and the function returns a
ProviderSet
Structure. Not only that,Can be more
ProviderSet
Grouping(PostSet, XxxSet)
。
For the previousInitializeApp
We can upgrade the function like this:
//go:build wireinject package wire func InitializeAppV2() * { ( , , ) return &{} }
Then passWire
The command generates code, which is consistent with the previous result.
Wire injectors
Injector (injectors
) is to bring all providers (providers
) Connect and review our previous code:
func InitializeApp() * { ( , , , ) return &{} }
InitializeApp
The function is an injector, and the function passes internally.The function connects all providers and returns
&{}
, the return value is not actually used, it is just to meet the compiler's requirements and avoid error reporting. The real return value comes from。
Advanced usage of Wire
Bind interface
Recalling the code we wrote before:
package handler ··· func NewPostHandler(serv ) *PostHandler { return &PostHandler{serv: serv} } ··· pakacge service ··· func NewPostService() IPostService { return &PostService{} } ···
NewPostHandler
Functions depend oninterface,
NewPostService
The function returnsIPostService
The value of the interface, the type of these two places matches, soWire
Tools can correctly identify and generate code. However, this is not a recommended best practice. BecauseGo
In-houseBest PracticesYes ReturnSpecific typesso it is best toNewPostService
Return to the specific typePostService
Value:
func NewPostServiceV2() *PostService { return &PostService{} }
But soWire
The tool will thinkIPostService
Interface type andPostService
Type mismatch, resulting in the generation of code failure. Therefore, we need to modify the code of the injector:
func InitializeAppV3() * { ( , service.NewPostServiceV2, , (new(), new(*)), ) return &{} }
useTo establish a binding relationship between interface type and specific implementation type,
Wire
The tool can type match and generate code based on this binding relationship.
The first parameter of the function is a pointer to the desired interface type value, and the second actual parameter is a pointer to the type value that implements the interface type.
Struct Providers
Wire
The library has a function that is, it can construct structures according to existing types. Let's take a look at the following example:
package main type Name string func NewName() Name { return "Chen Mingyong" } type PublicAccount string func NewPublicAccount() PublicAccount { return "Official Account: Go Technology Driving" } type User struct { MyName Name MyPublicAccount PublicAccount } func InitializeUser() *User { ( NewName, NewPublicAccount, (new(User), "MyName", "MyPublicAccount"), ) return &User{} }
In the above code, the custom type is first definedName
andPublicAccount
And structure typeUser
, and provided separatelyName
andPublicAccount
Initialization function (providers
). Then define an injector (injectors
)InitializeUser
, used to construct connection providers and construct*User
Example.
useThe function needs to pass two parameters. The first parameter is the pointer value of the structure type, and the other parameter is a mutable parameter that represents the name set of the structure fields that need to be injected.
According to the above code, useWire
The code generated by the tool is as follows:
func InitializeUser() *User { name := NewName() publicAccount := NewPublicAccount() user := &User{ MyName: name, MyPublicAccount: publicAccount, } return user }
If we don't want to return the pointer type, we just need to modify itInitializeUser
The return value of the function is just a non-pointer.
Bind value
Sometimes we can pass in the injectorValue expressionAssign a value to a type instead of relying on the provider (providers
)。
func InjectUser() User { ((User{MyName: "Chen Mingyong"})) return User{} }
In the above code, useFunctions are specified directly through expressions
MyName
The generated code is as follows:
func InjectUser() User { user := _wireUserValue return user } var ( _wireUserValue = User{MyName: "Chen Mingyong"} )
It should be noted that the value expression will be copied into the generated code file.
For interface types, you can useInterfaceValue
:
func InjectPostService() { ((new(), &{})) return nil }
Use structure fields as providers
Sometimes you can use a field in the structure as a provider to generate a similarGetXXX
function.
func GetUserName() Name { ( NewUser, (new(User), "MyName"), ) return "" }
You can useThe function adds any field, and the generated code is as follows:
func GetUserName() Name { user := NewUser() name := return name } func NewUser() User { return User{MyName: Name("Chen Mingyong"), MyPublicAccount: PublicAccount("Official Account: Go Technology Driving")} }
Cleaning functions
If a provider creates a value that needs to be cleaned (for example, closes a file), it can return a closure to clean up the resource. The injector uses it to return an aggregated cleanup function to the caller, or to clean up the resource when the provider called later in the injector implementation returns an error.
func provideFile(log Logger, path Path) (*, func(), error) { f, err := (string(path)) if err != nil { return nil, nil, err } cleanup := func() { if err := (); err != nil { (err) } } return f, cleanup, nil }
Alternative injector syntax
If you don't like writing something like this →return &{}
Place it at the end of your injector function declaration, you can usepanic
Let's write it more concisely:
func InitializeGin() * { panic((/* ... */)) }
summary
In this article, we discuss in detailGo Wire
Basic usage and advanced features of the tool. It is a code generation tool designed for dependency injection. It not only provides basic dependency parsing and code generation functions, but also supports a variety of advanced usages such as interface binding and constructing structures.
The design pattern of dependency injection is widely used.Wire
Tools allow dependency injection inGo
It's easier in language.
The above is a detailed content on the use of the go dependency injection tool Wire in an easy-to-understand way. For more information about the go dependency injection tool Wire, please follow my other related articles!