I'm working on a Go RESTful API application with multiple databases. When starting the server, the user supplies which database they would like to use.
In the application, I have three functions of which one handles the connection: selectedDb.Get()
, selectedDb.Add()
, selectedDb.Connect()
.
If somebody selects, Mysql, it handles things for Mysql, if someone selects MongoDB it handles things for Mongo and so on.
This is how I try to accomplish this:
DbInterface.go
package dbinit
type Object struct {
Uuid string
Object string
Deleted bool
}
// The interface that all connectors should have
type Intfc interface {
Connect() HERE_LIES_MY_PROBLEM
Add(string, string, string) string
Get(string) (Object, bool)
}
MySQL.go
package mysqlConnector
import (
...
)
type Mysql struct{}
// Connect to mysql
func (f Mysql) Connect() HERE_LIES_MY_PROBLEM {
client, err = sql.Open("mysql", "yourusername:yourpassword@/yourdatabase")
if err != nil {
panic(err.Error())
}
return client
}
// Add item to DB
func (f Mysql) Add(owner string, refType string, object string) string {
// do stuff related to this DB
return // a string
}
func (f Mysql) Get(Uuid string) (dbinit.Object, bool) {
// do stuff related to this DB
return // an object and a bool
}
Mongo.go
package mongoConnector
import (
...
)
type Mongo struct{}
// Connect to mongo
func (f Mongo) Connect() HERE_LIES_MY_PROBLEM {
info := &mgo.DialInfo{
Addrs: []string{hosts},
Timeout: 60 * time.Second,
Database: database,
Username: username,
Password: password,
}
client, err := mgo.DialWithInfo(info)
if err != nil {
panic(err)
}
return client
}
// Add item to DB
func (f Mongo) Add(owner string, refType string, object string) string {
// do stuff related to this DB
return // a string
}
func (f Mongo) Get(Uuid string) (dbinit.Object, bool) {
// do stuff related to this DB
return // an object and a bool
}
main.go
...
var selectedDb dbinit.Intfc
commandLineInput := "mysql" // just for the example
if commandLineInput == "mysql" {
selectedDb = mysqlConnector.Mysql{}
} else if commandLineInput == "mongo" {
selectedDb = mongoConnector.Mongo{}
}
client := selectedDb.Connect()
// this runs everytime the API is called
api.HandlerFoobar = foobar.handlerFunction(func(params foobar.Params) middleware.Responder {
// Here I want to add something to the selected dbinit
selectedDb.Get(client, addStringA, addStringB, addStringC)
return // the API response
})
...
Problem statement
When I return the client for Mysql, it doesn't work for Mongo and visa versa.
I want to connect to the database ONLY when starting the server and store the client
inside the client variable. The problem, however, is that Mongo returns another client than Mysql does and so forth.
HERE_LIES_MY_PROBLEM
in the code?If you want to preserve the interface with those methods, you should change a little your interface as:
Interface:
// The interface that all connectors should have
type Intfc interface {
// Connect to the database, if an error occur at the moment
// of connection, return the error
Connect() error
// Add returns a string, it returns an error if something occurs
Add(string, string, string) (string, error)
Get(string) (Object, bool)
}
MySQL:
type Mysql struct{
conn *sql.DB // contains the connection to the DB
}
// Connect to mysql
func (f *Mysql) Connect() error {
conn, err := sql.Open("mysql", "yourusername:yourpassword@/yourdatabase")
if err != nil {
return error
}
f.conn = conn
return nil
}
// Add item to DB
func (f *Mysql) Add(owner string, refType string, object string) (string, error) {
// do something
return // a string and error
}
func (f *Mysql) Get(Uuid string) (dbinit.Object, bool) {
// do something
return // an object and a bool
}
Mongo:
type Mongo struct{
session *mgo.Session
}
// Connect to mongo
func (f *Mongo) Connect() error {
info := &mgo.DialInfo{
// some data
}
session, err := mgo.DialWithInfo(info)
if err != nil {
return error
}
f.session = session
return nil
}
// Add item to DB
func (f *Mongo) Add(owner string, refType string, object string) (string, error) {
// do something
return // a string and error (it could be nil at success)
}
func (f *Mongo) Get(Uuid string) (dbinit.Object, bool) {
// do something
return // an object and a bool
}
Main:
var selectedDb dbinit.Intfc
commandLineInput := "mysql"
if commandLineInput == "mysql" {
selectedDb = &mysqlConnector.Mysql{}
} else if commandLineInput == "mongo" {
selectedDb = &mongoConnector.Mongo{}
}
err := selectedDb.Connect()
if err != nil {
panic(err)
}
// this runs everytime the API is called
api.HandlerFoobar = foobar.handlerFunction(func(params foobar.Params) middleware.Responder {
data, err := selectedDb.Add(addStringA, addStringB, addStringC)
if err != nil {
// do something
}
return // the API response
})
But you also can remove the Connect() error
method from Intfc
and just use Add
and Get
, but you should update the packages like:
Mysql
// Connect to mysql, it could be any function name
func Connect() (*Mysql, error) {
connection, err := sql.Open("mysql", "yourusername:yourpassword@/yourdatabase")
if err != nil {
return nil, error
}
return &Mysql{conn: connection}
}
Mongo
// Connect to mongo, it could be any function name
func Connect() (*Mongo, error) {
info := &mgo.DialInfo{
// some data
}
s, err := mgo.DialWithInfo(info)
if err != nil {
return nil, error
}
return &Mongo{session: s}
}
Main
var selectedDb dbinit.Intfc
var err error
commandLineInput := "mysql"
if commandLineInput == "mysql" {
selectedDb, err = mysqlConnector.Connect()
} else if commandLineInput == "mongo" {
selectedDb, err = mongoConnector.Connect()
}
if err != nil {
panic(err)
}
I think, that interface Intfc (or better name DbIntfc) should have only methods Get and Add.
And it should exist another function - but not a part of DbIntfc, that returns DbIntfc - that connects to MySql or MongoDb. Let's look:
type MySqlDbIntfc struct{
db *Sql.DB
}
// Connect to mysql
func NewMySqlDbIntfc() (DbIntfc,error) {
// Please do not prefer panic in such abstract methods
client, err := sql.Open("mysql", "yourusername:yourpassword@/yourdatabase")
if err != nil {
return nil, err
}
return &MySqlDbIntfc{client}, nil
}
func (mySqlDb *MySqlDbIntfc) Get(Uuid string) (dbinit.Object, error) {
var obj dbinit.Object
err := mySqlDb.db.QueryRow("SELECT uuid, object, deleted FROM myTable WHERE uuid=?", Uuid).Scan(&obj.Uuid, &obj.Object, &obj.Deleted)
if err != nil {
return dbinit.Object{}, err
}
return obj, nil
}
And implemention NewMgoDbIntfc should be easy, including methods NewMgoDbIntfc.Add/Get.
And decision whether use NewMySqlDbIntfc or NewMgoDbIntfc should also be easy.
Elaborating on my comment, instead of
type Intfc interface {
Connect() HERE_LIES_MY_PROBLEM
Add(string, string, string) string
Get(string) (Object, bool)
}
you can use
type Intfc interface {
Connect() DBClient
}
and
type DBClient interface {
Add(string, string, string) string
Get(string) (Object, bool)
}
type MySQLClient sql.DB
type MongoClient mgo.Session
func (f Mysql) Connect() DBCLient {
client, err = sql.Open("mysql", "yourusername:yourpassword@/yourdatabase")
if err != nil {
panic(err.Error())
}
return MySQLClient(client)
}
func (f Mongo) Connect() DBClient {
info := &mgo.DialInfo{
Addrs: []string{hosts},
Timeout: 60 * time.Second,
Database: database,
Username: username,
Password: password,
}
client, err := mgo.DialWithInfo(info)
if err != nil {
panic(err)
}
return MongoClient(client)
}
func (s *MySQLClient) Add(...) {
// ...
}
func (s *MongoClient) Add(...) {
// ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With