Dependency Injection: A Straightforward Implementation in Golang

Dependency Injection: A Straightforward Implementation in Golang

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 inside di-tutor directory.

mkdir di

Make file named init.go inside di directory

cd di
touch init.go

Content init.go file.

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 from main.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

Run main.go

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

ears.go

package head

import “fmt”

type Ears struct{}

func (e *Ears) Hearing() {
fmt.Println(“Hearing…”)
}

func NewEars() *Ears {
return &Ears{}
}

eyes.go

package head

import “fmt”

type Eyes struct{}

func (e *Eyes) Seeing() {
fmt.Println(“Seeing…”)
}

func NewEyes() *Eyes {
return &Eyes{}
}

di.go on head directory

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

hands.go

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{}
}

legs.go

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{}
}

di.go on body directory

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

human.go

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

init.go

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

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!

Leave a Reply

Your email address will not be published. Required fields are marked *