Preamble
Before we move into the implementation, we should know why we use this kind of thing? what is the purpose of implement dependency injection? So dependency injection helps to decouples components in a system by removing direct dependencies between them. making them more modular and easier to understand, maintainable, and testable. In simple explanation you do not have to do initialization of an instance everytime you need it, just take it from dependency injection, so your coding process will be straightforward. In this moment we will use Uber Dig to achive that.
Implementation
We will entering coding and instruction section. And I expecting you already knows basic commands of your lovely OS and Coding stuffs. Let’s go to the detail.
- Initialize new Golang Project.
mkdir di-tutor
cd di-tutor
go mod init
- Make directory named
di
insidedi-tutor
directory.
mkdir di
- Make file named
init.go
insidedi
directory
cd di
touch init.go
package di
import (
"go.uber.org/dig"
)
// Container master containers for all dependencies injected
// this global variable will be accessed from main function
// and will provide needed instances across functionalities
var Container = dig.New()
// Injected this struct represents dependencies injections
// bank whole injected instance will be accessed from this
// structure.
type Injected struct {
FooBar string
}
// NewInjected initialize dependencies injection entries
// for all dependencies based what this function params
// needed will be injected again using Injected struct.
func NewInjected() *Injected {
return &Injected{
FooBar: "Hello This is FooBar content",
}
}
// init default initialization function from golang
func init() {
var err error
// Injecting needed dependencies across functionalities
// Wrapping up all injected dependencies
err = Container.Provide(NewInjected)
if err != nil {
panic(err)
}
}
Inside this file is where all instances that the application needs, got injected. But for this moment it only inject *Injected
struct with "Hello This is FooBar content"
string inside FooBar
attribute.
- Load
*Injected
struct frommain.go
file.
package main
import (
"fmt"
"github.com/yourgituname/di-tutor/di"
)
func main() {
err := di.Container.Invoke(func(inj *di.Injected) {
fmt.Println(inj.FooBar)
})
if err != nil {
panic(err)
}
}
Inside the main.go
file we try to load *Injected
struct that defined inside init.go
file on di
directory. And let’s try to print string content from FooBar
attribute of *Injected
struct.
- Download needed libraries in project
go mod vendor
go run main.go
The output after you run main.go
will look like this.
Use Case
In last section we talk about injecting a struct that responsible to holds all injected instance, but what is the real use case? I do not want to use Backend Engineering for the use case, its too specific and to advanced, let’s use human anatomy as the use case. First we need to think what is human anatomy has? Let’s breakdown.
- Human has Head and Body
- Head Section has (Eyes, Ears) (Just 2 for simplification)
- Body Section has (Hands, Legs) (Just 2 for simplification)
Abstracting
In this sub-section let’s make an abstraction for Human and their anatomy. Make new package that will look like this. Follow this image.
Head
package head
import "fmt"
type Ears struct{}
func (e *Ears) Hearing() {
fmt.Println("Hearing...")
}
func NewEars() *Ears {
return &Ears{}
}
package head
import "fmt"
type Eyes struct{}
func (e *Eyes) Seeing() {
fmt.Println("Seeing...")
}
func NewEyes() *Eyes {
return &Eyes{}
}
package head
import (
"go.uber.org/dig"
)
type DependenciesHolder struct {
dig.In
Ears *Ears
Eyes *Eyes
}
func RegisterDependencies(container *dig.Container) error {
var err error
err = container.Provide(NewEars)
err = container.Provide(NewEyes)
if err != nil {
return err
}
return nil
}
Body
package body
import "fmt"
type Hands struct{}
func (h *Hands) TakeWithRightHand() {
fmt.Println("Taking with right hand")
}
func (h *Hands) TakeWithLeftHand() {
fmt.Println("Taking with left hand")
}
func (h *Hands) PunchWithRightHand() {
fmt.Println("Punching with right hand")
}
func (h *Hands) PunchWithLeftHand() {
fmt.Println("Punching with left hand")
}
func NewHands() *Hands {
return &Hands{}
}
package body
import "fmt"
type Legs struct{}
func (l *Legs) WalkWithRightLeg() {
fmt.Println("Walking with right leg")
}
func (l *Legs) WalkWithLeftLeg() {
fmt.Println("Walking with left leg")
}
func (l *Legs) KickWithRightLeg() {
fmt.Println("Kicking with right leg")
}
func (l *Legs) KickWithLeftLeg() {
fmt.Println("Kicking with left leg")
}
func NewLegs() *Legs {
return &Legs{}
}
package body
import (
"go.uber.org/dig"
)
type DependenciesHolder struct {
dig.In
Hands *Hands
Legs *Legs
}
func RegisterDependencies(container *dig.Container) error {
var err error
err = container.Provide(NewHands)
err = container.Provide(NewLegs)
if err != nil {
return err
}
return nil
}
Human
package human
import (
"github.com/yourgituname/di-tutor/human/body"
"github.com/yourgituname/di-tutor/human/head"
)
type Human struct {
HeadDependenciesHolder head.DependenciesHolder
BodyDependenciesHolder body.DependenciesHolder
}
func (h *Human) RunningAllHumanFunction() {
// head
h.HeadDependenciesHolder.Ears.Hearing()
h.HeadDependenciesHolder.Eyes.Seeing()
// body
h.BodyDependenciesHolder.Hands.TakeWithRightHand()
h.BodyDependenciesHolder.Hands.TakeWithLeftHand()
h.BodyDependenciesHolder.Hands.PunchWithRightHand()
h.BodyDependenciesHolder.Hands.PunchWithLeftHand()
h.BodyDependenciesHolder.Legs.WalkWithRightLeg()
h.BodyDependenciesHolder.Legs.WalkWithLeftLeg()
h.BodyDependenciesHolder.Legs.KickWithRightLeg()
h.BodyDependenciesHolder.Legs.KickWithLeftLeg()
}
func NewHuman(
headDependenciesHolder head.DependenciesHolder,
bodyDependenciesHolder body.DependenciesHolder,
) *Human {
return &Human{
HeadDependenciesHolder: headDependenciesHolder,
BodyDependenciesHolder: bodyDependenciesHolder,
}
}
Inject Head and Body DependenciesHolder
and *Human
struct on init.go
file
package di
import (
"github.com/yourgituname/di-tutor/human"
"github.com/yourgituname/di-tutor/human/body"
"github.com/yourgituname/di-tutor/human/head"
"go.uber.org/dig"
)
// Container master containers for all dependencies injected
// this global variable will be accessed from main function
// and will provide needed instances across functionalities
var Container = dig.New()
// Injected this struct represents dependencies injections
// bank whole injected instance will be accessed from this
// structure.
type Injected struct {
FooBar string
Head head.DependenciesHolder
Body body.DependenciesHolder
Human *human.Human
}
// NewInjected initialize dependencies injection entries
// for all dependencies based what this function params
// needed will be injected again using Injected struct.
func NewInjected(
hd head.DependenciesHolder,
bd body.DependenciesHolder,
hm *human.Human,
) *Injected {
return &Injected{
FooBar: "Hello This is FooBar content",
Head: hd,
Body: bd,
Human: hm,
}
}
// init default initialization function from golang
func init() {
var err error
// Injecting needed dependencies across functionalities
err = head.RegisterDependencies(Container)
err = body.RegisterDependencies(Container)
err = Container.Provide(human.NewHuman)
// Wrapping up all injected dependencies
err = Container.Provide(NewInjected)
if err != nil {
panic(err)
}
}
Load it from main.go
package main
import (
"github.com/yourgituname/di-tutor/di"
)
func main() {
err := di.Container.Invoke(func(inj *di.Injected) {
inj.Human.RunningAllHumanFunction()
})
if err != nil {
panic(err)
}
}
Now try to run updated main.go
file
go run main.go
The output of updated main.go
will look like this.
If you want to read directly from github, I have push the repository so hope you got better understanding
Dependency Injection Tutorial Github
Conclusion
Hope you understand the whole objective of this tutorial, the conclusion is. With uber dig dependency injection you don’t need to directly initialize an instance, just by put initialize function of an instance uber dig automatically finds injected instance by referencing parameters type, if the type is same will it automatically use injected instance and otherwise it will error and telling you the instance with certain type is not there. And by implement dependency injection will help you structure your dependency accros your code base, minimalize coupling by initialize every dependency at the first run.
My Thanks
Thank you for visiting! I hope you found it useful and enjoyable. Don’t hesitate to reach out if you have any questions or feedback. Happy reading!