1
votes

I am trying to re-learn NodeJS after a couple years of putting it down, so I'm building a small banking website as a test. I decided to use Sequelize for my ORM, but I'm having a bit of trouble sending money between people in a way that I like. Here was my first attempt:

// myUsername - who to take the money from
// sendUsername - who to send the money to
// money - amount of money to be sent from `myUsername`->`sendUsername`
// Transaction is created to keep a log of banking transactions for record-keeping.
module.exports = (myUsername, sendUsername, money, done) => {
    // Create transaction so that errors will roll back
    connection.transaction(t => {
        return Promise.all([
            User.increment('balance', {
                by: money,
                where: { username: myUsername },
                transaction: t
            }),
            User.increment('balance', {
                by: -money,
                where: { username: sendUsername },
                transaction: t
            }),
            Transaction.create({
                fromUser: myUsername,
                toUser: sendUsername,
                value: money
            }, { transaction: t })
        ]);
    }).then(result => {
        return done(null);
    }).catch(err => {
        return done(err);
    });
};

This worked, but it didn't validate the model when it was incremented. I'd like for the transaction to fail when the model does not validate. My next attempt was to go to callbacks, shown here (same function header):

connection.transaction(t => {
        // Find the user to take money from
        return User
        .findOne({ where: { username: myUsername } }, { transaction: t })             .then(myUser => {
                    // Decrement money
                    return myUser
                    .decrement('balance', { by: money, transaction: t })
                    .then(myUser => {
                            // Reload model to validate data
                            return myUser.reload(myUser => {
                                    // Validate modified model
                                    return myUser.validate(() => {
                                            // Find user to give money to
                                            return User
                                            .findOne({ where: { username: sendUsername } }, { transaction: t })
                                            .then(sendUser => {
                                                    // Increment balance
                                                    return sendUser
                                                    .increment('balance', { by: money, transaction: t })
                                                    .then(sendUser => {
                                                            // Reload model
                                                            return sendUser.reload(sendUser => {
                                                                    // Validate model
                                                                    return sendUser.validate(() => {
                                                                            // Create a transaction for record-keeping
                                                                            return Transaction
                                                                            .create({
                                                                                    fromUser: myUser.id,
                                                                                    toUser: sendUser.id,
                                                                                    value: money
                                                                            }, { transaction: t });
                                                                    });
                                                            });
                                                    });
                                            });
                                    });
                            });
                    });
            });
    }).then(result => {
            return done(null);
    }).catch(err => {
            return done(err);
    });

This works, in that money is still transfered beetween people, but it still doesn't validate the models. I think the reason is that the .validate() and the .reload() methods do not have the ability to add the transaction: t parameter on it. My question is if there's a way to do validation in a transaction, but I'd also like some help fixing this "callback hell." Again, I haven't done JS in a while, so there are probably better ways of doing this now that I'm just now aware of.

Thanks!

1

1 Answers

3
votes

I believe you can't get validations to fire on the Model's increment and decrement and need to have instances. In some sequelize Model methods you can configure validations to run, but it doesn't look like it here

I'd do it like this

module.exports = async function(myUserId, sendUserId, money) {
  const transaction = await connection.transaction();
  try {
    const [myUser, sendUser] = await Promise.all([
      User.findById(myUserId, { transaction }),
      User.findById(sendUserId, { transaction })
    ]);
    await Promise.all([
      myUser.increment('balance', {
        by: money,
        transaction
      }),
      myUser.increment('balance', {
        by: -money,
        transaction
      })
    ]);
    await Transaction.create({...}, { transaction })
    await transaction.commit();
  } catch(e) {
    await transaction.rollback();
    throw e;
  }
}