Dependency Injection: A Straightforward Implementation in Golang
Tags: tutorial, architecture, go, backend
Published: 2024-05-10 08:35:24

### 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](https://github.com/uber-go/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
```

- 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)
	}
}
```
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
```

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

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

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

- `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,
	}
}
```

---

#### 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)
	}
}
```

#### 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)
	}
}
```
Now try to run updated `main.go` file
```bash
go run main.go
```
The output of updated `main.go` will look like this.
![New updated main.go](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjh988e1dwupdzfn0bwg.png)

If you want to read directly from github, I have push the repository so hope you got better understanding 

[Dependency Injection Tutorial Github](https://github.com/xoxoist/di-tutor)

---

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