1
\$\begingroup\$

I am coming from PHP OOP background so just want to find out if what I do with Go is common/acceptable practise or not (I am newbie in Go). It is to do with Dependency Injection.

In OOP it is ideal to Dependency Inject object X into object Y rather than directly instantiating object X in object Y. I won't list the reasons because I believe most of you know general OOP practises etc. Given that Go is not an OOP language and I've seen both versions being used, which version below wins over another an why?

api/internal/config/config.go

package config

type Config struct
{
   Locale string
}

func NewConfig() Config {
   return Config{"en"}
}

VER 1

api/internal/app/app.go

package app

func Start(c config.Config) {
   fmt.Println(c.Locale)
}

cmd/main.go

package main

import (
    "api/internal/app"
    "api/internal/config"
)

func main() {
    c := config.NewConfig()

    app.Start(c)
}

VER 2

api/internal/app/app.go

package app

import (
   "fmt"
   "api/internal/config"
)

func Start() {
   c := config.NewConfig()

   fmt.Println(c.Locale)
}

cmd/main.go

package main

import "api/internal/app"

func main() {    
    app.Start()
}
\$\endgroup\$
1
\$\begingroup\$

Version 1 wins hands down.

Because, as you say yourself, go isn't an OO language, you kind of have to treat your entry point (e.g. main func) as your injection point. The main function loads up the global config (usually from flags and/or environment variables), and then passes them on the the various components you'll use (or packages if you like).

Go is a great language to write tests in quickly. If I see code like the second snippet:

func Start() {
    c := config.New()
    // do stuff with c
}

I will rewrite it. The config value is created in that function, so it's safe to assume that this function will need to check the config values. How am I going to reliably unit-test the package, if I can't pass in all possible combinations of config?

Sure, I could write a ton of code setting/unsetting environment variables, and call this Start function over and over again, but am I really unit-testing the code then? Surely, if the config package is broken, then all other tests become unreliable to say the least. What about a change to the config package? How awful it'd be to rewrite all the tests just to make sure the changes to the config are reflected there, too? It's going to be a PITA, and an enormous waste of time and resources.

PS: code like config.NewConfig() will also get rewritten by a lot of gophers. This is referred to as stuttering code (read this).

I now I'm getting config, because I'm using the config package. The function name New is enough information, surely. config.NewConfig reads like "from config, get me new config" as opposed to "hey, config, give me a new value".
This might be personal preference, but I generally find it better to use the function Get for config, rather than New. Config is, IMO, an immutable set of data, not something that does something, not something that needs constructing, it needs to be fetched/loaded. For that reason, I'd write config.Get() and that func would looks something like this:

package config

// Conf - global config struct
type Conf struct {
    App // config per package
}

// App - config specific to the App package
type App struct {
    Locale string
}

func Get() (*Conf, error) {
    // get values, return...
}
\$\endgroup\$
  • \$\begingroup\$ Thanks for the answer. Assume that the config is needed in an another package but the package is not bootstrapped when we stated the app in main(). I guess it is ok to create/fetch config with config.Get() in the package? \$\endgroup\$ – BentCoder Aug 28 at 16:55
  • \$\begingroup\$ @BentCoder: No it wouldn't be. The whole point of DI is that dependencies are created and injected into whatever component depends on them. If you create the deps in app.New(), then you're no longer injecting. config.Get() should be called once, before you create any instance that requires values from config \$\endgroup\$ – Elias Van Ootegem Aug 29 at 10:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.