Some of the expectations from a dependency injection container framework is as below (Borrowed from this post on medium by Minjie Zha):
No code pollution:
There are basically just two philosophies when it comes to Dependency injection in Golang.
It basically makes use of code generation for getting the job done. A user of this approach would need to ensure that all the required factory methods (Constructor methods of the corresponding structs) are handcrafted and made available. These are the building blocks for Dependency Injection.
The user then starts writing the injector function, wherein he/she expresses how to use various different providers (the factory methods that we created) to construct a new object in question. The Dependency Injection framework (such as wire
) merely uses these functions to generate code (the generated code would have a function that matches the injector function signature) which contains all the verbose calls to all the different providers that are involved to create the object.
Since this is generated code it is idiomatic to golang.
Wire from Google relies heavily on this approach.
In this approach there's a heavy relying on reflection to get this done. Its done at runtime. So any missing dependencies that may not have been expressed, will fail only at runtime. This can be equated to what sophisticated Dependency Injection frameworks in Java such as Spring
(or) Guice
do.
work on the reflection based approach.
Now lets look at some code samples and cross compare them with different implementations.
The sample has been borrowed from Drew Olson's repository
To begin with, following is how the problem statement can be displayed as:
The below entities are standalone and don't need anything else for them to be instantiated:
Config
Person
We have a type named PersonRepository
that depends on sql.DB
object and exposes a method named FindAll()
which returns a slice of Person
objects.
We have a custom end-point called /people
which is bound to a handler.
This handler makes use of a service named PersonService
to retrieve the slice of Person
objects.
The complete entities involved in the problem statement is found here.
Here's how the code would look like:
func Main() {
fmt.Println("Handcrafting Dependency Injection manually")
config := di.NewConfig()
fmt.Println("Database to be read from ", config.DatabasePath)
db, err := di.ConnectDatabase(config)
if err != nil {
panic(err)
}
personRepository := di.NewPersonRepository(db)
personService := di.NewPersonService(config, personRepository)
server := di.NewServer(config, personService)
server.Run()
}
Here's how the code would look like:
func Main() {
fmt.Println("Making use of [dig] as Dependency Injection")
container := BuildContainer()
err := container.Invoke(func(server *di.Server) {
server.Run()
})
if err != nil {
panic(err)
}
}
func BuildContainer() *dig.Container {
container := dig.New()
_ = container.Provide(di.NewConfig)
_ = container.Provide(di.ConnectDatabase)
_ = container.Provide(di.NewPersonRepository)
_ = container.Provide(di.NewPersonService)
_ = container.Provide(di.NewServer)
return container
}
Here's how the code would look like:
func Main() {
fmt.Println("Making use of [wire] as Dependency Injection")
server, err := NewServerInstance()
if err != nil {
panic(err)
}
server.Run()
}
Contents of wire.go
(This would need to be written by us)
//+build wireinject
package wire
import (
"github.com/google/wire"
"github.com/krmahadevan/di"
)
func NewServerInstance() (*di.Server, error) {
wire.Build(di.NewConfig,
di.ConnectDatabase, //Since this can return an error, we need to ensure we return back that same error
di.NewPersonRepository,
di.NewPersonService,
di.NewServer)
return &di.Server{}, nil
}
Now when you run wire
from the command prompt the generated code would look like this:
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package wire
import (
"github.com/krmahadevan/di"
)
// Injectors from wire.go:
func NewServerInstance() (*di.Server, error) {
config := di.NewConfig()
db, err := di.ConnectDatabase(config)
if err != nil {
return nil, err
}
personRepository := di.NewPersonRepository(db)
personService := di.NewPersonService(config, personRepository)
server := di.NewServer(config, personService)
return server, nil
}
For any queries, log an issue here.
Tags: Go