Bookshelf.js locked transactions
Is it possible to create atomic database transactions with bookshelf? I have a problem with duplicates in the database. The problematic code looks like this:
bookshelf.transaction(function (t) {
var modelLocation = new Models.Location({'name':event.venue});
modelLocation.fetch({transacting:t})
.then(function (fetchedLocation) {
if (!fetchedLocation) {
modelLocation.save(null,{transacting:t}).then(function (savedModel) {
t.commit(savedModel)
}).catch(function (err) {
t.rollback(err)
});
}
else{
t.commit(fetchedLocation)
}
})
})
I am calling the method containing this code almost synchronously and asynchronously 20 times. Of these 20, there are 5 duplicate datasets. This results in about 2-3 duplicates in the database. The current workaround is to wrap the whole thing in a setTimeout with a random interval between 0 and 10 seconds, which almost never gives me duplicates. But this is clearly not a production-ready solution.
source to share
OK, so in the end I decided to go with the async.js library and queue it up. The queue ensures that a maximum of n asynchronous tasks are running concurrently. In this case 1. I created a module that exports the queue instance. This way I can use it for multiple modules. He's just waiting for the promise to fulfill.
var async = require('async');
module.exports = async.queue(function (task, callback) {
task().then(function () {
callback();
});
},1);
Then in the module where I need an atomic transaction, I have the following code:
var queue = require('./transactionQueue');
...
...
queue.push(function(){
return bookshelf.transaction(function (t) {
var modelLocation = new Models.Location({'name':event.venue});
return modelLocation
.fetch({transacting:t})
.then(function (fetchedLocation) {
if (!fetchedLocation) {
return modelLocation
.save(null,{transacting:t});
}
});
});
});
It's important to move the transaction into a function so that it doesn't get executed right away.
source to share
Since Bookshelf transactions are promises, you don't need to explicitly call commit()
or rollback()
. Just let a fulfilled promise make a commitment, or you can force a rollback by throwing an exception.
There was apparently a small bug in your code that could cause problems: the argument is missing in fetch()
then()
- this argument is the result of a call fetch()
, an instance, if the object was found or null
if not.
bookshelf.transaction(function (t) {
var modelLocation = new Models.Location({'name':event.venue});
return modelLocation
.fetch()
.then(function (fetchedLocation) {
if (!fetchedLocation) {
modelLocation
.save(null,{transacting:t});
}
})l
});
I can't test it now, but hope it helps.
source to share