英文:
Managing connections per request in Go
问题
假设来说,每次请求都连接到数据库并在请求完成后关闭,这样做是否是一个好的实践?
我正在使用mongodb
和mgo
来处理数据库。
在我的项目中,我希望通过从请求头中获取数据库名称来连接到特定的数据库(当然,这需要与身份验证机制结合,比如我的应用中使用的JWT)。流程大致如下:
-
用户身份验证:
发送 POST 请求到 http://api.app.com/authenticate
// 这个请求会在一个“全局”数据库中检查用户,
// 对其进行身份验证,并返回一个签名的JWT令牌
// 该令牌存储在bolt.db中,用于身份验证机制 -
一些 RESTful 操作:
发送 POST 请求到 http://api.app.com/v1/blog/posts
// 每个对 /v1* 的请求都会设置JWT中间件
// 请求头中的Client-Domain
设置为数据库的名称,例如 'app-com'
// 因此,我们会连接到该数据库,并在请求完成后关闭连接
所以我的问题是:
- 这种做法可行吗?- 我已经了解了连接池和重用连接的概念,但还没有深入研究过
- 是否有更好的方法来实现所需的功能?
- 如何确保会话仅在请求完成后关闭?
我之所以需要这样做,是因为我们有多个供应商,他们拥有相同的数据库集合,但具有不同的条目,并且只能访问自己的数据库。
更新/解决方案
最终,我使用了Go
内置的Context
,通过复制会话并在需要进行任何CRUD操作的任何地方使用它。
类似于:
func main() {
...
// 配置连接并设置为全局变量
model.DBSession, err = mgo.DialWithInfo(mongoDBDialInfo)
defer model.DBSession.Close()
...
n := negroni.Classic()
n.Use(negroni.HandlerFunc(Middleware))
...
}
func Middleware(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
...
db := NewDataStore(clientDomain)
// db.Close() 是 ds.session.Close() 的别名,此处未包含该函数的代码
// 我仍在对此进行实验,我需要确保会话仅在请求完成后关闭,目前并不总是如此
defer db.Close()
ctx := req.Context()
ctx = context.WithValue(ctx, auth.DataStore, db)
req = req.WithContext(ctx)
...
}
func NewDataStore(db string) *DataStore {
store := &DataStore{
db: DBSession.Copy().DB(db),
session: DBSession.Copy(),
}
return store
}
然后在 HandlerFunc 中使用它,例如 /v1/system/users
:
func getUsers(res http.ResponseWriter, req *http.Request) {
db := req.Context().Value(auth.DataStore).(*model.DataStore)
users := make([]SystemUser{}, 0)
// db.C() 是 ds.db.C() 的别名,此处未包含该函数的代码
db.C("system_users").Find(nil).All(&users)
}
与我最初尝试的方法相比,响应时间减少了40%。
英文:
Hypothetically speaking, is it good practice to connect to a database for each request and close in when the request has completed?
I'm using mongodb
with mgo
for the database.
In my project, I would like to connect to a certain database by getting the database name from the request header (of course, this is combined with an authentication mechanism, e.g. JWT in my app). The flow goes something like:
-
User authentication:
POST to http://api.app.com/authenticate // which checks the user in a "global" database, // authenticates them and returns a signed JWT token // The token is stored in bolt.db for the authentication mechanism
-
Some RESTful operations
POST to http://api.app.com/v1/blog/posts // JWT middleware for each request to /v1* is set up // `Client-Domain` in header is set to a database's name, e.g 'app-com' // so we open a connection to that database and close when // request finishes
So my questions are:
- Is this feasible? - I've read about connection pools and reusing them but I haven't read much about them yet
- Is there a better way of achieving the desired functionality?
- How do I ensure the session is only closed when the request has completed?
The reason why I need to do this is because we have multiple vendors that have the same database collections with different entries with restricted access to their own databases.
Update / Solution
I ended up using Go
's built in Context
by Copying a session and using it anywhere I need to do any CRUD ops
Something like:
func main() {
...
// Configure connection and set in global var
model.DBSession, err = mgo.DialWithInfo(mongoDBDialInfo)
defer model.DBSession.Close()
...
n := negroni.Classic()
n.Use(negroni.HandlerFunc(Middleware))
...
}
func Middleware(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
...
db := NewDataStore(clientDomain)
// db.Close() is an alias for ds.session.Close(), code for this function is not included in this post
// Im still experimenting with this, I need to make sure the session is only closed after a request has completed, currently it does not always do so
defer db.Close()
ctx := req.Context()
ctx = context.WithValue(ctx, auth.DataStore, db)
req = req.WithContext(ctx)
...
}
func NewDataStore(db string) *DataStore {
store := &DataStore{
db: DBSession.Copy().DB(db),
session: DBSession.Copy(),
}
return store
}
And then use it in a HandlerFunc, example /v1/system/users
:
func getUsers(res http.ResponseWriter, req *http.Request) {
db := req.Context().Value(auth.DataStore).(*model.DataStore)
users := make([]SystemUser{}, 0)
// db.C() is an alias for ds.db.C(), code for this function is not included in this post
db.C("system_users").Find(nil).All(&users)
}
40% response time decrease over the original method I experimented with.
答案1
得分: 0
假设来说,这不是一个好的做法,因为:
- 数据库逻辑分散在几个包中。
- 很难进行测试。
- 无法应用 DI(主要是代码维护困难)。
回答你的问题:
- 是可行的,但你将不会在 go 包中使用连接池(如果你想了解有关连接池的更多信息,请查看这里的代码)。
- 更好的方法是创建一个全局变量,其中包含数据库连接,并在应用程序即将停止时关闭它(而不是在每个请求中关闭连接)。
- 如何确保会话仅在请求完成后关闭<-你应该检查数据库查询的结果,然后关闭连接(但我不建议在每个请求后关闭连接,因为你需要再次打开连接进行另一个请求,然后再次关闭等等)。
英文:
Hypothetically speaking is not a good practice because:
- The database logic is scattered among several packages.
- It's difficult to test
- You can't apply DI (mainly it will be hard to maintain the code)
Replying to your questions:
- Yes is feasible BUT you will not use the connection pool inside them go package (take a look to the code here if you want know more about Connection Pool)
- A better way is to create a global variable that contains the database connection and close when the application is going to stop (and not close the connection every request)
- How do I ensure the session is only closed when the request has complete<- you should checkout the answer fro your db query and then close the connection (but I don't recommend to close the connection after a request because you'll need to open again for another request and close again etc...)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论