Singleton Pattern with Go — Access global variables thread safe

What is Singleton Pattern?
If you have worked with backend development, you might have come across a pattern where you need to access a common object which was created during application startup. There should only be a single instance of such an object. This is known as the singleton pattern. I am using the term object as of now. Since this is specific to Object Oriented Programming languages only, you can think of a global struct or variable as an object for functional programming language.
The most common example is logging into a file. When our application starts, we might want to create an instance of a class that reads and locks a file at application startup. For any logging that is required to be done, the same class should be used and only its functions should be called to avoid conflicts.
Why not Global variables?
When we talk about functional programming, there are no classes. How would you implement a singleton pattern? One such way is to define a variable in main()
or init()
function and export that variable by capitalizing it. However, this approach has drawbacks.
Imagine exporting an important variable globally where any function can access it and any function can modify it. Others will be able to read such variables. This can cause chaos. While developing the application, we will definitely be careful about this, however, we can never know what change can destroy the working application. There are various other problems as well with this approach:
- Difficult to track where this variable is modified.
- Makes code tightly coupled.
- Can introduce race conditions if being modified by multiple threads or go-routines.
How to make it right?
A great way to do this is to create a variable and create a function that will check if there is some data. If yes, just return the variable. If not, initialize and return.
Let’s take a look at an example. Suppose, we want that there should be a variable containing a database connection. It’s a very common use case and it should be common across the application instance. Below are the steps:
- Create a
struct
namedConfig
and create a variable of*Config
type. Create a variable once as well in order to make it thread-safe.
type Config struct {
Connection *sql.DB
}
var instance *Config
var once sync.Once
2. Create a new exported function GetConfig
. We will use the sync.Once
package to make sure that instance
should be initialized only once.
func GetConfig() *Config {
once.Do(func() {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/mydatabase")
if err != nil {
panic(err.Error())
}
instance = &Database{Connection: db}
})
return instance
}
And this is it. Whenever we need application config, we can simply call the GetConfig()
function which will return the instance
variable. This will make sure there will only be a single instance of the variable which can be accessed by n
number of threads. So it is thread safe as well.
The complete code looks like below. I have modified some variable names to make them more specific to the database. However, it's your choice. I have additionally added a few query functions as well.
package singleton
import (
"database/sql"
"sync"
_ "github.com/go-sql-driver/mysql"
)
type Database struct {
conn *sql.DB
}
var instance *Database
var once sync.Once
func GetInstance() *Database {
once.Do(func() {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/mydatabase")
if err != nil {
panic(err.Error())
}
instance = &Database{conn: db}
})
return instance
}
func (d *Database) Query(query string, args ...interface{}) (*sql.Rows, error) {
return d.conn.Query(query, args...)
}
func (d *Database) Exec(query string, args ...interface{}) (sql.Result, error) {
return d.conn.Exec(query, args...)
}
You can follow me on LinkedIn here.