1

I am currently building a Go application that needs to connect to multiple databases dynamically.

For context I have 22 Databases (db1, db2, db3...) and the dbUser, dbPass and dbPort remains the same. To determine which database to connect to, I need access to the query param in echo before database connection.

I need a solution to connect to the right database efficiently. What are some best practices and methods for achieving this in Go?

Main.go

package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/go-sql-driver/mysql"
    _ "github.com/lib/pq"

    "github.com/labstack/echo"
    "github.com/spf13/viper"

    _variantHttpDelivery "backend/server/variant/delivery/http"
    _variantHttpDeliveryMiddleware "backend/server/variant/delivery/http/middleware"
    _variantRepo "backend/server/variant/repository/postgres"
    _variantUcase "backend/server/variant/usecase"
)

func init() {
    viper.SetConfigFile(`config.json`)
    err := viper.ReadInConfig()
    if err != nil {
        panic(err)
    }

    if viper.GetBool(`debug`) {
        log.Println("Service RUN on DEBUG mode")
    }
}

func main() {
    dbHost := viper.GetString(`database.host`)
    dbPort := viper.GetString(`database.port`)
    dbUser := viper.GetString(`database.user`)
    dbPass := viper.GetString(`database.pass`)
    dbName := viper.GetString(`database.name`)

    connection := fmt.Sprintf("postgresql://%s:%s@%s:%s/%s", dbUser, dbPass, dbHost, dbPort, dbName)
    dsn := fmt.Sprintf("%s?%s", connection)
    dbConn, err := sql.Open(`postgres`, dsn)
    log.Println("Connection Successful 👍")

    if err != nil {
        log.Fatal(err)
    }
    err = dbConn.Ping()

    if err != nil {
        log.Fatal(err)
    }

    defer func() {
        err := dbConn.Close()
        if err != nil {
            log.Fatal(err)
        }
    }()

    e := echo.New()
    middL := _variantHttpDeliveryMiddleware.InitMiddleware()
    e.Use(middL.CORS)
    variantRepo := _variantRepo.NewPsqlVariantRepository(dbConn)

    timeoutContext := time.Duration(viper.GetInt("context.timeout")) * time.Second
    au := _variantUcase.NewVariantUsecase(variantRepo, timeoutContext)
    _variantHttpDelivery.NewVariantHandler(e, au)

    log.Fatal(e.Start(viper.GetString("server.address"))) //nolint
}

Repository which handles all the database logic

package postgres

import (
    "backend/server/domain"
    "context"
    "database/sql"
    "github.com/sirupsen/logrus"
    "reflect"
)

type psqlVariantRepository struct {
    Conn *sql.DB
}

// NewPsqlVariantRepository will create an object that represent the variant.Repository interface
func NewPsqlVariantRepository(conn *sql.DB) domain.VariantRepository {
    return &psqlVariantRepository{conn}
}

func (m *psqlVariantRepository) GetByVCF(ctx context.Context, vcf string) (res domain.Variant, err error) {
    query := `SELECT * FROM main1 WHERE variant_vcf = $1`

    list, err := m.fetch(ctx, query, vcf)

    if err != nil {
        return domain.Variant{}, err
    }

    if len(list) > 0 {
        res = list[0]
    } else {
        return res, domain.ErrNotFound
    }

    return
}

func (m *psqlVariantRepository) fetch(ctx context.Context, query string, args ...interface{}) (result []domain.Variant, err error) {
    rows, err := m.Conn.QueryContext(ctx, query, args...)

    if err != nil {
        logrus.Error(err)
        return nil, err
    }

    defer func() {
        errRow := rows.Close()
        if errRow != nil {
            logrus.Error(errRow)
        }
    }()

    result = make([]domain.Variant, 0)

    for rows.Next() {
        t := domain.Variant{}
        values := make([]interface{}, 0, reflect.TypeOf(t).NumField())
        v := reflect.ValueOf(&t).Elem()

        for i := 0; i < v.NumField(); i++ {
            if v.Type().Field(i).Type.Kind() == reflect.String {
                values = append(values, new(sql.NullString))
            } else {
                values = append(values, v.Field(i).Addr().Interface())
            }
        }

        err = rows.Scan(values...)
        if err != nil {
            logrus.Error(err)
            return nil, err
        }

        for i, value := range values {
            if ns, ok := value.(*sql.NullString); ok {
                v.Field(i).SetString(ns.String)
            }
        }

        result = append(result, t)
    }

    logrus.Info("Successfully fetched results from database 👍")

    return result, nil
}

So far I couldn't find any solution

1
  • Encapsulate the database connection, maintain *sql.DB link for each database name, using map[string]*sql.DB or other connection pool related (skip here for simplicity). And get the corresponding *sql.DB before querying and initialize psqlVariantRepository to query, which will not cause performance waste. Commented Feb 1, 2023 at 5:10

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.