英文:
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不同。
- 在代码中的
HERE_LIES_MY_PROBLEM
位置应该填写什么? - 或者,我是否对处理这些事情的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.
- What should be in the places where I have
HERE_LIES_MY_PROBLEM
in the code? - 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
方法,只使用 Add
和 Get
方法,但你需要更新包中的代码:
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("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:
<!--language: go -->
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:
<!-- language: go -->
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
<!-- language: go -->
// 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
<!-- language: go -->
// 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
<!-- language: go -->
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
得分: 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("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(...) {
// ...
}
答案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("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.
答案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,
"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
}
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["redis"].(*redis.Client)
// get postgre connection.
mongoConn := listConnection["mongodb"].(*mgo.Session)
}
Again this approach is my opinion there are others that might be suit with yours.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论