Dependency Injection Using google/wire

Keywords: Go Google Unix github

Wire Use Tutorial

Wire is a tool provided by Google to help Google developers implement compile-time dependency injection.
Learn to use Wire through examples.Here's a small welcome program to learn how to use Wire.

The first step in building a welcome program

  • Let's create a applet that simulates an event with a greeting for a welcome guest with a specific message.
  • We created three data types

    • Information for the receptionist
    • Message-conveying Receptionist
    • An event that begins with a welcome
    type Message string
    
    type Greeter struct {
        // ... TBD
    }
    
    type Event struct {
        // ... TBD
    }
  • Now we'll create a simple initializer that always returns a hard-coded message:

    func NewMessage() Message {
        return Message("Hi there!")
    }
  • Our guests need to refer to this message, and we also create an initializer for them.

    func NewGreeter(m Message) Greeter {
        return Greeter{Message: m}
    }
    
    type Greeter struct {
        Message Message // <- adding a Message field
    }
  • In the initializer, we assigned a Message field to Greeter.Now, when we create a Green method on Greeter, we can use this message:

    func (g Greeter) Greet() Message {
        return g.Message
    }
  • Next, Event needs to have a Greeter, so we'll also create an initializer for it.

    func NewEvent(g Greeter) Event {
        return Event{Greeter: g}
    }
    
    type Event struct {
        Greeter Greeter // <- adding a Greeter field
    }
  • Then we add a way to start the event:

    func (e Event) Start() {
        msg := e.Greeter.Greet()
        fmt.Println(msg)
    }

    The Start method is at the heart of our little app: it tells the welcome person to send a greeting and then prints the message to the screen.

  • Now that we have all the components of the application ready, let's see what it takes to initialize all the components without using Wire.Our main function is as follows:

    func main() {
        message := NewMessage()
        greeter := NewGreeter(message)
        event := NewEvent(greeter)
    
        event.Start()
    }

    First we create a message, then we create a welcomer with this message, and finally we create an event with this welcomer.After all initialization is complete, we can start the event.

  • We use the Dependent Injection design principle.In fact, this means that we pass everything that each component needs.This design style helps you write code that is easy to test and facilitates the exchange of one dependency with another.

Generate code using Wire

One disadvantage of dependency injection is that so many initialization steps are required.Let's see how you can use Wire to make the process of initializing components smoother.

  • Let's first simplify our main function:

    func main() {
        e := InitializeEvent()
    
        e.Start()
    }
  • Next, in a separate file called wire.We will define the InitializeEvent.This is where things get interesting:

    // wire.go
    
    func InitializeEvent() Event {
        wire.Build(NewEvent, NewGreeter, NewMessage)
        return Event{}
    }

    Instead of initializing each component in turn and passing it on to the next component, use a connection call.Build the initializer that passes what we want to use.In Wire, initializers are called "providers", which provide a specific type of function.We add a zero value to the event as a return value to meet the compiler's requirements.Note that even if we add values to events, Wire will ignore them.In fact, the injector's purpose is to provide information about which providers are used to construct an event, so we'll exclude it from the final binary code in the build constraint at the top of the file:

    //+build wireinject
    
    # Similar
    //+build wireinject
    // The build tag makes sure the stub is not built in the final build.
    package main
  • InitializeEvent is an injector.Now that we have finished the injector, we can use the wire command line tool.

    # Installation Tools
    go get github.com/google/wire/cmd/wire
  • Then simply run the wire in the same directory as the code above.Wire finds the InitializeEvent injector and generates a function whose body is populated with all necessary initialization steps.Generate file as wire_gen.go

    // wire_gen.go
    
    func InitializeEvent() Event {
        message := NewMessage()
        greeter := NewGreeter(message)
        event := NewEvent(greeter)
        return event
    }

    It looks like what we wrote above! Now, this is a simple example with only three components, so it's not too difficult to write initializers manually.Imagine how useful Wire is for a much more complex component.When using Wire, we will submit two Wires.To wire_gen.Go to source code control.

Change using Wire

  • To demonstrate how Wire handles more complex settings, let's refactor Event's initializer to return an error and see what happens.

    func NewEvent(g Greeter) (Event, error) {
        if g.Grumpy {
            return Event{}, errors.New("could not create event: event greeter is grumpy")
        }
        return Event{Greeter: g}, nil
    }
  • We would say that sometimes a receptionist may be hot-tempered, so we can't create an event.NewGreeter's initialization now looks like this:

    func NewGreeter(m Message) Greeter {
        var grumpy bool
        if time.Now().Unix()%2 == 0 {
            grumpy = true
        }
        return Greeter{Message: m, Grumpy: grumpy}
    }

    We've added a grumpy field to the Greeter structure, and if the initializer takes even seconds to invoke compared to the Unix era, we'll create a fussy Greeter instead of a friendly Greeter.

    func (g Greeter) Greet() Message {
        if g.Grumpy {
            return Message("Go away!")
        }
        return g.Message
    }
  • Now you see how bad a hot-tempered receptionist can be for something.So NewEvent may fail.Our Master must now consider that InitializeEvent may fail:

    func main() {
        e, err := InitializeEvent()
        if err != nil {
            fmt.Printf("failed to create event: %s\n", err)
            os.Exit(2)
        }
        e.Start()
    }
  • We also need to update the InitializeEvent to add the error type to the return value:

    // wire.go
    
    func InitializeEvent() (Event, error) {
        wire.Build(NewEvent, NewGreeter, NewMessage)
        return Event{}, nil
    }
  • Run the wire generation code again, as follows

    // wire_gen.go
    
    func InitializeEvent(phrase string) (Event, error) {
        message := NewMessage(phrase)
        greeter := NewGreeter(message)
        event, err := NewEvent(greeter)
        if err != nil {
            return Event{}, err
        }
        return event, nil
    }

    Wire checks the injector's parameters and sees that we've added a string (for example, phrase) to the parameter list, as well as that in all providers, NewMessage accepts a string, so it passes the phrase to NewMessage.

Complete code

Posted by dylan001 on Sat, 09 Nov 2019 01:10:34 -0800