DEV Community

Cover image for Golang with google wire
kittichanr
kittichanr

Posted on

Golang with google wire

Introduction

Google Wire is a package that helps manage Dependency Injection (DI) in Go, developed by Google. Its main purpose is to generate code that wires together functions with injected values so they can work together.

Dependency Injection (DI) is the practice of providing dependencies from the outside instead of creating them inside the function. The benefits are:

  1. Reduced code coupling (loose coupling)
  2. Easier testing — since the code is decoupled, you can mock dependencies and inject them for testing easily

Example:

package main

import "fmt"

type Animal interface {
    MakeSound()
}

// Dog
type Dog struct{}

func (d *Dog) MakeSound() {
    fmt.Println("Woof!")
}

// Cat
type Cat struct{}

func (c *Cat) MakeSound() {
    fmt.Println("Meow!")
}

type Zoo struct {
    animals []Animal
}

func (z *Zoo) MakeSounds() {
    for _, animal := range z.animals {
        animal.MakeSound()
    }
}

// NewZoo is a constructor function for Zoo with DI
func NewZoo(animals ...Animal) *Zoo {
    return &Zoo{animals: animals}
}

func main() {
    // Injecting dependencies
    dog := &Dog{}
    cat := &Cat{}
    zoo := NewZoo(dog, cat) // dog and cat are injected into NewZoo

    zoo.MakeSounds()
}
Enter fullscreen mode Exit fullscreen mode

Core Concepts

1. Providers
These are functions that return newly created objects, for example:

type Message string

func NewMessage() Message {
    return Message("Hi there!")
}
Enter fullscreen mode Exit fullscreen mode

They can be grouped into what’s called a provider set, like this:

package foobarbaz

import (
    // ...
    "github.com/google/wire"
    "errors"
)

// Foo
type Foo struct {
    X int
}

func ProvideFoo() Foo {
    return Foo{X: 42}
}

// Bar
type Bar struct {
    X int
}

func ProvideBar(foo Foo) Bar {
    return Bar{X: -foo.X}
}

// Baz
type Baz struct {
    X int
}

func ProvideBaz(bar Bar) (Baz, error) {
    if bar.X == 0 {
        return Baz{}, errors.New("cannot provide baz when bar is zero")
    }
    return Baz{X: bar.X}, nil
}

// Group provider 
var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)
Enter fullscreen mode Exit fullscreen mode

2. Injectors

These are functions that combine all provider functions together after code generation.

Example:

// +build wireinject
// The build tag ensures this stub is excluded from the final build.

package main

import (
    "context"

    "github.com/google/wire"
    "example.com/foobarbaz"
)

// injector function
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
    wire.Build(foobarbaz.SuperSet)
    return foobarbaz.Baz{}, nil
}
Enter fullscreen mode Exit fullscreen mode

Now that you understand providers and injectors, let’s look at how to set up and use Google Wire in practice.

Set Up and Basic Usage

1.Install the Google Wire package

go install github.com/google/wire/cmd/wire@latest
Enter fullscreen mode Exit fullscreen mode

2.Create your desired provider functions, for example:

// greeter/greeter.go
package greeter

type Message string

// provider function
func NewMessage() Message {
    return Message("Hi there!")
}

type Greeter struct {
    Message Message
}

func (g Greeter) Greet() Message {
    return g.Message
}

// provider function
func NewGreeter(m Message) Greeter {
    return Greeter{Message: m}
}

type Event struct {
     Greeter Greeter
}

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

func NewEvent(g Greeter) Event {
    return Event{Greeter: g}
}

var MainBindingSet = wire.NewSet(NewMessage, NewGreeter, NewEvent)
Enter fullscreen mode Exit fullscreen mode

3.Create an injector function in di/wire.go:

// di/wire.go
//go:build wireinject
// +build wireinject

package di

import (
 "context"

 "github.com/google/wire"
 "github.com/kittichanr/go-wire/greeter"
)

func InitializeApplication(ctx context.Context) (greeter.Event, func(), error) {
 wire.Build(greeter.MainBindingSet)
 return greeter.Event{}, func() {}, nil
}
Enter fullscreen mode Exit fullscreen mode

You must include the go:build and +build comments so that the Wire CLI knows where to generate code.

// go:build wireinject
// +build wireinject
Enter fullscreen mode Exit fullscreen mode

4.In main.go, call the injector function to initialize everything when running your application:

// main.go
package main

import (
 "context"
 "log"

 "github.com/kittichanr/go-wire/di"
)

func main() {
 ctx := context.Background()
 _, cleanUpFn, err := di.InitializeApplication(ctx)
 defer cleanUpFn()

 if err != nil {
  log.Fatal("initial app failed")
  panic(err)
 }
}
Enter fullscreen mode Exit fullscreen mode

5.Run wire ./di in your terminal to generate the code that connects all provider functions. It will produce a file called di/wire_gen.go, which might look like this:

// Code generated by Wire. DO NOT EDIT.

//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package di

import (
 "context"
 "github.com/kittichanr/go-wire/greeter"
)

// Injectors from wire.go:

func InitializeApplication(ctx context.Context) (greeter.Event, func(), error) {
 message := greeter.NewMessage()
 greeterGreeter := greeter.NewGreeter(message)
 event := greeter.NewEvent(greeterGreeter)
 return event, func() {
 }, nil
}
Enter fullscreen mode Exit fullscreen mode

Note: Do not edit the generated file. Instead, update your provider functions and re-run wire to regenerate the file.

Notice how wire_gen.go wires all provider functions for you — no need to inject them manually. Yay!

Conclusion

Go Wire is a tool that helps generate code for dependency injection, allowing functions with dependencies to work seamlessly together. You don’t need to manually wire every dependency, and you can be confident they will be initialized in the correct order. The example above demonstrates a basic setup — check out the Go Wire GitHub for more advanced usage.

Source code example: https://github.com/kittichanr/go-wire-basic

References

Top comments (0)