DEV Community

Syed Omair
Syed Omair

Posted on

1

The Singleton Pattern in Golang

In software development, there are times when you need to ensure that only one instance of a particular class exists. This is where the Singleton pattern comes into play. It's a creational design pattern that restricts the instantiation of a class to a single object.

Why Use Singleton?

  • Resource Management: When dealing with resources like database connections, file systems, or configuration settings, having multiple instances can lead to conflicts and inefficiencies.
  • Centralized Access: Singletons provide a global point of access, making it easy for different parts of your application to interact with a shared resource.
  • Configuration: A singleton can hold application-wide configuration settings.

The Go Implementation: Database Connection Example

Let's illustrate the Singleton pattern with a practical example: managing database connections in Go.

type PostgresAdapter struct {
    dbUrl         string
    dbMaxIdle     int
    dbMaxOpen     int
    dbMaxLifeTime int
    dbMaxIdleTime int
    gormConf      string
    db            *gorm.DB
    mu            sync.Mutex
}

func NewPostgresAdapter(url string, dbMaxIdle, dbMaxOpen, dbMaxLifeTime, dbMaxIdleTime int, gormConf string) *PostgresAdapter {
    return &PostgresAdapter{dbUrl: url,
        dbMaxIdle:     dbMaxIdle,
        dbMaxOpen:     dbMaxOpen,
        dbMaxLifeTime: dbMaxLifeTime,
        dbMaxIdleTime: dbMaxIdleTime,
        gormConf:      gormConf,
    }
}

func (p *PostgresAdapter) MakeConnection() (*gorm.DB, error) {
    p.mu.Lock()
    defer p.mu.Unlock()

    if p.db != nil {
        return p.db, nil
    }

    db, err := makeConnection(postgres.Open(p.dbUrl), p.dbUrl, p.dbMaxIdle, p.dbMaxOpen, p.dbMaxLifeTime, p.dbMaxIdleTime, p.gormConf)
    if err != nil {
        return nil, err
    }

    p.db = db
    return db, nil
}
Enter fullscreen mode Exit fullscreen mode

In this code, we have PostgresAdapter that handle database connections using the gorm library. We want to ensure that each adapter creates only one database connection. Here's how the Singleton pattern is implemented:

  • Private Instance:

Each adapter (PostgresAdapter) has a db *gorm.DB field to store the database connection.

  • Mutex for Thread Safety:

A sync.Mutex (mu) is used to protect the critical section where the database connection is created. This prevents race conditions in concurrent environments.

  • Check for Existing Instance:

The MakeConnection() method checks if the db field is already populated. If it is, the existing connection is returned.

  • Instance Creation:

If the db field is nil, the makeConnection() function is called to create the database connection.
The newly created connection is then stored in the db field.

Key Singleton Elements in Action:

  • The mu.Lock() and mu.Unlock() calls ensure that only one goroutine can create the database connection at a time.
  • The if p.db != nil checks prevent redundant database connection creation.
  • By storing the database connection in the adapter struct, we maintain a single, persistent connection throughout the adapter's lifecycle.

Benefits of This Approach:

  • Efficiency: We avoid the overhead of creating multiple database connections.
  • Consistency: All parts of the application using the adapter share the same database connection.
  • Thread Safety: The mutex ensures that the Singleton implementation is safe for concurrent use.

Important Considerations:

  • Testability: Singletons can make unit testing more challenging due to their global state. Consider using dependency injection to make your code more testable.
  • Overuse: Avoid using the Singleton pattern when a simple object instance would suffice.

Conclusion:

The Singleton pattern is a valuable tool for managing resources and ensuring centralized access in your applications. By understanding its principles and implementing it carefully, you can create more efficient and robust software. In our database connection example, the Singleton pattern helps us maintain a single, thread-safe connection, improving performance and resource utilization.

Image of Datadog

Create and maintain end-to-end frontend tests

Learn best practices on creating frontend tests, testing on-premise apps, integrating tests into your CI/CD pipeline, and using Datadog’s testing tunnel.

Download The Guide

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay