How to handle error of singleton only once

Issue

How to handle error of singleton only once?

I have a singleton service which could generate error only at first call and then it returns already created instance.

Service looks like below:

package data

import (
    "sync"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

var (
    databaseSingleton *gorm.DB
    once              sync.Once
)

func NewDatabase() (*gorm.DB, error) {
    once.Do(func() {
        // ...
        databaseSingleton, err = gorm.Open(postgres.Open(connectionString), config)
        if err != nil {
            return nil, err
        }
    })
    return databaseSingleton, nil
}

The problem is multiple services which uses databaseSingleton above handle error which can occurs only once.

Services which uses databaseSingleton looks like below:

func NewServiceOne() (ServiceOne, error) {
   database, err := NewDatabase()
   // want omit this error handling
   if err != nil {
       return nil, err
   }
   return &serviceOne{database}, nil
}
func NewServiceTwo() (ServiceTwo, error) {
   database, err := NewDatabase()
   // want omit this error handling
   if err != nil {
       return nil, err
   }
   return &serviceTwo{database}, nil
}
func NewServiceThree() (ServiceThree, error) {
   database, err := NewDatabase()
   // want omit this error handling
   if err != nil {
       return nil, err
   }
   return &serviceThree{database}, nil
}

If there any way to omit this error handling because err could be generated only once?

Solution

If the error occurs (only once), your databaseSingleton will not be setup. You should return the error in all cases.

Although this isn’t something you can do anything about (since the attempt to initialize databaseSingleton will not be repeated due to the use of sync.Once), you could as well halt the app.

In fact, there is no point deferring this initialization, you could just do it during package init, and terminate if it fails. And if it succeeds, you could use databaseSingleton without having to check error of the initialization.

So simply do it like this:

var databaseSingleton *gorm.DB

func init() {
    var err error
    databaseSingleton, err = gorm.Open(postgres.Open(connectionString), config)
    if err != nil {
        log.Fatalf("Failed to connect to DB: %v", err)
    }
}

NewServiceOne() could look like this:

func NewServiceOne() ServiceOne {
   return &serviceOne{databaseSingleton}
}

Answered By – icza

Answer Checked By – Willingham (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.