Sharing a globally defined db conn with multiple packages

Simple answer: pass an initialised connection pool to your packages' own globals.

e.g.

// package stuff

var DB *sql.DB

func GetAllStuff() (*Stuff, error) {
    err := DB.Query("...")
    // etc.
}

// package things

var DB *sql.DB

func GetAllThings() (*Thing, error) {
    err := DB.Query("...")
    // etc.
}

// package main

func main() {
    db, err := sql.Open("...")
    if err != nil {
        log.Fatal(err)
    }

    stuff.DB = db
    things.DB = db

    // etc.
}

We define package-level globals, make sure they're exported (capitalised) and then pass a pointer to our connection pool to them.

This is "okay", but can mask "where" things are being used. If you're looking at a handler it may not be clear where the connection is coming from, especially as your package grows. A more scalable approach might look like the below:

// package stuff

type DB struct {
    *sql.DB
}

func New(db *sql.DB) (*DB, error) {
    // Configure any package-level settings
    return &DB{db}, nil
}

func (db *DB) GetAllStuff() (*Stuff, error) {
    err := db.Query("...")
    // etc.
}

// package things

type DB struct {
    *sql.DB
}

func New(db *sql.DB) (*DB, error) {
    // Configure any package-level settings
    return &DB{db}, nil
}

func (db *DB) GetAllThings() (*Thing, error) {
    err := db.Query("...")
    // etc.
}

// package main

func main() {
    db, err := sql.Open("...")
    if err != nil {
        log.Fatal(err)
    }

    stuffDB, err := stuff.New(db)
    if err != nil {
        log.Fatal(err)
    }

    thingsDB, err := things.New(db)
    if err != nil {
        log.Fatal(err)
    }

    // Simplified.
    http.HandleFunc("/stuff/all", stuff.ShowStuffHandler(stuffDB))
    http.HandleFunc("/things/all", things.ShowThingsHandler(thingsDB))

    // etc.
}

func ShowStuffHandler(db *stuff.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // We can use our stuff.DB here
        stuff, err := db.GetAllStuff()
        // etc.
    }
}

If you have more than just the DB connection as a dependency (e.g. config params, hostnames, etc.) , wrap them in an things.Env struct or a stuff.Env struct for each package.

An example would be to have a things.New("deps...") *Env function that returns a configured *things.Env that contains the dependencies used by your things package.


There is also the option of creating another package to hold your database connection-related settings. It can then have a package level global, which can be initialized in main and used in any package that is importing it.

This way, you can explicitly see that the database package is being imported. Here is some sample code.

package database

var (
    // DBCon is the connection handle
    // for the database
    DBCon *sql.DB
)

package main

import "myApp/database"

func main() {

    var err error
    database.DBCon, err = sql.Open("postgres", "user=myname dbname=dbname sslmode=disable")

}

package user

import "myApp/database"

func Index() {
    // database handle is available here
    database.DBCon

    ...
}

Tags:

Go