SoFunction
Updated on 2025-03-05

In-depth and easy-to-understand the use of Wire's dependency injection tool

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.

GoLanguage has many dependency injection tools, and this article will dive into a popular oneGoLanguage Dependency Injection Tool—Wire

Ready? Prepare a cup of your favorite coffee or tea and find out with this article.

Wire

Wireis 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 installWiretool:

go install /google/wire/cmd/wire@latest

Please make sure you have already installed it before installation$GOPATH/binAdd to environment variables$PATHinside.

Basic use of Wire

Pre-code preparation

Although we have passedgo installThe command has been installedWireCommand line tool, but in specific projects, we still need to install the required project through the following commandWireDepend on to combineWireTool generation code:

go get /google/wire@latest

Next, let's simulate a simplewebBlog project, write relevant code for querying article interfaces, and useWireTools generate code.

First, we define the relevant types and methods and provide the correspondingInitialize function

definitionPostHandlerStructure, method to create a registered routeRegisterRoutesand query article routing methodsGetPostByIdand initialized functionsNewPostHandler, and it depends onIPostServiceinterface:

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}
}

definitionIPostServiceInterface and provides a specific implementationPostService, then createGetPostByIdMethod, used to process the logic of querying articles, and then provide the initialization functionNewPostService, this function returnsIPostServiceInterface 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 initializationfunctionNewGinEngineAndRegisterRoute, this function depends on*Type, function internal call relatedhandlerStructure 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 thatWireTools can generate corresponding dependency injection code.

First we need to create awireThe configuration file is usually named. In this file, we need to define one or more injector functions (Injectorfunction, which will be explained in the following content) to guide theWireTools 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 useMethods to declare dependencies, includingPostHandlerPostServiceandInitGinEngineAs a constructor for dependencies.

The purpose of this is to connect or bind all the initialization functions we defined earlier. When we runwireWhen 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 wireinjector// +build wireinject(go 1.18Previous versions used) comments, the function is to only usewireThis part of the code will only be compiled when the tool is used, and will be ignored in other cases.

NextExecute the file directorywireCommand, generatewire_gen.goThe 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 thatWireThe benefits of tools.

Wire's core concept

WireThere 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 codeNewPostHandlerfunction:

func NewPostHandler(serv ) *PostHandler {
    return &PostHandler{serv: serv}
}

The return value is not limited to one, if necessary, you can add one extraerrorreturn value.

If there are too many providers, we can also connect in group form, for examplepostRelatedhandlerandserviceMake combinations:

package handler
var PostSet = (NewPostHandler, )

useThe function groups the provider, and the function returns aProviderSetStructure. Not only that,Can be moreProviderSetGrouping(PostSet, XxxSet)

For the previousInitializeAppWe can upgrade the function like this:

//go:build wireinject
package wire
func InitializeAppV2() * {
    (
       ,
       ,
    )
    return &{}
}

Then passWireThe 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 &{}
}

InitializeAppThe 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{}
}
···

NewPostHandlerFunctions depend oninterface,NewPostServiceThe function returnsIPostServiceThe value of the interface, the type of these two places matches, soWireTools can correctly identify and generate code. However, this is not a recommended best practice. BecauseGoIn-houseBest PracticesYes ReturnSpecific typesso it is best toNewPostServiceReturn to the specific typePostServiceValue:

func NewPostServiceV2() *PostService {
    return &PostService{}
}

But soWireThe tool will thinkIPostServiceInterface type andPostServiceType 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,WireThe 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

WireThe 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 definedNameandPublicAccountAnd structure typeUser, and provided separatelyNameandPublicAccountInitialization function (providers). Then define an injector (injectorsInitializeUser, used to construct connection providers and construct*UserExample.

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, useWireThe 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 itInitializeUserThe 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 expressionsMyNameThe 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 similarGetXXXfunction.

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 usepanicLet's write it more concisely:

func InitializeGin() * {
    panic((/* ... */))
}

summary

In this article, we discuss in detailGo WireBasic 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.WireTools allow dependency injection inGoIt'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!