我在Node.js上运行Express,我想知道如何在不同的Node模块之间有效地传递单个数据库连接上下文对象(想想它们有点像应用程序模型)。
我希望能够在一个模型中启动数据库事务,并在单个HTTP请求的持续时间内将其保留在对其他受影响模型的调用中。
我已经看到人们尝试使用在我的路由运行之前作为中间件公开的每个请求数据库连接来解决这个问题(从连接池获取,然后在路由之后运行另一个中间件以将连接返回到池)。遗憾的是,这意味着将上下文对象明确地传递给所有受影响的函数,这些函数不够优雅且笨重。
我也看到人们在谈论continuation-local-storage和AsyncWrap模块,但我不清楚他们如何解决我的特定问题。我尝试简单地使用continuation-local-storage,但因为我主要在我的代码中使用promises和generator,所以它无法从run
方法返回状态(它只返回{{1}对象传递给它的回调)。
以下是我尝试做的一个例子:
context
这里有几点说明:
// player-routes.js
router.post('/items/upgrade', wrap(function *(req, res) {
const result = yield playerItem.upgrade(req.body.itemId);
res.json();
});
// player-item.js
const playerItem = {
upgrade: Promise.coroutine(function *(user, itemId) {
return db.withTransaction(function *(conn) {
yield db.queryAsync('UPDATE player_items SET level = level + 1 WHERE id = ?', [itemId]);
yield player.update(user);
return true;
});
})
};
module.exports = playerItem;
// player.js
const player = {
update(user) {
return db.queryAsync('UPDATE players SET last_updated_at = NOW() WHERE id = ?', [user.id]);
})
};
module.exports = player;
// db.js
db.withTransaction = function(genFn) {
return Promise.using(getTransactionConnection(), conn => {
return conn.beginTransactionAsync().then(() => {
const cr = Promise.coroutine(genFn);
return Promise
.try(() => cr(conn))
.then(res => {
return conn.commitAsync().thenReturn(res);
}, err => {
return conn.rollbackAsync()
.then(() => logger.info('Transaction successfully rolled back'))
.catch(e => logger.error(e))
.throw(err);
});
});
});
};
函数只是一小部分包装中间件,允许我在路由中使用generator / yield。wrap
模块也只是流行的db
模块的一个小包装器,已被宣传。我想做的事情,可能在mysql
,检查是否在当前上下文中设置了db.queryAsync
个对象(我设置在conn
呼叫return Promise...
。如果是这样,请使用该连接执行所有后续数据库调用,直到上下文超出范围。
不幸的是,将db.withTransaction
调用包装在CLS命名空间代码中并不允许我实际返回承诺 - 它只返回return Promise...
对象,这在我的情况下是不正确的。看起来CLS的大多数用法都依赖于实际上没有从context
回调中返回任何内容。我也看了cls-bluebird,但这似乎并没有做我需要做的事情。
有什么想法吗?我觉得我很接近,但并不是所有人都知道我需要它。