Nested mysql transactions between two tables using secelize.js file

I have 2 sequelize models (Event and Inventory associated with 2 tables). I created an Event._create method to use it to create an event on a db event with multiple products recorded in the db inventory at the same time. Each inventory is associated with the event_id of the newly created event.

Because all of these things have to succeed or fail altogether, I use the seelize transaction to achieve this.

At first I thought about doing something like this.

sequelize.transactionPromise = Promise.promisify(sequelize.transaction, sequelize);

return sequelize.transactionPromise({autocommit: 0})
.then(function(t) {
    return Event.create(ev, {transaction: t})
    .then(function(event){
        var event_id = event.id;  // ------ (*)
        return Promise.resolve([1, ..., event_number])
        .then(function(){
            Inventory.create({product_id: some_product_id, event_id: event_id}, 
            {transaction: t});
        });
    .then(function(){ 
        return Promise.cast(t.commit())
        .then(function() { // successfully committed  
            return res.json(d);
        }).catch(function(err){ // cannot commit somehow
            return res.json(500, err.toString());
        });
    }).catch(function(err){  // error rollback
        return Promise.cast(t.rollback())
        .then(function() {
            return Promise.reject('rollback: ' + err.toString());
        });
    });
});

      

But that doesn't work because before the transaction the transaction (*) is irrelevant and gives me an event_id of NULL.

Instead, I do something like below:

var Event = sequelize.model('Event'); 
var Inventory = sequelize.model('Inventory');

var _create = function(t, ev){
    var ev_id_secret = {secret: 'some random secret'};
    return Promise.cast(Event.create(ev_id_secret))
    .then(function(d){
        ev_id_secret.id = d.id;
        return true;
    }).then(function(){
        return Promise.resolve(_.range(ev.number_of_products))
        .map(function(){
            var inventory = {
                event_id: ev_id_secret.id,
                product_id: ev.product_id
            };
            return Promise.cast(Inventory._create(t, inventory));
        });
    }).then(function(){  // thennable a transaction
            return Promise.cast(Event.update(ev, ev_id_secret, {transaction: t}));
    });
};

      

So, I can do something like this.

sequelize.transactionPromise=Promise.promisify(sequelize.transaction, sequelize); 
return sequelize.transactionPromise({autocommit: 0})
.then(function(t) {
    return Event._create(t, ev)
    .then(function(){
        return Promise.cast(t.commit())
        .then(function() {
            return res.json(d);
        }).catch(function(err){
            return res.json(500, err.toString());
        });
    }).catch(function(err){
        return Promise.cast(t.rollback())
        .then(function() {
            return Promise.reject('rollback: ' + err.toString());
        });
    });
}).catch(function(err){
    console.log(err.stack);
    res.json(500, {error: err.toString()});
});

      

What I do with _create is that I just insert an empty event (with a randomly generated secret) in the Event db and some empty products in the Inventory db and then get the event_id using that secret to query, updating the event and inventory accordingly ...

The point is that when a promise is rejected, a transactional rollback () is called and empty records of events and products are left in the db. So I have to deal with empty entries later, which is really worrying.

So my question is how to make a transaction between two tables correctly? Am I on the right track?

ps: as a side question, you can see my code is filled with Promise.xxx statements, this provides control flow, but the promise gets really messy. Is there something I can do to improve my code? Thank.

+3


source to share


2 answers


Your initial implementation was correct. However, after the event is raised, the object should return with the ID already populated without the need for committing (I assume you are using the standard auto-incrementing secelize IDs). All SQL databases function this way. I recommend debugging your application to determine the root of the problem.

Here's an example of implementing a secelize transaction from one of my applications. I've just tested and verified that calling db.Deposit.create () resolves an object with a valid id. I am using Bluebird, Sequelize 2.0.0-rc2 and Postgresql 9.3



module.exports.createDeposit = function(deposit) {
  return db.sequelize.transaction({isolationLevel: 'READ COMMITTED' })
  .then(function(t){
    var strQuery = 'UPDATE "Accounts" '
                   + 'SET "balance"="balance" + :amount, "updatedAt"=NOW() '
                   + 'WHERE "id"= :AccountId RETURNING *';

    return sql.query(strQuery,
                     db.Account.build(),
                     { raw: true, transaction : t },
                     { AccountId: deposit.AccountId, amount: deposit.amount })

    .then(function(account){
      if (!account) throw new Error('Account does not exist')
      return db.Deposit.create(deposit, { transaction: t });
    })
    .then(function(dbDeposit){
      // If successful, dbDeposit object contains a valid id
      if (!dbDeposit) throw new Error('Failed to create deposit');
      return t.commit()
      .then(function(){
        return dbDeposit;
      });
    })
    .catch(function(e){
      return t.rollback()
      .then(function(){
        throw e;
      });
    });
  });
};

      

+2


source


Your initial implementation was correct as far as I can tell, but it can be improved by using managed transactions and full promise support in recent versions of Sequelize:

return sequelize.transaction({autocommit: false}, function(t) {
  return Event.create(ev, {transaction: t}).then(function(event) {
    return Promise.map(_.range(event_number), function (number) {
      return Inventory.create({product_id: some_product_id, event_id: event.get('id')}, {transaction: t});
    });
  });
}).then(function(){
  // Automatically comitted at this point if the promise chain returned to the transaction successfully resolved
  return res.status(200).json(d);
}).catch(function(err){
  // Automatically rolledback at this point if the promise chain returned to the transaction was rejected
  return res.status(403).json();
});

      



You can read more about managed transactions in the docs: http://sequelize.readthedocs.org/en/latest/docs/transactions/

+2


source







All Articles