DEV Community

Cover image for Dependency Injection: A Straightforward Implementation in Golang
Nightsilver Academy
Nightsilver Academy

Posted on • Edited on

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
Enter fullscreen mode Exit fullscreen mode
  • Make directory named di inside di-tutor directory.
mkdir di
Enter fullscreen mode Exit fullscreen mode
  • Make file named init.go inside di directory
cd di
touch init.go
Enter fullscreen mode Exit fullscreen mode
  • 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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • Run main.go
go run main.go
Enter fullscreen mode Exit fullscreen mode

The output after you run main.go will look like this.
Running Output


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.

Human Package Structure Package


Head

  • ears.go
package head

import "fmt"

type Ears struct{}

func (e *Ears) Hearing() {
    fmt.Println("Hearing...")
}

func NewEars() *Ears {
    return &Ears{}
}
Enter fullscreen mode Exit fullscreen mode
  • eyes.go
package head

import "fmt"

type Eyes struct{}

func (e *Eyes) Seeing() {
    fmt.Println("Seeing...")
}

func NewEyes() *Eyes {
    return &Eyes{}
}
Enter fullscreen mode Exit fullscreen mode
  • 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
}
Enter fullscreen mode Exit fullscreen mode

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{}
}
Enter fullscreen mode Exit fullscreen mode
  • 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{}
}
Enter fullscreen mode Exit fullscreen mode
  • 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
}
Enter fullscreen mode Exit fullscreen mode

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,
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

Now try to run updated main.go file

go run main.go
Enter fullscreen mode Exit fullscreen mode

The output of updated main.go will look like this.
New updated main.go

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!

Top comments (7)

Collapse
 
michaeltharrington profile image
Michael Tharrington

Nice, this looks helpful! Appreciate ya sharing.

Collapse
 
nghtslvr profile image
Nightsilver Academy

Thanks, so happy you found it useful, have a great day fella.

Collapse
 
eazlai profile image
Eazl.ai

Cool

Collapse
 
nghtslvr profile image
Nightsilver Academy • Edited

Thank you very much, feel free to ask me more.

Collapse
 
garima_tiwari_f1110ec141d profile image
Garima Tiwari

Cool

Collapse
 
nghtslvr profile image
Nightsilver Academy

Thank you 🙏🏻

Collapse
 
asac2142 profile image
Andrés Albán Carvallo

Why use an external package for handling DI in Go? Can't just Go handle that by itself?
Other programming languages can achieve DI without the need of a package... why Go is different in this?