Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with unknown variables or How to deal with multiple databases

Tags:

go

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.

  1. What should be in the places where I have HERE_LIES_MY_PROBLEM in the code?
  2. Or do I get the Go paradigm wrong for dealing with these things?
like image 696
Bob van Luijt Avatar asked May 03 '17 09:05

Bob van Luijt


3 Answers

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)
}
like image 98
Motakjuq Avatar answered Oct 26 '22 00:10

Motakjuq


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.

like image 39
lofcek Avatar answered Oct 26 '22 01:10

lofcek


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(...) {
    // ...
}
like image 43
Ralph Avatar answered Oct 26 '22 00:10

Ralph