Skip to main content

Singleton Pattern 📜💍⚔️🏹

·2138 words·11 mins
Table of Contents

Hi. … 🤔, to be honest I don’t know how write an introduction to this new section 🫥.

I’ll get to the point at once.

This post be similar to:

“One Ring to rule them all, One Ring to find them, One Ring to bring them all and in the darkness bind them."
The Ring’s inscription 📜💍

And also to

En el fin del mundo solo, solito, solo
Calle Barcelona - La Vida Bohème

Singleton pattern 🏹
#

All the code of the post could be found in this link

The Singleton pattern is a creational design pattern that restricts the creation of a struct to a single instance throughout the entire program’s execution. This ensures that all parts of your code are working with the same object.

Ok, as you can read the singleton pattern bases all the responsibility in only one instance of class.

singleton-pattern
hugo new content posts/my-first-post.md The main reason are:

  • There must be exactly one instance of a class and it must be accessible to clients from a well-known access point. When the sole instance should be extensible by subclassing and clients should be able to use an extended instance without modifying
  • To be more in depth when using a singleton pattern is when we are working with classes that are used for logging, controller objects, caching, thread pool, database connections. These types of classes use a lot of computational resources and it is not a good idea to have a lot of them throughout our code 👀.

Note: For this section I’ll use go to explain the design pattern. And why not python like you used to do in the previous section? Well I really like Go as a language program, so like Python I want to master it.
A little break-off, I KNOW THAT GO IS NOT A COMPLETELY OOP, but works for the explanation, so shhh 🤫 To the end of the section I also write the Pythoner way to implement the pattern. But i didn’t explain in much detail like i use to do in Go That said, let’s get to code 🦾

Singleton pattern implementation
#

For this example we are gonna create a class to connect to a Redis database, this to create a more realistic scenario. And why Redis, … It’s a cool database, that’s all … and all the posts that I read use MySQL drivers, and MySQL to explain this pattern is to normie 🙈.

Before starting to writing code 👁️
#

Create a cloud Redis database in this link It’s free $$$.

And to connect with the database we use this driver

Redis Package 📦
#

To create a client in go, we must create a redis package.

It’s a good practice write package for every “general” functionality of your program.

settingsRedis.go
#

In the Redis package we create settingsRedis files that contains all the const to connect to the database package redisclient

package redisclient

const (
	publicEndPoint = "YourURLToConnectTOTheDataBase"
	password       = "YourPassword"
	username       = "default"
)

redis.go
#

Okay, now we create a file redis.go that contains all the attributes and methods of the Redis client

In this file, we are gonna write the following code:

  • RedisConnectionStruct, this struct save the *redis.Client as attribute
  • Global-package variable redisSingletonInstance
  • Constructor of the struct GetRedisClient
package redisclient

import (
   "time"

   "github.com/redis/go-redis/v9"
)

// The only instance of the class
var redisSinletonInstance redisConnection

// The struct of the redis client
type redisConnection struct {
   RedisClient *redis.Client
}

// The constructor of the class
func GetRedisClient() (*redisConnection, error) {

   //URL of our connection redis database
   redisURL := "redis://" + username + ":" + password + "@" + publicEndPoint

   //SET the options of the client connection
   opt, err := redis.ParseURL(redisURL)
   if err != nil {
       return nil, err
   }

   opt.DialTimeout = 5 * time.Second

   //Create the connections
   connection := redis.NewClient(opt)

   //save the connection in our "global-package" variable
   redisSinletonInstance := redisConnection{
       RedisClient: connection,
   }

   return &redisSinletonInstance, nil
}

This is our first code, we did not implement any pattern yet, but as you can see, we only create a function that return a instance of the redisConnection and we stored in redisSingletonInstance.

If way stored the all the new instance of the redisConnection structure in the redisSingletonInstance, we only have one instance? 🤔

redisClient_test.go
#

This file contains all the testing code for our redisClient.

Our first test will be to check our Redis connection to the cloud database

package redisclient_test

import (
   "context"
   "fmt"
   redisclient "test/redisClient"
   "testing"
)

func TestNewClient(t *testing.T) {

   //Create our redis connection instance
   redisConn, err := redisclient.GetRedisClient()

   //Check if we have an error
   if err != nil {
       t.Errorf("Got an error creating the client: %d", err)
   }

   //Check the connection
   conn := redisConn.RedisClient.Ping(context.Background())

   //Check if the attribute Error is != than nil
   if conn.Err() != nil {
       t.Errorf("Could not establish the connection, Error: %d", err)
   }

}

If we run our test, we get a PASS result, similar to:

=== RUN   TestNewClient
--- PASS: TestNewClient (4.74s)

Check if it is single instance ☑️
#

Right now, we may think that we are only creating a single instance (or maybe overwriting the existing instance), thus we are using the redisSinletonInstance in our constructor GetRedisClient().

Okay, so we’ll put this assumption to the test.

func TestIfIsSingleton(t *testing.T) {


   //Create one instance
   conn1, _ := redisclient.GetRedisClient()
   //Create one instance
   conn2, _ := redisclient.GetRedisClient()
   //Check if the instances are the same
   if conn1 != conn2 {
       t.Error("conn1 and conn2 are not the same instance")
   }


}

If we run the test, will get and output similar to this:

=== RUN   TestIfIsSingleton
	redisClient_test.go:37: conn1 and conn2 are not the same instance
--- FAIL: TestIfIsSingleton (0.00s)
FAIL
FAIL	command-line-arguments  0.694s
FAIL

OMG, so our 2 instance of the redisClient are not the same instance 🫠 ???.

Well, yes, this happens because every time we call GetRedisClient() we are rewriting a new value of the redisSinletonInstance, then the GetRedisClient() return a new value of the variable saved in the cache of the program.

Only one instance ❗
#

To solve the previous problem we may use a Golang approach.

singleton-pattern

The recommended approach to achieve a Singleton is by using a mutex from the sync package and change the redisSinletonInstance to a pointer. This guarantees thread-safe access to the instance creation process, especially when dealing with concurrent routines (goroutines).

Rewriting our code:

package redisclient

import (
   "sync"
   "time"

   "github.com/redis/go-redis/v9"
)

// The only instance of the class
var redisSinletonInstance *redisConnection

// For thread-safe
var lock = &sync.Mutex{}

// The struct of the redis client
type redisConnection struct {
   RedisClient *redis.Client
}

// The constructor of the class
func GetRedisClient() (*redisConnection, error) {
   //First check
   if redisSinletonInstance == nil {
       //Lock for safe thread
       lock.Lock()
       defer lock.Unlock()
       //Second check
       if redisSinletonInstance == nil {
           //URL of our connection redis database
           redisURL := "redis://" + username + ":" + password + "@" + publicEndPoint

           //SET the options of the client connection
           opt, err := redis.ParseURL(redisURL)
           if err != nil {
               return nil, err
           }

           opt.DialTimeout = 5 * time.Second

           //Create the connections
           connection := redis.NewClient(opt)

           //save the connection in our "global-package" variable
           redisSinletonInstance = &redisConnection{
               RedisClient: connection,
           }
       }
   }

   return redisSinletonInstance, nil

}

As you can see, the so-called double-checked approach. However, this code may appear a bit strange. Since the outer check redisSinletonInstance == nil has already been performed, it seems redundant to perform a second redisSinletonInstance == nil check after acquiring the lock. In fact, the outer redisSinletonInstance == nil check is to improve the execution efficiency of the program. If instance already exists, there is no need to enter the if logic, and the program can return instance directly. This avoids the overhead of acquiring the lock for every GetSingleton() call and makes the locking more fine-grained. The inner redisSinletonInstance == nil check is for considering concurrency safety. In extreme cases, when multiple goroutines reach the point of acquiring the lock simultaneously, the inner check comes into play.

If we run the TestIfIsSingleton() again, the result of the test would be similar to this

=== RUN   TestIfIsSingleton
--- PASS: TestIfIsSingleton (0.00s)

A more attractive way ✨
#

Right now we already know how to write a singleton pattern in Go, but the way we have done it doesn’t seem to be very understandable to read and pretty 🤢.

But it’s okay, Go have another way to do this

I will rewrite the code, and do the explanation later 😉 .

package redisclient


import (
   "sync"
   "time"

   "github.com/redis/go-redis/v9"
)


// The only instance of the class
var redisSinletonInstance *redisConnection

// For thread-safe
var once sync.Once

// The struct of the redis client
type redisConnection struct {
   RedisClient *redis.Client
}

// The constructor of the class
func GetRedisClient() (*redisConnection, error) {

   var (
       err error
       opt *redis.Options
   )

   once.Do(func() {
       //URL of our connection redis database
       redisURL := "redis://" + username + ":" + password + "@" + publicEndPoint

       //SET the options of the client connection
       opt, err = redis.ParseURL(redisURL)
       if err != nil {
           return
       }

       opt.DialTimeout = 5 * time.Second

       //Create the connections
       connection := redis.NewClient(opt)

       //save the connection in our "global-package" variable
       redisSinletonInstance = &redisConnection{
           RedisClient: connection,
       }
   })

   if err != nil {
       return nil, err
   }
   return redisSinletonInstance, nil

}

Go provides sync.Once method in the sync package, the function passed in once.Do only gets called once, even if GetCache gets called 1000s of times, the function inside once.Do will only get executed once and is concurrency safe. I encourage you to read more about.

This way is more attractive, isn’t it? 🤭

Python
#

Python offers flexibility in implementing the singleton pattern. This post explores one approach using the __new__ dunder method.

The __new__ method is a static method that’s integral to the class itself. Its primary role is to create and return a new instance of the class. It receives the class as its first argument, followed by any additional arguments intended for the instance construction.

In simpler terms, the __new__ method deals with class-level object creation, not object manipulation.

In the code example below, we’ll establish a Redis client and store it within a dictionary. This strategy ensures that only one instance is created, for the different Redis databases you might interact with in your software project. (Remember, a project could potentially have numerous connections to various databases).

Code
#

settings.py
#

publicEndPoint = "YourURLToConnectTOTheDataBase"
port           =  000
password       = "YourPassword"
username       = "default"

redis_client.py
#

import redis

class RedisClient:

    # Dictionary of instance to connect a differents redis database
    __instances = {}

    def __new__(cls, redis_host, redis_port, redis_username, redis_password):
        #Create a tuple with the config of the connection to the database
        config_key = (redis_host,redis_port,redis_username,redis_password)

        if config_key not in cls.__instances:
            #Create a new object
            instance = super(RedisClient, cls).__new__(cls)
            #Save our objectec in __instances dic
            cls.__instances[config_key] = instance
            #Call the constructor
            instance.__init__(redis_host, redis_port, redis_username, redis_password)

        return cls.__instances[config_key]
    
    #Constructor of the class
    def __init__(self, redis_host, redis_port, redis_username, redis_password) -> None:
        #Atributes of our class
        self.redis_host = redis_host
        self.redis_port = redis_port
        self.redis_username = redis_username
        self.redis_pasword = redis_password
        self.client = None
    
    def connect(self):
        """
        Create a connection to the redis DB

        Return: A redis client
        """
        #Check if the instance of the object has been created before
        if self.client:
            return self.client
        try:
            self.client = redis.Redis(
                host=self.redis_host,
                port=self.redis_port,
                username= self.redis_username,
                password=self.redis_pasword,
                decode_responses=True
            )
        except:
            self.client = None
    
    def ping(self):
        if self.client.ping():
            print("Ping: Pong") 

    def close(self)-> None:
        self.client.close()

test.py
#

import unittest
import redis_client
import settings

class TestRedisClinet(unittest.TestCase):
    
    def test_constructor(self):

        client = redis_client.RedisClient(
        redis_host= settings.publicEndPoint,
        redis_port= settings.port,
        redis_password= settings.password,
        redis_username=settings.username
        )
        client.connect()
        self.assertIsNotNone(client.client,"Could not create the Redis client")
    
    def test_single_instance(self):

        r1 = redis_client.RedisClient(
        redis_host= settings.publicEndPoint,
        redis_port= settings.port,
        redis_password= settings.password,
        redis_username=settings.username
        )
        r2 = redis_client.RedisClient(
        redis_host= settings.publicEndPoint,
        redis_port= settings.port,
        redis_password= settings.password,
        redis_username=settings.username
        )

        r1.connect()
        r2.connect()

        self.assertEqual(r1,r2,"Not singleton instance")



if __name__ == "__main__":
    unittest.main()

Try this code by yourself!

Brief note
#

I’ve been using redis for a short time, but I really like it, it’s quite different from MongoDB or a SQL database. Maybe later I will write some posts about Redis. 🤔

The songs of the post
#

Mi niña (mi niña)
Mi niña bonita (mi niña bonita)
Mi niña no baila pa toa la gente
Mi niña no baila pa toa la gente

Calle Barcelona - La Vida Bohème

I’ve never been a big fan of La Vida Bohème, but they released a new album Diáspora Live Vol.1. This album is a compilation of a tour that they did last year, and it’s really good.

I have to admit that La Vida Bohéme has a really good live performance.

I encourage you to listen the entire album, you can’t miss it

My favorites from the album:

Sábado
Llegó la policía
Mi primo
Fue el que disparó
Bang

Manos Arriba - La Vida Bohème