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.