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.
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.
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.
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.”
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.
Now, run the command go get github.com/google/wire/cmd/wire
to install.
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.
Now you will see that Wire has generated a file called wire_gen
.
If you should run the code again, you will get an error.
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.
If you run go run .
again, you should still see the same “Hello, world!” output.
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.
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.
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.
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{} }
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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
One Reply to "Go dependency injection with Wire"
Why the filename is wire.py not wire.go