SoFunction
Updated on 2025-03-05

Tutorial on using the official Go dependency injection tool Wire

1. Preface

I have been in contact with Golang for a while and found that Golang also needs a dependency injection framework similar to Spring in Java. If the project scale is relatively small, it is not a big problem whether there is a dependency injection framework, but when the project becomes larger, it is very necessary to have a suitable dependency injection framework. Through research, we learned that the commonly used dependency injection tools in Golang are mainly Inject, Dig, etc. But today we are mainly introducing Wire developed by the Go team, a tool that implements dependency injection during compilation.

2. What is Dependency Injection (DI)

Speaking of dependency injection, it is necessary to introduce another noun control inversion (IoC). IoC is a design idea, and its core role is to reduce the coupling degree of code. Dependency injection is a design pattern that implements control inversion and solves dependency problems.

For example, suppose that our code hierarchy relationship is a dal layer connecting the database, which is responsible for the read and write operations of the database. Then the service on our dal layer is responsible for calling the dal layer to process data. In our current code, it might look like this:

// dal/

func (u *UserDal) Create(ctx , data *UserCreateParams) error {
    db := ().Model(&{})
    user := {
      Username: ,
      Password: ,
   }

    return (&user).Error
}

// service/
func (u *UserService) Register(ctx , data *) (*, error) {
   params := {
      Username: ,
      Password: ,
   }

   err := ().Create(ctx, params)
   if err != nil {
      return nil, err
   }

   registerRes := {
      Msg: "register success",
   }

   return &registerRes, nil
}

In this code, the hierarchical dependency is service -> dal -> db, and the upstream hierarchy passesGetxxxInstantiate dependencies. But in actual production, our dependency chains are less vertical dependencies, and more horizontal dependencies. That is, in one method, we may need to call it multiple timesGetxxxThis makes our code extremely unconcise.

Not only that, our dependencies are written to death, that is, the dependant's code writes the generation relationship of the dependant's dependency. When the generation method of the dependant is changed, we also need to change the dependant's function, which greatly increases the amount of code modification and the risk of errors.

Next, we use dependency injection to modify the code:

// dal/
type UserDal struct{
    DB *
}

func NewUserDal(db *) *UserDal{
    return &UserDal{
        DB: db
    }
}

func (u *UserDal) Create(ctx , data *UserCreateParams) error {
    db := (&{})
    user := {
      Username: ,
      Password: ,
   }

    return (&user).Error
}

// service/
type UserService struct{
    UserDal *
}

func NewUserService(userDal ) *UserService{
    return &UserService{
        UserDal: userDal
    }
}

func (u *UserService) Register(ctx , data *) (*, error) {
   params := {
      Username: ,
      Password: ,
   }

   err := (ctx, params)
   if err != nil {
      return nil, err
   }

   registerRes := {
      Msg: "register success",
   }

   return &registerRes, nil
}

//  
db := ()
userDal := (db)
userService := (userDal)

As in the above encoding situation, we implement inter-hierarchical dependency injection by injecting the db instance object into dal and then injecting the dal instance object into service. Decoupled part of the dependencies.

The above implementation method is indeed no problem when the system is simple and the amount of code is small. But when the project is so large that the relationship between structures becomes very complex, the way each dependency is created manually and then assembled layer by layer will become extremely cumbersome and prone to errors. At this time, the Warriors Wire appeared!

3. Wire Come

3.1 Introduction

Wire is a lightweight Golang dependency injection tool. It is developed by the Go Cloud team and it completes dependency injection during the compilation period by automatically generating code. It does not require a reflection mechanism. You will see later that the code generated by Wire is no different from handwriting.

3.2 Quick use

Wire installation:

go get /google/wire/cmd/wire

The above command will be$GOPATH/binGenerate an executable program inwire, this is the code generator. Can$GOPATH/binAdd system environment variables$PATH, so it can be executed directly on the command linewireOrder.

Let's take a look at how to use it in an examplewire

Now we have three types like this:

type Message string
type Channel struct {
    Message Message
}
type BroadCast struct {
    Channel Channel
}

The init method of the three:

func NewMessage() Message {
    return Message("Hello Wire!")
}
func NewChannel(m Message) Channel {
    return Channel{Message: m}
}
func NewBroadCast(c Channel) BroadCast {
    return BroadCast{Channel: c}
}

Suppose Channel has a GetMsg method and BroadCast has a Start method:

func (c Channel) GetMsg() Message {
    return 
}

func (b BroadCast) Start() {
    msg := ()
    (msg)
}

If we write the code manually, our writing method should be:

func main() {
    message := NewMessage()
    channel := NewChannel(message)
    broadCast := NewBroadCast(channel)

    ()
}

If usedwire, what we need to do becomes the following work:

1. Extract an init method InitializeBroadCast:

func main() {
    b := ()

    ()
}

2. Write a file for wire tool to parse dependencies and generate code:

//+build wireinject

package demo

func InitializeBroadCast() BroadCast {
    (NewBroadCast, NewChannel, NewMessage)
    return BroadCast{}
}

Note: You need to add build constraints in the file header://+build wireinject

3. Use the wire tool to generate code and execute commands in the directory where you are located:wire gen . The following code will be generated, that is, the Init function that is actually used when compiling the code:

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject
func InitializeBroadCast() BroadCast {
    message := NewMessage()
    channel := NewChannel(message)
    broadCast := NewBroadCast(channel)
    return broadCast
}

We tellwire, the various components we useinitmethod(NewBroadCastNewChannelNewMessage),SowireThe tool will automatically deduce dependencies based on the function signatures of these methods (parameter type/return value type/function name).

andwire_gen.goThere is one position at the head of the file+build, but one iswireinject, the other one is!wireinject+buildIn fact, it is a feature of the Go language. Conditional compilation similar to C/C++, executiongo buildYou can pass in some options to determine whether some files are compiled based on this option.wireTools will only handlewireinjectfile, so ourThe file needs to be added. Generatedwire_gen.goIt's for us to use.wireNo processing is required, so there is!wireinject

3.3 Basic concepts

WireThere are two basic concepts.Provider(Constructor) andInjector(Injector)

  • ProviderIn fact, it is the ordinary method of generating components. These methods receive the required dependencies as parameters, create the component and return it. Our example aboveNewBroadCastthat isProvider
  • InjectorIt can be understood asProvidersconnector, which is used to call in dependency orderProvidersand finally return to the build target. Our example aboveInitializeBroadCastthat isInjector

4. Wire usage practice

Here is a brief introductionwireApplication in Feishu Questionnaire Form Service.

Feishu Questionnaire Form ServiceprojectIn the module, the initialization of the handler layer, service layer and dal layer is achieved through parameter injection to achieve dependency inversion. passBuildInjectorThe injector is used to initialize all external dependencies.

4.1 Basic use

The pseudocode of dal is as follows:

func NewProjectDal(db *) *ProjectDal{
    return &ProjectDal{
        DB:db
    }
}

type ProjectDal struct {
   DB *
}

func (dal *ProjectDal) Create(ctx , item *) error {
   result := (item)
   return ()
}
// QuestionDal、QuestionModelDal...

The service pseudocode is as follows:

func NewProjectService(projectDal *, questionDal *, questionModelDal *) *ProjectService {
   return &projectService{
      ProjectDal:       projectDal,
      QuestionDal:      questionDal,
      QuestionModelDal: questionModelDal,
   }
}

type ProjectService struct {
   ProjectDal       *
   QuestionDal      *
   QuestionModelDal *
}

func (s *ProjectService) Create(ctx , projectBo *) (int64, error) {}

The handler pseudocode is as follows:

func NewProjectHandler(srv *) *ProjectHandler{
    return &ProjectHandler{
        ProjectService: srv
    }
}

type ProjectHandler struct {
   ProjectService *
}

func (s *ProjectHandler) CreateProject(ctx , req *) (resp *
, err error) {}

The pseudo-code is as follows:

func NewInjector()(handler *) *Injector{
    return &Injector{
        ProjectHandler: handler
    }
}

type Injector struct {
   ProjectHandler *
   // components,others...
}

In the following definition:

// +build wireinject

package app

func BuildInjector() (*Injector, error) {
   (
      NewInjector,

      // handler
      ,

      // services
      ,
      // More service...
      //dal
      ,
      ,
      ,
      // More dal...
      // db
      ,
      // other components...
   )

   return new(Injector), nil
}

implementwire gen ./internal/app/Generate wire_gen.go

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

func BuildInjector() (*Injector, error) {
   db, err := ()
   if err != nil {
      return nil, err
   }
   
   projectDal := (db)
   questionDal := (db)
   questionModelDal := (db)
   projectService := (projectDal, questionDal, questionModelDal)
   projectHandler := (projectService)
   injector := NewInjector(projectHandler)
   return injector, nil
}

Add the method to initialize the injector in

injector, err := BuildInjector()
if err != nil {
   return nil, err
}

//Project service startssvr := (, logOpt)
()

Note that if you run, it appearsBuildInjectorRedefine, then check yours//+build wireinjectandpackage appWhether there is a blank line between these two lines, this blank line must be! See /google/wire/issues/117

4.2 Advanced Features

4.2.1 NewSet

NewSetGenerally, when there are many initialization objects, it will be reduced.InjectorInformation. When our project is huge to a certain extent, it is conceivable that there will be a lot of providers.NewSetHelp us group these Providers according to business relationships and formProviderSet(Constructor collection), you only need to use this collection in the future.

// 
var ProjectSet = (NewProjectHandler, NewProjectService, NewProjectDal)

// 
func BuildInjector() (*Injector, error) {
   (InitGormDB, ProjectSet, NewInjector)

   return new(Injector), nil
}

4.2.2 Struct

The above exampleProviderAll are functions, except functions, structures can also act asProviderThe role.WireProvide us withStructural constructor(Struct Provider). The structure constructor creates a structure of a certain type and then fills its fields with parameters or calling other constructors.

// project_service.go
// Function providerfunc NewProjectService(projectDal *, questionDal *, questionModelDal *) *ProjectService {
   return &projectService{
      ProjectDal:       projectDal,
      QuestionDal:      questionDal,
      QuestionModelDal: questionModelDal,
   }
}

// Equivalent to(new(ProjectService), "*") // "*" represents all field injection
// It is also equivalent to(new(ProjectService), "ProjectDal", "QuestionDal", "QuestionModelDal")

// If individual attributes do not want to be injected, you can modify the struct definition:type App struct {
    Foo *Foo
    Bar *Bar
    NoInject int `wire:"-"`
}

4.2.3 Bind

BindThe function is to allow interface type dependencies to participateWireConstruction.WireThe construction of  rely relies on parameter types, and interface types are not supported.BindFunctions achieve the purpose of dependency injection by binding interface types and implementation types.

// project_dal.go
type IProjectDal interface {
   Create(ctx , item *) (err error)
   // ...
}

type ProjectDal struct {
   DB *
}

var bind = (new(IProjectDal), new(*ProjectDal))

4.2.4 CleanUp

The constructor can provide a cleanup function. If the subsequent constructor returns, the cleanup function returned by the previous constructor will be called. InitializationInjectorAfter that, this cleaning function can be obtained. Typical application scenarios of the cleaning function are file resources and network connection resources. The cleanup function is usually used as the second return value, and the parameter type isfunc(). whenProviderAny one of them has a cleanup function,InjectorThe function return value must also include the function. AndWirerightProviderThe number and order of return values ​​are as follows:

  • The first return value is the object to be generated
  • If there are 2 return values, the second return value must be func() or error
  • If there are 3 return values, the second return value must be func() and the third return value must be error
// 
func InitGormDB()(*, func(), error) {
    // Initialize db link    // ...
    cleanFunc := func(){
        ()
    }

    return db, cleanFunc, nil
}

// 
func BuildInjector() (*Injector, func(), error) {
   (
      ,
      // ...
      NewInjector
   )

   return new(Injector), nil, nil
}

// Generated wire_gen.gofunc BuildInjector() (*Injector, func(), error) {
   db, cleanup, err := ()
   // ...
   return injector, func(){
       // All provider cleaning functions will be here       cleanup()
   }, nil
}

// 
injector, cleanFunc, err := ()
defer cleanFunc()

For more details, please refer to the official guide for wire:/google/wire/blob/main/docs/

4.3 Advanced use

Then we use the abovewireAdvanced FeaturesprojectServices are code-renovated:

project_dal.go

type IProjectDal interface {
   Create(ctx , item *) (err error)
   // ...
}

type ProjectDal struct {
   DB *
}

// The method is a constructor provided by wire. "*" means injecting values ​​into all fields. Here you can use "DB" instead// Method binds interface and implementationvar ProjectSet = (
   (new(ProjectDal), "*"),
   (new(IProjectDal), new(*ProjectDal)))


func (dal *ProjectDal) Create(ctx , item *) error {}
// DalSet dal Injectionvar DalSet = (
   ProjectSet,
   // QuestionDalSet、QuestionModelDalSet...
)

project_service.go

type IProjectService interface {
   Create(ctx , projectBo *) (int64, error)
   // ...
}

type ProjectService struct {
   ProjectDal       
   QuestionDal      
   QuestionModelDal 

}
func (s *ProjectService) Create(ctx , projectBo *) (int64, error) {}

var ProjectSet = (
   (new(ProjectService), "*"),
   (new(IProjectService), new(*ProjectService)))

// ServiceSet service injectionvar ServiceSet = (
   ProjectSet,
   // other service set...
)

The handler pseudocode is as follows:

var ProjectHandlerSet = ((new(ProjectHandler), "*"))

type ProjectHandler struct {
   ProjectService 
}

func (s *ProjectHandler) CreateProject(ctx , req *) (resp *
, err error) {}

The pseudo-code is as follows:

var InjectorSet = ((new(Injector), "*"))

type Injector struct {
   ProjectHandler *
   // others...
}

 // +build wireinject

package app


func BuildInjector() (*Injector, func(), error) {
   (
      // db
      ,
      // dal
      ,
      // services
      ,
      // handler
      ,
      // injector
      InjectorSet,
      // other components...
   )

   return new(Injector), nil, nil
}

5. Things to note

5.1 The same type of problem

wire does not allow different injection objects to have the same type. Google officials believe that this situation is a design flaw. In this case, the type of the object can be distinguished by type alias.

For example, the service will operate two Redis instances at the same time, RedisA & RedisB

func NewRedisA() * {...}
func NewRedisB() * {...}

For this case, wire cannot deduce a dependency relationship. This can be implemented like this:

type RedisCliA *
type RedisCliB *

func NewRedisA() RedicCliA {...}
func NewRedisB() RedicCliB {...}

5.2 Singleton Problem

The essence of dependency injection is to use singletons to bind interfaces and implement mapping relationships between interface objects. In practice, some objects are stateful. Objects of the same type always change in different use case scenarios. Singletons will cause data errors and cannot save each other's states. For this scenario, we usually design multi-layer DI containers to achieve singleton isolation, or to manage the life cycle of the object by itself.

6. Conclusion

Wire is a powerful dependency injection tool. Unlike Inject, Dig, etc., Wire only generates code instead of using reflection to inject at runtime, so there is no need to worry about performance losses. During the project engineering process, Wire can help us to build and assemble complex objects well.

For more information about Wire, please send to:/google/wire

This is the end of this article about the tutorial on using Wire, the official Go dependency injection tool. For more related Go languages, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!