Как работать с неизвестными переменными или Как работать с несколькими базами данных
Я работаю над 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
- Не могу ли я получить парадигму 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)
}
Опять же, этот подход - мое мнение, что есть другие, которые могут удовлетворить ваши.