Emmanuel John I'm a full-stack software developer, mentor, and writer. I am an open source enthusiast. In my spare time, I enjoy watching sci-fi movies and cheering for Arsenal FC.

Go dependency injection with Wire

3 min read 1048

Go Dependency Injection With Wire

Introduction

Go is a relatively new language, but it’s worth exploring. It was designed to have a simple syntax while maintaining the speed of a programming language like C or C++.

In this article, we will be looking at a software engineering technique found in most programming languages and unsurprisingly in Go as well: dependency injection.

What is dependency injection?

Dependency injection is a software engineering technique where an object or struct receives its dependencies at compile time. Wikipedia defines dependency injection as such:

Dependency injection is a technique in which an object receives other objects that it depends on, called dependencies. Typically, the receiving object is called a client and the passed-in (‘injected’) object is called a service.

To get a better view of this, let’s analyze an example. Take a look at the following code:

package main

import (
   "fmt"
)

type Message string
type Greeter struct {
   Message Message
}
type Event struct {
   Greeter Greeter
}

func GetMessage() Message {
   return Message("Hello world!")
}
func GetGreeter(m Message) Greeter {
   return Greeter{Message: m}
}
func (g Greeter) Greet() Message {
   return g.Message
}
func GetEvent(g Greeter) Event {
   return Event{Greeter: g}
}
func (e Event) Start() {
   msg := e.Greeter.Greet()
   fmt.Println(msg)
}
func main() {
   message := GetMessage()
   greeter := GetGreeter(message)
   event := GetEvent(greeter)

   event.Start()
}

If you take a look at the code above, we have a message, a greeter, and an event. There is also a GetMessage function that returns a message; a GetGreeter function that takes in a message and returns a greeter; and a GetEvent function that accepts a greeter and returns an event. The event also has a method called Start that prints out the message.

If you take a look at our main method, we first create a message, then we pass in the message as a dependency to the greeter and finally pass that to the event. Run the code by running the command go run . in the terminal.

Go Dependency Injection With Wire

As you can see, it prints “Hello, world!” to the console. This is a very shallow dependency graph, but you can already see the complexity that comes with this when implementing this in a large codebase. That’s where dependency injection tools like Wire come in.

What is Wire?

Wire is a code dependency tool that operates without runtime state or reflection. Code written to be used with Wire is useful even for handwritten initialization.

Wire can generate source code at compile time as well as implement dependency injection. According to the official documentation, “In Wire, dependencies between components are represented as function parameters, encouraging explicit initialization instead of global variables.”

How to install Wire

To use Wire, first, you need to initialize Go modules in your current working directory. Run the command go mod init go-wire to do this.

Go Dependency Injection With Wire

Now, run the command go get github.com/google/wire/cmd/wire to install.

Go Dependency Injection With Wire

Now, let’s refactor our code to use Wire as a dependency injection tool. Create a file called wire.py and add the following code:

package main

import "github.com/google/wire"

func InitializeEvent() Event {
   wire.Build(GetMessage, GetGreeter, GetEvent)
   return Event{}
}

First of all, we import Wire, then we create a function called InitializeEvent. This function returns an event that we will use in our main method. In the InitializeEvent function, we make a call to Wire. Then we build and pass in all our dependencies. Note we can pass in these dependencies in any order.

Then we return an empty event. Don’t worry, Wire will take over here!

Now, change your main method to this:

func main() {
   event := InitializeEvent()
   event.Start()
}

Notice how we have successfully cut down the code in our main method to just two lines.



Run the command go run github.com/google/wire/cmd/wire to generate our dependencies with Wire.

Go Dependency Injection With Wire

Now you will see that Wire has generated a file called wire_gen.

Go Dependency Injection With Wire

If you should run the code again, you will get an error.

Go Dependency Injection With Wire

This is because our InitializeEvent function has now been redeclared in the wire_gen file. Add
//+build wireinject to the beginning of your wire.go file to tell Go to ignore it when building. Make sure to add a new line after that or this will not work.

Go Dependency Injection With Wire

If you run go run . again, you should still see the same “Hello, world!” output.

Go Dependency Injection With Wire

Working with arguments

What if you wanted to dynamically pass in a message as an argument? Let’s take a look at how we can do this. Modify the GetMessage function to this:

func GetMessage(text string) Message {
   return Message(text)
}

Now we have to pass in a text to display. Let’s try running this and see the output.

Go Dependency Injection With Wire

As you can see, Wire recognizes that we have to pass in an argument to the GetMessage function. Let’s resolve this error. Modify your InitializeEvent function in your wire.go file:

func InitializeEvent(string) Event {
   wire.Build(GetMessage, GetGreeter, GetEvent)
   return Event{}
}

Now we are telling Wire that we expect a string argument.

Run go run github.com/google/wire/cmd/wire again. If you take a look at our wire_gen.go file, you will see that Wire has refactored the code to accept this value.


More great articles from LogRocket:


Go Dependency Injection With Wire

Pass in the string in your main method:

func main() {
   event := InitializeEvent("Hello People!")
   event.Start()
}

Run go run . again and you should see the text printed out.

Go Dependency Injection With Wire

Error handling in Wire

What if we forget to pass in any initializer? Let’s see how Wire handles this.

In your wire.go file, omit the GetEvent argument and run go run github.com/google/wire/cmd/wire again:

func InitializeEvent(string) Event {
   wire.Build(GetMessage, GetEvent)
   return Event{}
}

Go Dependency Injection With Wire

As you can see, Wire detects that we have failed to pass in an argument and prints out a helpful output telling us which argument we omitted. The same thing happens when we pass in more than the required number of arguments.

Conclusion

In this article, we have covered the basic features of Wire. We have seen how we can use it for dynamic dependency injection in our little example above, but the true power of Wire comes out when we have a large codebase. If you want to learn more advanced features, you can check out the documentation here.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Emmanuel John I'm a full-stack software developer, mentor, and writer. I am an open source enthusiast. In my spare time, I enjoy watching sci-fi movies and cheering for Arsenal FC.

One Reply to “Go dependency injection with Wire”

Leave a Reply