Singleton Design Pattern in Go: A Safe Approach to Global Variables


Introduction

In the realm of software design, there is a widely shared belief that global variables can cause numerous issues, often sparking debates (or, depending on whom you ask, impassioned arguments). Over time, I have come to realize that the use of global variables tends to signify poor code quality and overall suboptimal design. They have a knack for introducing complex states that are challenging to manage and debug, fostering tight coupling and making code testing more arduous. Ultimately, these approaches lead to code that is more difficult to maintain. But now we got through the disclaimer portion let’s move on from discussing the reasons for avoiding them and focus instead on a safer alternative: the Singleton design pattern. This approach will provide us with guidance on how to navigate these risky situations with care.

The Singleton in Action

The Singleton design pattern ensures that a class has only one instance and provides a global point of access to this instance. More than just being a global variable, it offers a structured and safe approach to having a single point of truth throughout an application.

Let’s use a real-world example in Go to illustrate this pattern:

internal/singleton/db/singleton.go:

package db

import (
	"sync"
)

var (
	clientInstance *client
	mutex          sync.Mutex
)

type QueryResult string

type Singleton struct {
	dbc *client
}

func (d *Singleton) Query(q string) QueryResult {
	return d.dbc.query(q)
}

func GetSingleton() Singleton {
	mutex.Lock()
	defer mutex.Unlock()

	if clientInstance == nil {
		clientInstance = &client{}
	}
	return Singleton{dbc: clientInstance}
}

Here, instead of exposing a global variable directly, the GetSingleton function provides controlled access to a single client instance. This method ensures thread safety with the use of a mutex, so even in multi-threaded environments, you’re guaranteed a single instance.

Benefits of the Singleton Pattern as a Safe Global

  1. Controlled Access: Instead of directly mutating a global variable, Singletons provide methods to interact with it, offering more control and encapsulation.
  2. Resource Saving: Only one instance of the Singleton class or struct is instantiated, preventing wastage of resources from creating, maintaining, and destroying multiple instances.
  3. Consistency: A Singleton ensures all parts of your application interact with the same instance. This can be particularly useful for things like configuration management or connection pools.
  4. Lazy Initialization: The instance is only created when required. This ensures that if the instance isn’t needed, it won’t be created, thereby optimizing memory usage.
  5. Thread Safety: By using mutex locks, as seen in the code, our Singleton implementation ensures thread safety. In concurrent scenarios, like when using goroutines in Go, the Singleton property holds true.
  6. Reduced Side Effects: Traditional global variables can be mutated unexpectedly. By controlling access and mutation through Singleton methods, these side effects are minimized.

Wrapping It Up

Here’s the main logic that uses our Singleton:

cmd/singleton/main.go:

package main

import (
	"fmt"
	"sync"

	"github.com/domhoward14/creational_design_patterns/internal/singleton/db"
)

var wg sync.WaitGroup

func main() {
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go execQuery(i, &wg)
	}

	wg.Wait()
	fmt.Println("All goroutines have finished executing.")
}

func execQuery(i int, wg *sync.WaitGroup) {
	defer wg.Done()
	s := db.GetSingleton()
	res := s.Query(fmt.Sprintf("SELECT * FROM test_table_%d;", i))
	fmt.Println(res)
}

This demonstrates the Singleton’s effectiveness in a concurrent setting. Ten goroutines run, all accessing the same Singleton instance of our database client.

Conclusion

In conclusion, while global variables have their critics, the Singleton pattern offers a more refined, controlled, and safe way to manage global state in applications. It combines the accessibility of global variables with the controlled access and safety mechanisms that good software design demands, making it an invaluable pattern in the software developer’s toolkit.

Happy Coding!

Email: [email protected]
Linkedin: https://www.linkedin.com/in/eric-howard-8a4166127/