8

Google’s Wire: Automated Dependency Injection in Go

 1 year ago
source link: https://nelsonparente.medium.com/googles-wire-automated-dependency-injection-in-go-4e98864c3dd5
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Google’s Wire: Automated Dependency Injection in Go

1*PucM6X9pnSznV93vCjobaw.jpeg

Photo by Mandy Choi on Unsplash

Hello folks!

Today, we will discuss something that many consider a fundamental principle of software engineering and programming in general: Dependency Injection. It’s always helpful to revisit first principles because they are well-proven and battle-tested, and have become industry standards.

This article will focus on Dependency Injection, specifically how to use DI in Go. By the end of the article, we will have a good understanding of why we should use DI, how to use it, and how Wire can save us a lot of time when configuring our dependencies.

What is Dependency Injection?

When we build software, we often break our code into smaller, isolated components that interact with each other to provide specific functionality. These components have relationships between each other, which we refer to as dependencies.

While managing dependencies at a small scale is relatively straightforward, as a software system grows, the dependency graph becomes increasingly complex. This can result in a big initialization block and make it difficult to break up code cleanly, especially when some dependencies are used multiple times. It can become tedious and time-consuming to make changes to the code, test functionalities with multiple dependencies, and follow the code along.

This is where Dependency Injection comes into play. Dependency Injection, or DI, is a widely-used software design pattern that promotes loose coupling and flexibility. Instead of forcing objects or methods to build the dependencies they need, these dependencies are injected via constructors or parameters. This allows for better maintainability and testability, and can simplify the process of managing dependencies as a software system grows. In Go, we can use a tool like Wire to make the process of configuring dependencies even easier.

1*OpdqIiJPwUwjFlUBnTSLNA.png

The example above demonstrates the most common case of Dependency Injection — Constructor Injection. Our Service struct has two dependencies - the Logger and the Repository. To build a new instance of the Service, we pass both dependencies in its constructor.

An important detail here is that we are injecting interfaces. By not injecting a specific implementation of the dependencies, we increase the modularity and flexibility of our code. An interface is an abstraction, which means we could easily switch the implementation by a mock implementation or a totally new implementation without breaking our contract.

Using interfaces is critical when it comes to testing because it allows us to focus on the core behavior we want to test and manipulate the dependencies as we intend to.

One straightforward way to implement Dependency Injection would be to manually inject our dependencies. This approach is clean, simple, and doesn’t rely on too much magic.

1*4WuYueRoXd6kchDPmmiBLA.png

It’s child’s play! Now, let’s imagine that instead of one service with two dependencies, we have 100 services, each with an arbitrary number of dependencies. Manually managing all those dependencies would not be a smooth ride, for sure.

This is where Dependency Injection frameworks come into play. These frameworks provide a way to define and configure our dependencies. Typically, DI frameworks can work with reflection and provide runtime dependency injection, such as Uber’s Dig and Facebook’s Inject, or use code generation to provide compile-time dependency injection, like Google’s Wire.

Life is made up of trade-offs, and choosing your dependency injection framework is no different. We need to consider these trade-offs, but in the end, our goal is to simplify the process of managing dependencies, improve code organization, and increase code maintainability.

In this article, we will look at Google’s Wire, which is a compile-time dependency injection framework. The Google Wire team presents these as some arguments favoring compile-time DI over runtime DI:

  • Runtime dependency injection can be hard to follow and debug when the dependency graph gets complex;
  • Forgetting a dependency becomes a compile-time error, not run-time error;
  • Easier to avoid dependency bloat;
  • Dependency graph is knowable statically, which provides opportunities for tooling and visualization.

Google’s Wire

Google’s Wire is a compile-time dependency injection framework for Go that generates code to provide compile-time dependency injection. This means that the code that is generated by Wire is static and can be inspected before runtime, providing greater visibility and reducing the chance of errors.

Wire uses providers to construct and provide instances of dependencies, which are then injected into other components as needed. Providers are simply functions that return an instance of the dependency.

Wire also uses injectors to wire together the various components in the dependency graph. An injector is a function that takes as input the dependencies required by a component and returns the fully constructed instance of that component. By defining providers for each dependency, and injectors for each component, wire is able to generate code that automatically wires up all of the dependencies in your application, making it easy to manage even complex dependency graphs.

Let’s apply this to the simple code we have been using. First we need to create a new wire.go file and define our Provider.

1*N5pxfNLtvOuAYGz3H0k6cQ.png

Now we need to run wire code generation that will create a new wire_gen.go file. Pay attention to the similarities between our previous initialization code and the generated code.

1*w8_JGTfMjY7VFpBsJrp-Qg.png

To finalize we have to update our main file and use our new provider.

1*JsrBJLugA6LUAZ_kupGAeQ.png

This is a simple usage of Wire, but it’s not hard to see how beneficial it would be on a larger code base! In addition to the functionalities covered in this post, Wire provides several other powerful features that can further simplify dependency injection in your Go projects. Some of these additional features include support for named instances, cleanup functions, and interface binding.

In a future blog post, we will explore these features in more detail.

To conclude, dependency injection is a powerful pattern that can greatly simplify the management of dependencies in our code and improve its maintainability. By injecting dependencies through constructors or parameters, we increase the flexibility and modularity of our code, making it easier to test and maintain.

While there are various ways to do dependency injection, using a framework like Google’s Wire can provide significant benefits, especially when dealing with a large number of dependencies. By generating code at compile-time, Wire can help us catch errors early and improve our development workflow.

Overall, understanding and using dependency injection in our code can make a big difference in the quality and maintainability of our software. Whether we use a framework or not, adopting this pattern can lead to cleaner, more modular code that is easier to test and maintain over time.

Nelson out!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK