如何处理未知变量或如何处理多个数据库

huangapple go评论76阅读模式
英文:

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

问题

我正在处理一个使用多个数据库的Go RESTful API应用程序。在启动服务器时,用户会指定他们想要使用的数据库。

在应用程序中,我有三个函数,其中一个处理连接:selectedDb.Get()selectedDb.Add()selectedDb.Connect()

如果有人选择了MySQL,它会处理MySQL相关的事情;如果有人选择了MongoDB,它会处理MongoDB相关的事情,依此类推。

以下是我尝试实现这一目标的方式:

DbInterface.go

package dbinit

type Object struct {
    Uuid    string
    Object  string
    Deleted bool
}

// 所有连接器都应该具备的接口
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{}

// 连接到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
}

// 向数据库添加项目
func (f Mysql) Add(owner string, refType string, object string) string {
    // 处理与该数据库相关的事务

    return // 一个字符串
}

func (f Mysql) Get(Uuid string) (dbinit.Object, bool) {
    // 处理与该数据库相关的事务

    return // 一个对象和一个布尔值
}

Mongo.go

package mongoConnector

import (
    ...
)

type Mongo struct{}

// 连接到MongoDB
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
}

// 向数据库添加项目
func (f Mongo) Add(owner string, refType string, object string) string {
    // 处理与该数据库相关的事务

    return // 一个字符串
}

func (f Mongo) Get(Uuid string) (dbinit.Object, bool) {
    // 处理与该数据库相关的事务

    return // 一个对象和一个布尔值
}

main.go

...

var selectedDb dbinit.Intfc

commandLineInput := "mysql" // 仅作为示例

if commandLineInput == "mysql" {
    selectedDb = mysqlConnector.Mysql{}
} else if commandLineInput == "mongo" {
    selectedDb = mongoConnector.Mongo{}
}

client := selectedDb.Connect()

// 每次调用API时都会运行这里
api.HandlerFoobar = foobar.handlerFunction(func(params foobar.Params) middleware.Responder {

    // 在这里,我想向selectedDb添加一些内容
    selectedDb.Get(client, addStringA, addStringB, addStringC)

    return // API响应

})

...

问题陈述

当我返回MySQL的client时,它对于MongoDB不起作用,反之亦然。

我希望只在启动服务器时连接到数据库,并将client存储在client变量中。然而,问题在于MongoDB返回的client与MySQL返回的client不同。

  1. 在代码中的HERE_LIES_MY_PROBLEM位置应该填写什么?
  2. 或者,我是否对处理这些事情的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?

答案1

得分: 5

如果你想保留这些方法与接口的关联,你需要稍微修改接口的定义:

接口:

// 所有连接器都应该具有的接口
type Intfc interface {
    // 连接到数据库,如果连接时发生错误,返回错误
    Connect() error
    // 添加数据,返回一个字符串,如果发生错误,返回错误
    Add(string, string, string) (string, error)
    // 根据参数获取数据,返回一个对象和一个布尔值
    Get(string) (Object, bool)
}

MySQL:

type Mysql struct{
    conn *sql.DB // 数据库连接
}

// 连接到 MySQL
func (f *Mysql) Connect() error {
    conn, err := sql.Open("mysql", "yourusername:yourpassword@/yourdatabase")
    if err != nil {
        return err
    }
    f.conn = conn
    return nil
}

// 向数据库添加数据
func (f *Mysql) Add(owner string, refType string, object string) (string, error) {
    // 做一些操作
    return // 返回一个字符串和错误
}

func (f *Mysql) Get(Uuid string) (dbinit.Object, bool) {
    // 做一些操作
    return // 返回一个对象和一个布尔值
}

Mongo:

type Mongo struct{
    session *mgo.Session
}

// 连接到 MongoDB
func (f *Mongo) Connect() error {
    info := &mgo.DialInfo{
        // 一些数据
    }

    session, err := mgo.DialWithInfo(info)
    if err != nil {
        return err
    }
    f.session = session
    return nil
}

// 向数据库添加数据
func (f *Mongo) Add(owner string, refType string, object string) (string, error) {
    // 做一些操作
    return // 返回一个字符串和错误(成功时可能为 nil)
}

func (f *Mongo) Get(Uuid string) (dbinit.Object, bool) {
    // 做一些操作
    return // 返回一个对象和一个布尔值
}

主程序:

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

// 每次调用 API 时都会运行这段代码
api.HandlerFoobar = foobar.handlerFunction(func(params foobar.Params) middleware.Responder {
    data, err := selectedDb.Add(addStringA, addStringB, addStringC)
    if err != nil {
        // 做一些操作
    }

    return // API 响应
})

但你也可以从 Intfc 接口中移除 Connect() error 方法,只使用 AddGet 方法,但你需要更新包中的代码:

MySQL:

// 连接到 MySQL,可以使用任何函数名
func Connect() (*Mysql, error) {
    connection, err := sql.Open("mysql", "yourusername:yourpassword@/yourdatabase")
    if err != nil {
        return nil, err
    }
    return &Mysql{conn: connection}, nil
}

Mongo:

// 连接到 MongoDB,可以使用任何函数名
func Connect() (*Mongo, error) {
    info := &mgo.DialInfo{
        // 一些数据
    }

    s, err := mgo.DialWithInfo(info)
    if err != nil {
        return nil, err
    }
    return &Mongo{session: s}, nil
}

主程序:

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)
}
英文:

If you want to preserve the interface with those methods, you should change a little your interface as:

Interface:

<!-- language: go -->

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

<!-- language: go -->

type Mysql struct{
    conn *sql.DB // contains the connection to the DB
}

// Connect to mysql
func (f *Mysql) Connect() error {
    conn, err := sql.Open(&quot;mysql&quot;, &quot;yourusername:yourpassword@/yourdatabase&quot;)
    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:

<!--language: go -->

type Mongo struct{
    session *mgo.Session
}

// Connect to mongo
func (f *Mongo) Connect() error {
    info := &amp;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:

<!-- language: go -->

var selectedDb dbinit.Intfc

commandLineInput := &quot;mysql&quot;

if commandLineInput == &quot;mysql&quot; {
    selectedDb = &amp;mysqlConnector.Mysql{}
} else if commandLineInput == &quot;mongo&quot; {
    selectedDb = &amp;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

<!-- language: go -->

// Connect to mysql, it could be any function name
func Connect() (*Mysql, error) {
    connection, err := sql.Open(&quot;mysql&quot;, &quot;yourusername:yourpassword@/yourdatabase&quot;)
    if err != nil {
        return nil, error
    }
    return &amp;Mysql{conn: connection}
}

Mongo

<!-- language: go -->

// Connect to mongo, it could be any function name
func Connect() (*Mongo, error) {
    info := &amp;mgo.DialInfo{
        // some data
    }

    s, err := mgo.DialWithInfo(info)
    if err != nil {
        return nil, error
    }
    return &amp;Mongo{session: s}
}

Main

<!-- language: go -->

var selectedDb dbinit.Intfc
var err error
commandLineInput := &quot;mysql&quot;

if commandLineInput == &quot;mysql&quot; {
    selectedDb, err = mysqlConnector.Connect()
} else if commandLineInput == &quot;mongo&quot; {
    selectedDb, err = mongoConnector.Connect()
}

if err != nil {
    panic(err)
}

答案2

得分: 1

详细说明一下我的评论,而不是

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: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(...) {
    // ...
}
英文:

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(&quot;mysql&quot;, &quot;yourusername:yourpassword@/yourdatabase&quot;)
    if err != nil {
        panic(err.Error())    
    }

    return MySQLClient(client)
}

func (f Mongo) Connect() DBClient {

    info := &amp;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

得分: 1

我认为,接口Intfc(或更好的名称DbIntfc)应该只有Get和Add两个方法。

而且应该存在另一个函数 - 但不是DbIntfc的一部分,它返回连接到MySql或MongoDb的DbIntfc。让我们看一下:

type MySqlDbIntfc struct{
   db *Sql.DB
}

// 连接到mysql
func NewMySqlDbIntfc() (DbIntfc,error) {
    // 请不要在这样的抽象方法中使用panic
    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
}

实现NewMgoDbIntfc应该很简单,包括NewMgoDbIntfc.Add/Get方法。

而且决定是使用NewMySqlDbIntfc还是NewMgoDbIntfc也应该很容易。

英文:

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(&quot;mysql&quot;, &quot;yourusername:yourpassword@/yourdatabase&quot;)
        if err != nil {
            return nil, err
        }
        return &amp;MySqlDbIntfc{client}, nil
    }

    func (mySqlDb *MySqlDbIntfc) Get(Uuid string) (dbinit.Object, error) {
      var obj dbinit.Object
      err := mySqlDb.db.QueryRow(&quot;SELECT uuid, object, deleted FROM myTable WHERE uuid=?&quot;, Uuid).Scan(&amp;obj.Uuid, &amp;obj.Object, &amp;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.

答案4

得分: 0

> 1. 在代码中的HERE_LIES_MY_PROBLEM的位置应该填写什么?

根据源代码DialWithInfo()函数返回error*Session,它们都是来自mgo包的结构体。所以你可以将HERE_LIES_MY_PROBLEM替换为*mgo.Session

> 2. 我对处理这些事情的Go范式理解错了吗?

关于连接多个数据库的惯用方法,我认为有很多不同的观点。以下是我对连接Mongo和Redis的一些想法:

var logger *log.Logger

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

// 在这里创建不同类型的数据库连接。
func SystemConnection() map[string]interface{} {
	listConnection := make(map[string]interface{})
	var err error
	// 创建Redis连接
	redisConn := RedisHost{
		Address:  "localhost:6379",
		Password: "",
		DB:       0,
	}

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

	// 创建MongoDB连接
	mongo := MongoHost{
		Host: "localhost",
		Port: "27017",
	}
	mongoConnection := mongo.Connect()

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

func GetMongo() *mgo.Session {
	// 创建MongoDB连接
	mongo := MongoHost{
		Host: "localhost",
		Port: "27017",
	}
	mongoConnection := mongo.Connect()

	return mongoConnection
}

你可以从这里查看源代码。

要使用上述代码,在你的主程序的init()函数中调用SystemConnection(),像这样:

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

	// 获取Redis连接,将其转换为*redis.Client类型。
	redisConn := listConnection["redis"].(*redis.Client)

	// 获取MongoDB连接。
	mongoConn := listConnection["mongodb"].(*mgo.Session)
}

再次说明,这种方法是我个人的观点,可能还有其他适合你的方法。

英文:

> 1. What should be in the places where I have HERE_LIES_MY_PROBLEM in the code?

As you can see from the source that the DialWithInfo() return error and *Session from mgo package and that is a struct. so you can replace your HERE_LIES_MY_PROBLEM with *mgo.Session

> 2. do I get the Go paradigm wrong for dealing with these things?

As far as the idiomatic to connect with multiple databases there are many opinion I think. And here is some of my thought to connect with mongo and redis :

var logger *log.Logger

func init() {
	logger = log.New(os.Stderr,
		&quot;Database :: &quot;,
		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:  &quot;localhost:6379&quot;,
		Password: &quot;&quot;,
		DB:       0,
	}

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

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

	listConnection[&quot;redis&quot;] = redisConnection
	listConnection[&quot;mongodb&quot;] = mongoConnection
	return listConnection
}

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

	return mongoConnection
}

you can see the source from here

To use above code you can call the SystemConnection() on your init() in your main program. like this :

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

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

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

}

Again this approach is my opinion there are others that might be suit with yours.

huangapple
  • 本文由 发表于 2017年5月3日 17:17:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/43755892.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定