Dependency injection (DI) in Go is a polarizing topic among Go developers. Some argue it adds unnecessary complexity, while others believe it’s essential for writing clean and maintainable code:

I would like to provide a more nuanced perspective, helping you make your own decision about DI in Go. We’ll explore what DI actually is, along with its benefits and downsides, and why ultimately, I prefer using the DI approach when building services.

Are these discussions really about dependency injection? Link to heading

Dependency Injection is a specific technique for achieving Inversion of Control (IoC), a broader principle where the control flow of a program is inverted. In IoC, custom-written portions of a program receive the flow of control from a generic framework1. DI achieves this by passing dependencies into a constructor or function, in Go this is usually a interface or reference to a struct.

IoC without DI is very common, so common in fact that you’ve used it almost certainly without thinking about it. It might as well be called the “callback” or “hooks” pattern:

func handle(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello, %v", r.RemoteAddr)
} 

func main() {
	// we "register" our code with the http package
	http.HandleFunc("/hello", handle)

	// here we "invert" control to the http package. It will
	// call our code when a request comes in for "/hello"
	http.ListenAndServe(":8080", nil)
}

The more specific form of IoC, known as dependency injection, involves passing implementation code as “instances into constructors”. This concept might seem very un-Go-like because Go does not use classes (for instancing) or constructors (for creating these instances). So, when discussing this pattern in the context of Go, we should mean it like this:

func main() {
	// create a logger "instance"
	logs := log.New(os.Stderr, "", log.Lshortfile)

	// "inject" the logger dependency when initializing a server (look ma, no constructors!)
	server := http.Server{ErrorLog: logs}

	// server will take control, and it will hand control to the logger when something needs to be logged
	server.ListenAndServe()
}

These examples using the standard library demonstrate how IoC and DI are not only effective but also common. Given their presence in the standard library, why is there so much polarization among Go developers regarding DI?

Dependency injection containers and frameworks Link to heading

  • Google’s Wire
  • Uber’s Fx
  • Saber’s Di

What are you building? Link to heading

I think any discussion on any software development practice need to state what exactly is being build.

What tools are available Link to heading

  • Google’s Wire
  • Uber’s Fx
  • Saber’s Di

What are the pros Link to heading

  • Forces you to think of separation
  • Standardizes components
  • The super power for teleporting value

Insights from the Fx team Link to heading