Dependency Injection: A Straightforward Implementation in Golang

Nightsilver Academy - May 10 - - Dev Community

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. ```bash

mkdir di-tutor
cd di-tutor
go mod init


- Make directory named `di` inside `di-tutor` directory.
```bash


mkdir di


Enter fullscreen mode Exit fullscreen mode
  • Make file named init.go inside di directory ```bash

cd di
touch init.go


- Content `init.go` file.
```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)
    }
}


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. ```go

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
```bash


go mod vendor


Enter fullscreen mode Exit fullscreen mode
  • Run main.go ```bash

go run main.go

The output after you run `main.go` will look like this.
![Running Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xkrnvcyxh2m5a0iemk6k.png)

---

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nvbuzmenf6xb40eapbgm.png)

---

#### Head

- `ears.go`
```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 ```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
```go


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 ```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`
```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 ```go

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`
```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 ```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`
```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!

. . . . . . .