Having a singleton pattern in Go Wire injection

Viewed 721

I have a piece of code which is used to load configuration from file and parse it into a struct, I use this configuration quite often and hence I pass it around in the method parameters. Now I as my method parameters are increasing, I am looking at dependency injection and have settle with wire.

Now I have created a provider to load the configuration and an injector to provide the config struct. However each time I call the injection my file is read again, I want that the file is read once and the injection provided as many times as required without any additional loading.

Here is my provider:

// ProvideConfig config provider ...
func ProvideConfig() *config.FileConfig {
        var cfp string

        flag.StringVar(&cfp, "config", "config.json", "absolute path")
        flag.Parse()

    return config.Loadconfig(cfp)
}

Injector:

// GetConfig injector ...
func GetConfig() ConfigResource {
    wire.Build(ProvideConfig, NewConfigResource)
    return ConfigResource{}
}

Now when I call:

injection.GetConfig()

I see that ProvideConfig is called always. I can have a check in the provide config method the determine if the config is already loaded, I am not sure if there is a better way, something like a single instance loader which is built into the wire. I tried looking into the docs but could not find anything relevant.

1 Answers

As far as I'm aware, there's no built in way in wire to specify that a provider is a singleton / should only be called once.

This is accomplished in the usual way in Go, by using sync.Once. Your provider function can be a closure that does the expensive operation only once using sync.Once.Do. This is idiomatic in Go, and doesn't require any special provision from every library that wants to provide "single" loading.

Here's an example without wire:

type Value struct {
    id  int
    msg string
}

type ValueProvider func() *Value

// consumer takes a function that provides a new *Value and consumes
// the *Value provided by it.
func consumer(vp ValueProvider) {
    v := vp()
    fmt.Printf("Consuming %+v\n", *v)
}

// MakeSingleLoader returns a ValueProvider that creates a value once using an
// expensive operation, and then keeps returning the same value.
func MakeSingleLoader() ValueProvider {
    var v *Value
    var once sync.Once

    return func() *Value {
        once.Do(func() {
            v = ExpensiveOperation()
        })
        return v
    }
}

// ExpensiveOperation emulates an expensive operation that can take a while
// to run.
func ExpensiveOperation() *Value {
    return &Value{id: 1, msg: "hello"}
}

func main() {
    sl := MakeSingleLoader()
    consumer(sl)
    consumer(sl)
    consumer(sl)
}

If you're OK with the "singleton" value being a global, this code can be simplified a bit. Otherwise, it only calls ExpensiveOperation once, and keeps the value cached in a local inaccessible outside MakeSingleLoader.

Related