bodokaiser
bodokaiser

Reputation: 15752

Should I avoid package singletons in golang?

at the moment I have a package store with following content:

package store

var (
    db *Database
)

func Open(url string) error {
    // open db connection
}

func FindAll(model interface{}) error {
    // return all entries
}

func Close() {
    // close db connection
}

This allows me to use store.FindAll from other packages after I have done store.Open in main.go.

However as I saw so far most packages prefer to provide a struct you need to initialize yourself. There are only few cases where this global approach is used.

What are downsides of this approach and should I avoid it?

Upvotes: 5

Views: 2552

Answers (2)

Soheil Hassas Yeganeh
Soheil Hassas Yeganeh

Reputation: 1379

The standard http package has a ServerMux for generic usecases and but also has one default instance of ServerMux called DefaultServerMux (http://golang.org/pkg/net/http/#pkg-variables) for convenience. So that when you call http.HandleFunc it creates the handler on the default mux. You can find the same approach used in log and many other packages. This is essentially your "singleton" approach.

However, I don't think it's a good idea to follow that pattern in your use case, since the users need to call Open regardless of the default database. And, because of that, using a default instance would not really help and instead would actually make it less convenient:

d := store.Open(...)
defer d.Close()
d.FindAll(...)

is much easier to both write and read than:

store.Open(...)
defer store.Close()
store.FindAll(...)

And, also there are semantic problems: what should happen if someone calls Open twice:

store.Open(...)
defer store.Close()
...
store.Open(...)
store.FindAll(...) // Which db is this referring to?

Upvotes: 4

aml
aml

Reputation: 51

  1. You can't instantiate connections to 2 storages at once.
  2. You can't easily mock out storage in unit tests of dependent code using convenient tools like gomock.

Upvotes: 5

Related Questions