如何在多个程序包之间共享单个数据库连接

I have two packages named client and worker. I want to share same ssdb, mysql and redis connection with both the packages. One more similar problem that i am facing to share auth between these two packages.

app
  -> client pkg
  -> worker pkg
  main.go (contains auth as global variable)

Can anyone please suggest me the best way to implement these two things ?

There's lots of ways to do this and each approach has pros and cons and it really depends on what you are doing. One easy way to do this is to have a third package with the DB connection and import it from the other two packages and from the main package.

app
  -> client pkg // import "app/global"
  -> worker pkg // import "app/global"
  -> global pkg // Contains ssdb and auth as global variables
  main.go

Another approach that might be better depending on what you are doing is to have the functions on the client and worker packages accept a db connection as a parameter and from main.go initialize the db and pass it as a parameter when you call a function that needs it.

It depends on what you are doing but for big projects it's easier to maintain if you just have one package doing all your db operations and you access it from all the places you need to do something. That way you don't have to worry about this issue because only one package has to worry about the db connection even if several packages use it.

Edit:

The problem with global variables is that they can be modified at the same time from everywhere in your project and it can introduce race conditions, but there is nothing wrong in using them when this is not an issue.

In this case, you are just setting the value once, when you connect to the DB and then just use the object.

You mentioned you want to have another package for the auth, I recommend just having one package and having in it everything you need to access from more than one package, in this case ssdb and auth.

Here's one approach that is not always obvious to new Go developers, is a little elbow grease to implement but not terribly complex, and usually works fine in beginner apps:

app
    client // imports storage
    worker // imports storage
    config // all environment-related config goes here
    storage   // storage-engine-generic interface to the packages below it
        ssdb  // all ssdb-specific code here
        mysql // all mysql-specific code here
        redis // ditto

It uses package variables. If you're paranoid about an accidental write to an exported package variable, you can avoid the problem by using unexported package variables. Take advantage of the limited definition of Exported Identifiers in Go (see language specification).

In main, call

config.Init(configfile)
storage.Init()

Define your config.Init function to read the config file and set package variables to the connection information for your databases. If you're using enexported package variables, then allow public read-only access through exported functions. Otherwise you may be able to skip the functions, depending on what other features you want.

In storage, your Init function calls

ssdb.Init()
mysql.Init()
redis.Init()

Then also in storage you'll have public functions that client and server use that aren't specific to a storage engine, such as

func GetImage(id string) ([]byte) {
    return mysql.GetImage(id)
}

or whatever is appropriate for your application. The storage level of abstraction may or may not be worth it for you depending on how you change your app in the future. You decide whether it's worth investing in it.

In mysql package, you import config, and you have something like

var db *sql.DB
func Init() {
    getDb()
}
func getDb() (*sql.DB) {
    if db == nil {  // or something
        config.Log.Println("Opening db connection to mysql")
        db, err := sql.Open("mysql", config.MysqlConnectionString())
        // do something with err, possibly have a retry loop
    }
    return db
}
func GetImage(id string) ([]byte)
     db := getDb()
     // ...

The functions in the mysql package can use the db unexported package variable, but other packages cannot.

Using an unexported package variable with exported-function read-only access is not a terrible practice or particularly complex. That said, it's usually unecessary. If db were the exported package variable Db, would you suddenly type

mysql.Db, _ = sql.Open("mysql", "LEEERRROOYYYYY!!!!")

in your client code (and also decide to import mysql and sql to do it) and then deploy to production? Why would you be more likely to do that than to intentionally break any other part of your code?

Note that if you just typed

mysql.Db = "LEEERRROOYYYYYY!!!!"

Your application would fail to compile because of a type mismatch.