Как работать с неизвестными переменными или Как работать с несколькими базами данных

Я работаю над API-интерфейсом Go RESTful API с несколькими базами данных. При запуске сервера пользователь поставляет ту базу данных, которую они хотели бы использовать.

В приложении у меня есть три функции, из которых обрабатывается соединение: selectedDb.Get(), selectedDb.Add(), selectedDb.Connect().

Если кто-то выбирает, Mysql, он обрабатывает вещи для Mysql, если кто-то выбирает MongoDB, он обрабатывает вещи для Mongo и т.д.

Вот как я пытаюсь это сделать:

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:[email protected]/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

})

...

Описание проблемы

Когда я возвращаю клиента для Mysql, он не работает для Mongo и наоборот.

Я хочу подключиться к базе данных ТОЛЬКО при запуске сервера и сохранить client внутри клиентской переменной. Проблема, однако, в том, что Mongo возвращает другого клиента, чем Mysql, и т.д.

  • Что должно быть в тех местах, где у меня есть HERE_LIES_MY_PROBLEM в коде?
  • Или я не ошибаюсь в парадигме Go, чтобы справиться с этими вещами?

Ответ 1

Если вы хотите сохранить интерфейс с этими методами, вы должны немного изменить свой интерфейс:

Интерфейс:

// 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:[email protected]/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
}

Монго:

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
})

Но вы также можете удалить метод Connect() error из Intfc и просто использовать Add и Get, но вы должны обновить пакеты, например:

Mysql

// Connect to mysql, it could be any function name
func Connect() (*Mysql, error) {
    connection, err := sql.Open("mysql", "yourusername:[email protected]/yourdatabase")
    if err != nil {
        return nil, error
    }
    return &Mysql{conn: connection}
}

Монго

// 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}
}

Главная

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)
}

Ответ 2

Разрабатывая мой комментарий, вместо

type Intfc interface {
    Connect() HERE_LIES_MY_PROBLEM
    Add(string, string, string) string
    Get(string) (Object, bool)
}

вы можете использовать

type Intfc interface {
    Connect() DBClient
}

и

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:[email protected]/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(...) {
    // ...
}

Ответ 3

Я думаю, что интерфейс Intfc (или лучше имя DbIntfc) должен иметь только методы Get и Add.

И он должен существовать и другой функцией, но не частью DbIntfc, которая возвращает DbIntfc, которая подключается к MySql или MongoDb. Давайте посмотрим:

    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:[email protected]/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
    }

И реализация NewMgoDbIntfc должна быть простой, включая методы NewMgoDbIntfc.Add/Get.

И решить, должно ли быть легко использовать NewMySqlDbIntfc или NewMgoDbIntfc.

Ответ 4

  • Что должно быть в тех местах, где у меня есть HERE_LIES_MY_PROBLEM в коде?

Как вы можете видеть из источника, что пакет DialWithInfo() return error и *Session из mgo и что является struct. поэтому вы можете заменить HERE_LIES_MY_PROBLEM на *mgo.Session

  1. Не могу ли я получить парадигму Go для решения этих проблем?

Что касается идиоматики для подключения к нескольким базам данных, я думаю, что существует много мнений. И вот некоторые из моих мыслей, чтобы соединиться с монго и redis:

var logger *log.Logger

func init() {
    logger = log.New(os.Stderr,
        "Database :: ",
        log.Ldate|log.Ltime|log.Lshortfile)
}

//we create different types of databse connection here.
func SystemConnection() map[string]interface{} {
    listConnection := make(map[string]interface{})
    var err error
    // create redis connection
    redisConn := RedisHost{
        Address:  "localhost:6379",
        Password: "",
        DB:       0,
    }

    redisConnection, err := redisConn.Connect()
    if err != nil {
        panic(err)
    }

    //create mongodb connection
    mongo := MongoHost{
        Host: "localhost",
        Port: "27017",
    }
    mongoConnection := mongo.Connect()

    listConnection["redis"] = redisConnection
    listConnection["mongodb"] = mongoConnection
    return listConnection
}

func GetMongo() *mgo.Session {
    //create mongodb connection
    mongo := MongoHost{
        Host: "localhost",
        Port: "27017",
    }
    mongoConnection := mongo.Connect()

    return mongoConnection
}

вы можете увидеть источник здесь

Чтобы использовать вышеуказанный код, вы можете вызвать SystemConnection() на init() в своей основной программе. например:

func init(){
    listConnection := database.SystemConnection()

    //getting redis connection convert it from interface to *redisClient.
    redisConn := listConnection["redis"].(*redis.Client)

    // get postgre connection.
    mongoConn := listConnection["mongodb"].(*mgo.Session)

}

Опять же, этот подход - мое мнение, что есть другие, которые могут удовлетворить ваши.