A basic pattern. Extremely useful when dealing with database connections.

What is a singleton?

A singleton is a software design pattern described in the book Design Patterns by the Gang of Four.

It's goal is restricting the initialisation and usage of an object to a single instance.

When is it useful?

The two most common use cases when a singleton's benefits outweighs its side consequences are:

  1. When you need to keep a global state and want to avoid polluting the global namespace;
  2. When you want to restrict the usage of another service through a single channel (having only one connection to a given database in your app).

The two are basically the same in essence, so an example for one should suffice to link for the other.

Implementation

You can find the full code of this post in this repo.

We will build a very basic implementation: a shared counter.

Every caller of this package should get access to the same instance, and thus, to the same state it keeps. Our counter keeps the state `count` and allows you to `AddOne()`.

But first, we need to get access somehow to this shared instance we are talking about. The book Design Patterns uses the method `getInstance()` (note that we will capitalise it as per Go exposing rules) and we will go with that one.

package singleton

type singleton struct {
	count int
}

var instance *singleton

func GetInstance() *singleton {
	if instance == nil {
		instance = new(singleton)
	}

	return instance
}

func (s *singleton) AddOne() int {
	s.count ++
    
	return s.count
}
The implementation of a counter singleton

Note how the variable `instance` is lowercased so that it is not exposed to the users of the package.

Testing our implementation

Let's test the main part part of our implementation

package singleton

import "testing"

func TestGetInstance(t *testing.T) {
	aCounter := GetInstance()
    
	if aCounter == nil {
		t.Error("a new counter object must have been made")
	}
    
	anotherCounter := GetInstance()
    
	if aCounter != anotherCounter {
		t.Error("the counters seem to be different")
	}
}
Testing a singleton implementation; both counter variables should point to the same instance

At this point you should be convinced that our singleton works, because both variables, `aCounter` and `anotherCounter`, point to the same instance.

But if you are not, we can leverage from the access we have to the state field and test it out with these extra lines in the same test:

	mustBeOne := aCounter.AddOne()
	if mustBeOne != 1 {
		t.Error("we must have got 1")
	}

	mustBeTwo := anotherCounter.AddOne()
	if mustBeTwo != 2 {
		t.Errorf("we must have got 2")
	}
Extra testing of the singleton via the shared state

Conclusion

Singletons are the way to go when sharing an ephemeral state within a service is useful. It's implmentation is straight-forward and so is its testing.