DEV Community

Cover image for Golang Master Class: Dependency Injection(DI).
Sk
Sk

Posted on

Golang Master Class: Dependency Injection(DI).

I remember years ago when I first started learning Angular…

Dependency Injection made zero sense to me. None.

The worst part? Angular is built around DI, so I just pretended I got it. Even in interviews, I’d repeat fancy words I didn’t actually understand. Fake it till you make it, right?

Well, in this piece, I want to finally demystify it. And I promise, it’s not that deep once you break it down.
But to get it, we’ve gotta go way back…

Full Code(including prev article):

git clone https://github.com/sklyt/goway.git
Enter fullscreen mode Exit fullscreen mode

From 00

If you’re anything like me, your intro to code probably looked like this:

var sum = 4 + 4 // yes, `var` was popular in Js once upon a time
Enter fullscreen mode Exit fullscreen mode

Then the tutorial says: “Hey, you might want to reuse this logic. Let’s make a mini math library.”

We got functions.

function add(a, b){
   return a + b
}
Enter fullscreen mode Exit fullscreen mode

So functions are just a realization: Hey, I can bundle single lines of code and reuse them.
Right?

Dependency Injection is kinda like that, but instead of bundling single lines of code, we’re bundling functions and objects into something reusable.

We call that bundle a service.

It’s basically like a little npm library you make inside your own project. The only reason it’s not a full library is because you haven’t published it yet.

Let’s bring it back again.


DI in Simple Terms

Just like functions group single lines of logic...

DI groups related functions and objects into a single reusable unit you can plug into other parts of your app.

Without DI, you’d end up doing something like this:

import { function1, function2, functionN, Object1, Class1 } from "module"
Enter fullscreen mode Exit fullscreen mode

With DI, it’s more like:

import { MyModuleService } from "module"
// all the useful functions and objects are composed inside here
Enter fullscreen mode Exit fullscreen mode

But you don’t just jam anything together. DI is for grouping related stuff, functions or objects that belong together.

In our storage project, we already have two things that qualify:
OnlineStore and LocalStore.
Remember why? First article: interface-driven design, they both fulfill the same interface:

type Storage interface {
    Save(key string, data []byte) error
    Load(key string) ([]byte, error)
}
Enter fullscreen mode Exit fullscreen mode

So instead of doing this manually:

online := storage.NewOnlineStore("https://jsonplaceholder.typicode.com", gohttplib.WithTimeout(2*time.Second), gohttplib.WithBearerToken("secret-token-123"))
local := storage.NewLocalStore("./data")

// Then calling each store individually below 👇
Enter fullscreen mode Exit fullscreen mode

We can collapse everything into a StorageService:

online := storage.NewOnlineStore("https://jsonplaceholder.typicode.com", gohttplib.WithBearerToken("aiohsfnals"), gohttplib.WithTimeOut(2 * time.Second))
local := storage.NewLocalStore("./data")
log_ := logger.NewLogger("APP")

store := storage.NewStorageService(local, online, log_) // Service

data, err := store.Get("foo")
if err != nil {
    log_.Log(err.Error())
}
log_.Log(string(data))
Enter fullscreen mode Exit fullscreen mode

Yep, all that cache handling and fallback logic from useStorageGet?
Gone. Now it lives inside the service.


NewStorageService

Create a new file: internal/storage/service.go

Full implementation:

package storage

import (
    "errors"
    "fmt"
    "goway/internal/logger"
)

type StorageService struct {
    local  Storage
    online Storage
    log    *logger.Logger
}

func NewStorageService(local, online Storage, log *logger.Logger) *StorageService {
    return &StorageService{local, online, log}
}

func (svc *StorageService) Get(key string) ([]byte, error) {
    data, err := svc.local.Load(key)

    if err != nil && !errors.Is(err, ErrNotFound) {
        return nil, fmt.Errorf("cache error: %w", err)
    }

    if err != nil && errors.Is(err, ErrNotFound) {
        svc.log.Log(fmt.Sprintf("cache miss: %s", key))
    } else {
        return data, nil
    }

    data, err = svc.online.Load(key)
    if err != nil {
        return nil, fmt.Errorf("online fetch failed: %w", err)
    }

    if err := svc.local.Save(key, data); err != nil {
        svc.log.Log(fmt.Sprintf("warning: failed to populate cache: %v", err))
    }

    if len(data) == 0 {
        return nil, ErrNotFound
    }

    return data, nil
}
Enter fullscreen mode Exit fullscreen mode

In storage.go, add this:

import "errors"

var ErrNotFound = errors.New("service: key not found anywhere")
Enter fullscreen mode Exit fullscreen mode

Now our main.go looks like this:

func main() {
    online := storage.NewOnlineStore("https://jsonplaceholder.typicode.com",
        gohttplib.WithBearerToken("aiohsfnals"),
        gohttplib.WithTimeOut(2 * time.Second),
    )

    local := storage.NewLocalStore("./data")
    log_ := logger.NewLogger("APP")

    store := storage.NewStorageService(local, online, log_)

    data, err := store.Get("foo")
    if err != nil {
        log_.Log(err.Error())
    }

    log_.Log(string(data))
}
Enter fullscreen mode Exit fullscreen mode

My file went from 80+ lines to less than 30.


Now I guess I have to give you the official Dependency Injection mantra and why people say it matters:

Why DI Matters

DI isn't just a pattern, it’s a mindset. It encourages you to:

  • Decouple dependencies from the code that uses them.
  • Focus on high-level logic instead of implementation details.
  • Build systems that are flexible and testable.
  • Swap out implementations without touching the core code.
  • Mock dependencies easily for testing.
  • Keep your main logic clean.

⚠️ Disclaimer: That’s the textbook explanation. That’s how it was sold to me back in the day.

Do I really know what “focus on high-level logic” means? Honestly... still not sure.
But the modularity part? 100% true.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more