I am developing an application where I am using MongoDB as database with Nodejs + Express in application layer, I have two collections, namely
Here i have to update wallet of thousands of users with some amount and if successful create a new document with related info for each transaction, This is My code :
userModel.update({_id : ObjectId(userId)}, {$inc : {wallet : 500}}, function (err, creditInfo) { if(err){ console.log(err); } if(creditInfo.nModified > 0) { newTransModel = new transModel({ usersId: ObjectId(userId), amount: winAmt, type: 'credit', }); newTransModel.save(function (err, doc) { if(err){ Cb(err); } }); } });
but this solution is not atomic
there is always a possibility of user wallet updated with amount but related transaction not created in transactions collection resulting in financial loss.
I have heard that recently MongoDB
has added Transactions
support in its 4.0 version
, I have read the MongoDB docs but couldn't get it to successfully implement it with mongoose in Node.js, can anyone tell me how this above code be reimplemented using the latest Transactions
feature of MongoDB which have these functions
Session.startTransaction() Session.abortTransaction() Session.commitTransaction()
MongoDB Docs : Click Here
yes you should, its a good practice. Mongoose requires a connection to a MongoDB database. You can use require() and connect to a locally hosted database with mongoose.
Transactions in Mongoose. Transactions are new in MongoDB 4.0 and Mongoose 5.2. 0. Transactions let you execute multiple operations in isolation and potentially undo all the operations if one of them fails.
MongoDB added support for multi-document ACID transactions in version 4.0, and MongoDB expanded that support to include distributed transactions in version 4.2. You can implement a transaction in a variety of programming languages using any of MongoDB's official drivers.
For situations that require atomicity of reads and writes to multiple documents (in a single or multiple collections), MongoDB supports multi-document transactions. With distributed transactions, transactions can be used across multiple operations, collections, databases, documents, and shards.
with mongoose in Node.js, can anyone tell me how this above code be reimplemented using the latest Transactions feature
To use MongoDB multi-documents transactions support in mongoose you need version greater than v5.2. For example:
npm install [email protected]
Mongoose transactional methods returns a promise rather than a session which would require to use await
. See:
For example, altering the example on the resource above and your example, you can try:
const User = mongoose.model('Users', new mongoose.Schema({ userId: String, wallet: Number })); const Transaction = mongoose.model('Transactions', new mongoose.Schema({ userId: ObjectId, amount: Number, type: String })); await updateWallet(userId, 500); async function updateWallet(userId, amount) { const session = await User.startSession(); session.startTransaction(); try { const opts = { session }; const A = await User.findOneAndUpdate( { _id: userId }, { $inc: { wallet: amount } }, opts); const B = await Transaction( { usersId: userId, amount: amount, type: "credit" }) .save(opts); await session.commitTransaction(); session.endSession(); return true; } catch (error) { // If an error occurred, abort the whole transaction and // undo any changes that might have happened await session.abortTransaction(); session.endSession(); throw error; } }
is not atomic there is always a possibility of user wallet updated with amount but related transaction not created in transactions collection resulting in financial loss
You should also consider changing your MongoDB data models. Especially if the two collections are naturally linked. See also Model data for Atomic Operations for more information.
An example model that you could try is Event Sourcing model. Create a transaction entry first as an event, then recalculate the user's wallet balance using aggregation.
For example:
{tranId: 1001, fromUser:800, toUser:99, amount:300, time: Date(..)} {tranId: 1002, fromUser:77, toUser:99, amount:100, time: Date(..)}
Then introduce a process to calculate the amount for each users per period as a cache depending on requirements (i.e. per 6 hours). You can display the current user's wallet balance by adding:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With