Collections Missing with Fawn Transactions












0














In index.js, i create my database correctly, and i add a genre collection in the db, and is added fine.



However, when i add my rental collection, it isn't added or viewed in mongodb compass



My code for rental.js:



const mongoose = require('mongoose')
const joi = require('joi')

const rentalSchema = new mongoose.Schema({
customer: {
type: new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 2,
maxlength: 255
},
phone: {
type: String,
required: true,
minlength: 2,
maxlength: 255
},
isGold: {
type: Boolean,
default: false,
required: false
},
}),
movie: {
type: new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
minlength: 2,
maxlength: 500
},
dailyRentalRate: {
type: Number,
min: 2,
required: true,
max: 255
}
}),
},
dateOut: {
type: Date,
required: true,
default: Date.now
},
dateReturned: {
type: Date
},
rentalFee: {
type: Number,
min: 0
}
}
})

const Rental = mongoose.model('Rental', rentalSchema)

function validate(obj) {
const schema = {
customerId: joi.string().required(),
movieId: joi.string().required()
}

return joi.validate(obj, schema)
}

exports.Rental = Rental
exports.validate = validate


My index.js Code (Where i initialise the database):



const mongoose = require('mongoose')
const movies = require('./routes/movies')
const rentals = require('./routes/rentals')

mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true})
.then(() => console.log('Connected to mongodb..'))
.catch(() => console.error('Error connecting...'))


This is unusual, as i do the same thing for genre, but it is added and viewed in mongodb compass!



[The image of mongo db compass is here: ]



Here is my rentals.js file, that uses rental.js for models:



const express = require('express')
const router = express.Router()
const {Customer} = require('../models/customer')
const Fawn = require('fawn')
const mongoose = require('mongoose')
const {Movie} = require('../models/movie')
const {Rental, validate} = require('../models/rental')

Fawn.init(mongoose)

router.get('/rentals', async (req, res) => {
const rentals = await Rental.find().sort('-dateOut')
res.send (rentals)
})


router.post('/rentals', async (req, res) => {
const {error} = validate(req.body)
if (error) return res.status(400).send('Error')

// Makes sure the customerId/customer sends us is valid
const customer = await Customer.findById(req.body.customerId)
if (!customer) return res.status(404).send('Invalid customerId')

const movie = await Movie.findById(req.body.movieId)
if (!movie) return res.status(404).send('Invalid movieId')

let rental = new Rental({
customer: {
_id: customer._id,
name: customer.name,
phone: customer.phone
},
movie: {
_id: movie._id,
title: movie.title,
dailyRentalRate: movie.dailyRentalRate
}
})

// This is for our success scenario
try {
// All args in here treated all together as unit
new Fawn.Task()
// First arg is collection we work with, and second is obj we wanna save
.save('rentals', rental)
// Update movies collection Second Arg is movie that should be updated Third is we increment the numInstock prop, and decrement by 1
.update('movies', { _id: movie._id}, {
$inc: { numberInStock: -1}
})
.run()
res.send(rental)
}
catch(ex) {
// 500 means Internal server error
res.status(500).send('Something failed.')
}
})

module.exports = router


Here is mongodb compass, and the collections seen










share|improve this question
























  • mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true}) is invalid and would throw an error. You need to include the port with the useNewUrlParser option. mongoose.connect('mongodb://localhost:27017/vidly', { useNewUrlParser: true}). Right now these are "warnings", but expect that to become an actual error in the future.
    – Neil Lunn
    Nov 22 at 21:21












  • Aside from that, you are likely not looking at your vidly database when in compass, or of course you are looking in rental when mongoose is actually using rentals. Which is going to be the presumed problem unless you can show otherwise.
    – Neil Lunn
    Nov 22 at 21:24










  • It Still hasn't changed anything, as i tried adding port plus deleting the line completely. Same result though
    – Eesa Munir
    Nov 22 at 21:24










  • I am 100% looking at the vidly db, but only one collection shows up which isn't the rental one but appears to have the exact same code.
    – Eesa Munir
    Nov 22 at 21:25










  • Sorry lol, i have renamed all my models to unique ones, no errors, however still not appearing in mongodb compass
    – Eesa Munir
    Nov 22 at 21:30
















0














In index.js, i create my database correctly, and i add a genre collection in the db, and is added fine.



However, when i add my rental collection, it isn't added or viewed in mongodb compass



My code for rental.js:



const mongoose = require('mongoose')
const joi = require('joi')

const rentalSchema = new mongoose.Schema({
customer: {
type: new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 2,
maxlength: 255
},
phone: {
type: String,
required: true,
minlength: 2,
maxlength: 255
},
isGold: {
type: Boolean,
default: false,
required: false
},
}),
movie: {
type: new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
minlength: 2,
maxlength: 500
},
dailyRentalRate: {
type: Number,
min: 2,
required: true,
max: 255
}
}),
},
dateOut: {
type: Date,
required: true,
default: Date.now
},
dateReturned: {
type: Date
},
rentalFee: {
type: Number,
min: 0
}
}
})

const Rental = mongoose.model('Rental', rentalSchema)

function validate(obj) {
const schema = {
customerId: joi.string().required(),
movieId: joi.string().required()
}

return joi.validate(obj, schema)
}

exports.Rental = Rental
exports.validate = validate


My index.js Code (Where i initialise the database):



const mongoose = require('mongoose')
const movies = require('./routes/movies')
const rentals = require('./routes/rentals')

mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true})
.then(() => console.log('Connected to mongodb..'))
.catch(() => console.error('Error connecting...'))


This is unusual, as i do the same thing for genre, but it is added and viewed in mongodb compass!



[The image of mongo db compass is here: ]



Here is my rentals.js file, that uses rental.js for models:



const express = require('express')
const router = express.Router()
const {Customer} = require('../models/customer')
const Fawn = require('fawn')
const mongoose = require('mongoose')
const {Movie} = require('../models/movie')
const {Rental, validate} = require('../models/rental')

Fawn.init(mongoose)

router.get('/rentals', async (req, res) => {
const rentals = await Rental.find().sort('-dateOut')
res.send (rentals)
})


router.post('/rentals', async (req, res) => {
const {error} = validate(req.body)
if (error) return res.status(400).send('Error')

// Makes sure the customerId/customer sends us is valid
const customer = await Customer.findById(req.body.customerId)
if (!customer) return res.status(404).send('Invalid customerId')

const movie = await Movie.findById(req.body.movieId)
if (!movie) return res.status(404).send('Invalid movieId')

let rental = new Rental({
customer: {
_id: customer._id,
name: customer.name,
phone: customer.phone
},
movie: {
_id: movie._id,
title: movie.title,
dailyRentalRate: movie.dailyRentalRate
}
})

// This is for our success scenario
try {
// All args in here treated all together as unit
new Fawn.Task()
// First arg is collection we work with, and second is obj we wanna save
.save('rentals', rental)
// Update movies collection Second Arg is movie that should be updated Third is we increment the numInstock prop, and decrement by 1
.update('movies', { _id: movie._id}, {
$inc: { numberInStock: -1}
})
.run()
res.send(rental)
}
catch(ex) {
// 500 means Internal server error
res.status(500).send('Something failed.')
}
})

module.exports = router


Here is mongodb compass, and the collections seen










share|improve this question
























  • mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true}) is invalid and would throw an error. You need to include the port with the useNewUrlParser option. mongoose.connect('mongodb://localhost:27017/vidly', { useNewUrlParser: true}). Right now these are "warnings", but expect that to become an actual error in the future.
    – Neil Lunn
    Nov 22 at 21:21












  • Aside from that, you are likely not looking at your vidly database when in compass, or of course you are looking in rental when mongoose is actually using rentals. Which is going to be the presumed problem unless you can show otherwise.
    – Neil Lunn
    Nov 22 at 21:24










  • It Still hasn't changed anything, as i tried adding port plus deleting the line completely. Same result though
    – Eesa Munir
    Nov 22 at 21:24










  • I am 100% looking at the vidly db, but only one collection shows up which isn't the rental one but appears to have the exact same code.
    – Eesa Munir
    Nov 22 at 21:25










  • Sorry lol, i have renamed all my models to unique ones, no errors, however still not appearing in mongodb compass
    – Eesa Munir
    Nov 22 at 21:30














0












0








0







In index.js, i create my database correctly, and i add a genre collection in the db, and is added fine.



However, when i add my rental collection, it isn't added or viewed in mongodb compass



My code for rental.js:



const mongoose = require('mongoose')
const joi = require('joi')

const rentalSchema = new mongoose.Schema({
customer: {
type: new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 2,
maxlength: 255
},
phone: {
type: String,
required: true,
minlength: 2,
maxlength: 255
},
isGold: {
type: Boolean,
default: false,
required: false
},
}),
movie: {
type: new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
minlength: 2,
maxlength: 500
},
dailyRentalRate: {
type: Number,
min: 2,
required: true,
max: 255
}
}),
},
dateOut: {
type: Date,
required: true,
default: Date.now
},
dateReturned: {
type: Date
},
rentalFee: {
type: Number,
min: 0
}
}
})

const Rental = mongoose.model('Rental', rentalSchema)

function validate(obj) {
const schema = {
customerId: joi.string().required(),
movieId: joi.string().required()
}

return joi.validate(obj, schema)
}

exports.Rental = Rental
exports.validate = validate


My index.js Code (Where i initialise the database):



const mongoose = require('mongoose')
const movies = require('./routes/movies')
const rentals = require('./routes/rentals')

mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true})
.then(() => console.log('Connected to mongodb..'))
.catch(() => console.error('Error connecting...'))


This is unusual, as i do the same thing for genre, but it is added and viewed in mongodb compass!



[The image of mongo db compass is here: ]



Here is my rentals.js file, that uses rental.js for models:



const express = require('express')
const router = express.Router()
const {Customer} = require('../models/customer')
const Fawn = require('fawn')
const mongoose = require('mongoose')
const {Movie} = require('../models/movie')
const {Rental, validate} = require('../models/rental')

Fawn.init(mongoose)

router.get('/rentals', async (req, res) => {
const rentals = await Rental.find().sort('-dateOut')
res.send (rentals)
})


router.post('/rentals', async (req, res) => {
const {error} = validate(req.body)
if (error) return res.status(400).send('Error')

// Makes sure the customerId/customer sends us is valid
const customer = await Customer.findById(req.body.customerId)
if (!customer) return res.status(404).send('Invalid customerId')

const movie = await Movie.findById(req.body.movieId)
if (!movie) return res.status(404).send('Invalid movieId')

let rental = new Rental({
customer: {
_id: customer._id,
name: customer.name,
phone: customer.phone
},
movie: {
_id: movie._id,
title: movie.title,
dailyRentalRate: movie.dailyRentalRate
}
})

// This is for our success scenario
try {
// All args in here treated all together as unit
new Fawn.Task()
// First arg is collection we work with, and second is obj we wanna save
.save('rentals', rental)
// Update movies collection Second Arg is movie that should be updated Third is we increment the numInstock prop, and decrement by 1
.update('movies', { _id: movie._id}, {
$inc: { numberInStock: -1}
})
.run()
res.send(rental)
}
catch(ex) {
// 500 means Internal server error
res.status(500).send('Something failed.')
}
})

module.exports = router


Here is mongodb compass, and the collections seen










share|improve this question















In index.js, i create my database correctly, and i add a genre collection in the db, and is added fine.



However, when i add my rental collection, it isn't added or viewed in mongodb compass



My code for rental.js:



const mongoose = require('mongoose')
const joi = require('joi')

const rentalSchema = new mongoose.Schema({
customer: {
type: new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 2,
maxlength: 255
},
phone: {
type: String,
required: true,
minlength: 2,
maxlength: 255
},
isGold: {
type: Boolean,
default: false,
required: false
},
}),
movie: {
type: new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
minlength: 2,
maxlength: 500
},
dailyRentalRate: {
type: Number,
min: 2,
required: true,
max: 255
}
}),
},
dateOut: {
type: Date,
required: true,
default: Date.now
},
dateReturned: {
type: Date
},
rentalFee: {
type: Number,
min: 0
}
}
})

const Rental = mongoose.model('Rental', rentalSchema)

function validate(obj) {
const schema = {
customerId: joi.string().required(),
movieId: joi.string().required()
}

return joi.validate(obj, schema)
}

exports.Rental = Rental
exports.validate = validate


My index.js Code (Where i initialise the database):



const mongoose = require('mongoose')
const movies = require('./routes/movies')
const rentals = require('./routes/rentals')

mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true})
.then(() => console.log('Connected to mongodb..'))
.catch(() => console.error('Error connecting...'))


This is unusual, as i do the same thing for genre, but it is added and viewed in mongodb compass!



[The image of mongo db compass is here: ]



Here is my rentals.js file, that uses rental.js for models:



const express = require('express')
const router = express.Router()
const {Customer} = require('../models/customer')
const Fawn = require('fawn')
const mongoose = require('mongoose')
const {Movie} = require('../models/movie')
const {Rental, validate} = require('../models/rental')

Fawn.init(mongoose)

router.get('/rentals', async (req, res) => {
const rentals = await Rental.find().sort('-dateOut')
res.send (rentals)
})


router.post('/rentals', async (req, res) => {
const {error} = validate(req.body)
if (error) return res.status(400).send('Error')

// Makes sure the customerId/customer sends us is valid
const customer = await Customer.findById(req.body.customerId)
if (!customer) return res.status(404).send('Invalid customerId')

const movie = await Movie.findById(req.body.movieId)
if (!movie) return res.status(404).send('Invalid movieId')

let rental = new Rental({
customer: {
_id: customer._id,
name: customer.name,
phone: customer.phone
},
movie: {
_id: movie._id,
title: movie.title,
dailyRentalRate: movie.dailyRentalRate
}
})

// This is for our success scenario
try {
// All args in here treated all together as unit
new Fawn.Task()
// First arg is collection we work with, and second is obj we wanna save
.save('rentals', rental)
// Update movies collection Second Arg is movie that should be updated Third is we increment the numInstock prop, and decrement by 1
.update('movies', { _id: movie._id}, {
$inc: { numberInStock: -1}
})
.run()
res.send(rental)
}
catch(ex) {
// 500 means Internal server error
res.status(500).send('Something failed.')
}
})

module.exports = router


Here is mongodb compass, and the collections seen







javascript node.js mongodb mongoose






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 23 at 2:40









Neil Lunn

96.9k22170181




96.9k22170181










asked Nov 22 at 21:18









Eesa Munir

42




42












  • mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true}) is invalid and would throw an error. You need to include the port with the useNewUrlParser option. mongoose.connect('mongodb://localhost:27017/vidly', { useNewUrlParser: true}). Right now these are "warnings", but expect that to become an actual error in the future.
    – Neil Lunn
    Nov 22 at 21:21












  • Aside from that, you are likely not looking at your vidly database when in compass, or of course you are looking in rental when mongoose is actually using rentals. Which is going to be the presumed problem unless you can show otherwise.
    – Neil Lunn
    Nov 22 at 21:24










  • It Still hasn't changed anything, as i tried adding port plus deleting the line completely. Same result though
    – Eesa Munir
    Nov 22 at 21:24










  • I am 100% looking at the vidly db, but only one collection shows up which isn't the rental one but appears to have the exact same code.
    – Eesa Munir
    Nov 22 at 21:25










  • Sorry lol, i have renamed all my models to unique ones, no errors, however still not appearing in mongodb compass
    – Eesa Munir
    Nov 22 at 21:30


















  • mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true}) is invalid and would throw an error. You need to include the port with the useNewUrlParser option. mongoose.connect('mongodb://localhost:27017/vidly', { useNewUrlParser: true}). Right now these are "warnings", but expect that to become an actual error in the future.
    – Neil Lunn
    Nov 22 at 21:21












  • Aside from that, you are likely not looking at your vidly database when in compass, or of course you are looking in rental when mongoose is actually using rentals. Which is going to be the presumed problem unless you can show otherwise.
    – Neil Lunn
    Nov 22 at 21:24










  • It Still hasn't changed anything, as i tried adding port plus deleting the line completely. Same result though
    – Eesa Munir
    Nov 22 at 21:24










  • I am 100% looking at the vidly db, but only one collection shows up which isn't the rental one but appears to have the exact same code.
    – Eesa Munir
    Nov 22 at 21:25










  • Sorry lol, i have renamed all my models to unique ones, no errors, however still not appearing in mongodb compass
    – Eesa Munir
    Nov 22 at 21:30
















mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true}) is invalid and would throw an error. You need to include the port with the useNewUrlParser option. mongoose.connect('mongodb://localhost:27017/vidly', { useNewUrlParser: true}). Right now these are "warnings", but expect that to become an actual error in the future.
– Neil Lunn
Nov 22 at 21:21






mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true}) is invalid and would throw an error. You need to include the port with the useNewUrlParser option. mongoose.connect('mongodb://localhost:27017/vidly', { useNewUrlParser: true}). Right now these are "warnings", but expect that to become an actual error in the future.
– Neil Lunn
Nov 22 at 21:21














Aside from that, you are likely not looking at your vidly database when in compass, or of course you are looking in rental when mongoose is actually using rentals. Which is going to be the presumed problem unless you can show otherwise.
– Neil Lunn
Nov 22 at 21:24




Aside from that, you are likely not looking at your vidly database when in compass, or of course you are looking in rental when mongoose is actually using rentals. Which is going to be the presumed problem unless you can show otherwise.
– Neil Lunn
Nov 22 at 21:24












It Still hasn't changed anything, as i tried adding port plus deleting the line completely. Same result though
– Eesa Munir
Nov 22 at 21:24




It Still hasn't changed anything, as i tried adding port plus deleting the line completely. Same result though
– Eesa Munir
Nov 22 at 21:24












I am 100% looking at the vidly db, but only one collection shows up which isn't the rental one but appears to have the exact same code.
– Eesa Munir
Nov 22 at 21:25




I am 100% looking at the vidly db, but only one collection shows up which isn't the rental one but appears to have the exact same code.
– Eesa Munir
Nov 22 at 21:25












Sorry lol, i have renamed all my models to unique ones, no errors, however still not appearing in mongodb compass
– Eesa Munir
Nov 22 at 21:30




Sorry lol, i have renamed all my models to unique ones, no errors, however still not appearing in mongodb compass
– Eesa Munir
Nov 22 at 21:30












1 Answer
1






active

oldest

votes


















0














Using Fawn



The issue is one of usage with the Fawn library and comes from some misconceptions about the naming of mongoose models and how these interact with the library itself. As such the best way to demonstrate is with a minimal example of working code:



const { Schema } = mongoose = require('mongoose');
const Fawn = require('fawn');

const uri = 'mongodb://localhost:27017/fawndemo';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const oneSchema = new Schema({
name: String
});

const twoSchema = new Schema({
counter: Number
});

// don't even need vars since we access model by name
mongoose.model('One', oneSchema);
mongoose.model('Two', twoSchema);

// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));


(async function() {

try {

const conn = await mongoose.connect(uri, opts);

// init fawm
Fawn.init(mongoose);

// Clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)

// run test
let task = Fawn.Task();

let results = await task
.save('One', { name: 'Bill' })
.save('Two', { counter: 0 })
.update('Two', { }, { "$inc": { "counter": 1 } })
.run({ useMongoose: true });

log(results);

// List objects in models
for ( [k,m] of Object.entries(conn.models) ) {
let result = await m.find();
log(result);
}


} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}

})()


Note how the mongoose models are registered here:



mongoose.model('One', oneSchema);
mongoose.model('Two', twoSchema);


That first argument is the registered name which mongoose uses for the model in it's internal logic. From the perspective of mongoose itself, once you have registered the model name with the schema as above, you can actually call an instance of the model as follows:



const One = mongoose.model('One');


Typically people export the result of the initial registration and then just use the returned value which is a reference to mongoose's own internal storage of the model details and attached schema. But the line of code is equivalent to that same thing as long as the registration code has already been run.



A typical exports considering this can therefore be used as:



require('./models/one');
require('./models/two');

let results = await mongoose.model('One').find();


So you might not see that often in other code examples, but that is really to show what is actually happening from the perspective of the Fawn library with later code.



With that knowledge you can consider the following code in the listing:



    let task = Fawn.Task();

let results = await task
.save('One', { name: 'Bill' })
.save('Two', { counter: 0 })
.update('Two', { }, { "$inc": { "counter": 1 } })
.run({ useMongoose: true });


Here the methods of update() and save() familiar to mongoose and MongoDB users actually have a different first argument specific to their implementation on the Fawn.Task() result. That first argument is the "registered model name" for mongoose, which is what we just explained with the previous example.



What the Fawn library is actually doing is calling similar code to:



mongoose.model('One').save({ name: 'Bill' })


Well actually it's doing something a lot more complicated than that as is evidenced in the output of the example listing. It's actually doing a lot of other things related to two phase commits and writing temporary entries in another collection and eventually moving those over to the target collections. But when it does actually go to the collections for the registered models, then that is basically how it is doing it.



So the core issue in the code in the question is that you are not using the names that were actually registered to the mongoose models, and a few other things are missing from the documentation steps.



You're also not awaiting asynchronous functions correctly, and the try..catch within the question code is not doing anything with calls in this context. The listing here however demonstrates how to do that correctly using async/await.



You can alternately just use the native Promise.then(...).catch(...) aproach if your NodeJS version does not have async/await support, but there really is little other change than doing that and of course removing the try..catch since promises in that form will ignore it. Which is why you catch() instead.




NOTE - With some brief testing there appear to be a number of things which are supported mongoose/mongodb features which are not actually implemented and supported on this library's methods. Notably "upserts" was a prime example of a useful and common thing which the "two phase commit" system implemented here does not appear to support at all.



This partly seems an oversight in the code of the library where certain "options" to the methods are actually being ignored or stripped completely. This is a concern for getting the most out of MongoDB features.




Transactions



The whole usage of this library though at least seems suspicious to me that you picked it up because you "thought" this was "Transactions". Put plainly the two phase commit is NOT a transaction. Furthermore the implementation of any attempt at such control and rollback etc seem very loose at best.



If you have a modern MongoDB 4.0 server or above, and where you actually configured it to be named as a "replica set" ( which you can also do for a single member, where a common misconception is you need more than one ) then there is support for real transactions, and they are very easy to implement:



const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/trandemo';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const orderSchema = new Schema({
name: String
});

const orderItemsSchema = new Schema({
order: { type: Schema.Types.ObjectId, ref: 'Order' },
itemName: String,
price: Number
});

const Order = mongoose.model('Order', orderSchema);
const OrderItems = mongoose.model('OrderItems', orderItemsSchema);

// log helper

const log = data => console.log(JSON.stringify(data, undefined, 2));

// main

(async function() {

try {

const conn = await mongoose.connect(uri, opts);

// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)

let session = await conn.startSession();
session.startTransaction();

// Collections must exist in transactions
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.createCollection())
);

let [order] = await Order.create([{ name: 'Bill' }], { session });

let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);

// update an item
let result1 = await OrderItems.updateOne(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ session }
);
log(result1);

// commit
await session.commitTransaction();

// start another
session.startTransaction();

// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);

await session.abortTransaction();

/*
* $lookup join - expect Milk to be price: 4
*
*/

let joined = await Order.aggregate([
{ '$match': { _id: order._id } },
{ '$lookup': {
'from': OrderItems.collection.name,
'foreignField': 'order',
'localField': '_id',
'as': 'orderitems'
}}
]);
log(joined);


} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}

})()


That is really just a simple listing with the class Order and related OrderItems. There really is nothing special in the code and you should see that it's basically the same as most listing examples you will see with a few small changes.



Notably we initialize a session and also session.startTransaction() as an indicator that a transaction should be in progress. Note that session would generally have a wider scope where you would typically re-use that object for more than just a few operations.



Now you have session and the transaction is started, this is simply added to the "options" of the various statements being executed:



let [order] = await Order.create([{ name: 'Bill' }], { session });

let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);


Admittedly this is a brief example that does not fully cover all write error possibilities and how to handle that within separate try..catch blocks. But as a very basic example should any error occur before the session.commitTransaction() is called, then none of the operations since the transaction was started will actually be persisted within the session.



Also there is "causal consistency" in that once a normal write acknowledgement has been confirmed, then within the scope of the session the data appears written to the respective collections right up until the transaction commit or rollback.



In the event of a rollback ( as demonstrated in the final operation ):



// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);

await session.abortTransaction();


These writes though reported to be made as seen in the operation result, are indeed "rolled back" and further operations see the state of the data before these changes were made.



The full example code demonstrates this by adding the items with another update action in one transaction, then beginning another to alter data and read it then abort the transaction. The final data state shows of course only what was actually committed.




NOTE Operations like find() and findOne() or anything that retrieves data must include the session whilst a transaction is active in order to see the current state, just in the same way that write operations are doing as shown in the listing.



Without including the session, these changes in state are not visible in the "global" scope until the transaction is resolved.






Listing Outputs



Code listings given produce the following output when run, for reference.



fawndemo



Mongoose: ones.deleteMany({}, {})
Mongoose: twos.deleteMany({}, {})
Mongoose: ojlinttaskcollections.deleteMany({}, {})
Mongoose: ojlinttaskcollections.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a"), steps: [ { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030d"), index: 0, type: 'save', state: 0, name: 'One', data: { name: 'Bill' } }, { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030c"), index: 1, type: 'save', state: 0, name: 'Two', data: { counter: 0 } }, { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030b"), index: 2, type: 'update', state: 0, name: 'Two', data: { '*_**ojlint**escape$*__tx__00***___string$inc': { counter: 1 } } } ], __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.0.state': 1 } })
Mongoose: ones.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030e"), name: 'Bill', __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.0.state': 2 } })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.1.state': 1 } })
Mongoose: twos.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030f"), counter: 0, __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.1.state': 2 } })
Mongoose: twos.find({})
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.2.state': 1 } })
Mongoose: twos.update({}, { '$inc': { counter: 1 } }, {})
(node:24494) DeprecationWarning: collection.update is deprecated. Use updateOne, updateMany, or bulkWrite instead.
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.2.state': 2 } })
Mongoose: ojlinttaskcollections.deleteOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") })
[
{
"_id": "5bf765f7e5c71c5fae77030e",
"name": "Bill",
"__v": 0
},
{
"_id": "5bf765f7e5c71c5fae77030f",
"counter": 0,
"__v": 0
},
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626877488230301707",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626877488230301707",
"$clusterTime": {
"clusterTime": "6626877488230301707",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
]
Mongoose: ones.find({}, { projection: {} })
[
{
"_id": "5bf765f7e5c71c5fae77030e",
"name": "Bill",
"__v": 0
}
]
Mongoose: twos.find({}, { projection: {} })
[
{
"_id": "5bf765f7e5c71c5fae77030f",
"counter": 1,
"__v": 0
}
]
Mongoose: ojlinttaskcollections.find({}, { projection: {} })



transdemo



Mongoose: orders.deleteMany({}, {})
Mongoose: orderitems.deleteMany({}, {})
Mongoose: orders.insertOne({ _id: ObjectId("5bf7661c3f60105fe48d076e"), name: 'Bill', __v: 0 }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
Mongoose: orderitems.insertMany([ { _id: 5bf7661c3f60105fe48d076f, order: 5bf7661c3f60105fe48d076e, itemName: 'Cheese', price: 1, __v: 0 }, { _id: 5bf7661c3f60105fe48d0770, order: 5bf7661c3f60105fe48d076e, itemName: 'Bread', price: 2, __v: 0 }, { _id: 5bf7661c3f60105fe48d0771, order: 5bf7661c3f60105fe48d076e, itemName: 'Milk', price: 3, __v: 0 } ], { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
Mongoose: orderitems.updateOne({ order: ObjectId("5bf7661c3f60105fe48d076e"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626877647144091652",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626877647144091652",
"$clusterTime": {
"clusterTime": "6626877647144091652",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: orderitems.findOneAndUpdate({ order: ObjectId("5bf7661c3f60105fe48d076e"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334"), upsert: false, remove: false, projection: {}, returnOriginal: false })
{
"_id": "5bf7661c3f60105fe48d0771",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Milk",
"price": 5,
"__v": 0
}
Mongoose: orders.aggregate([ { '$match': { _id: 5bf7661c3f60105fe48d076e } }, { '$lookup': { from: 'orderitems', foreignField: 'order', localField: '_id', as: 'orderitems' } } ], {})
[
{
"_id": "5bf7661c3f60105fe48d076e",
"name": "Bill",
"__v": 0,
"orderitems": [
{
"_id": "5bf7661c3f60105fe48d076f",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Cheese",
"price": 1,
"__v": 0
},
{
"_id": "5bf7661c3f60105fe48d0770",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Bread",
"price": 2,
"__v": 0
},
{
"_id": "5bf7661c3f60105fe48d0771",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Milk",
"price": 4,
"__v": 0
}
]
}
]





share|improve this answer





















  • Neil, I have renamed to the exact same name as registered.
    – Eesa Munir
    Nov 23 at 17:06










  • @EesaMunir You have complete program listings and also included output as the result of "me running those programs". What you are expected to do is to use those same listings yourself in a fresh install and see them working for yourself. You can "compare" to your code to see what you are doing wrong. But we will not debug your entire project. You also have some really good advice here that you would "appear" to have chosen some library on the presumption it gave you "transactions". It does not, and you have instruction on how to do it correctly. I consider your question answered.Learn from it.
    – Neil Lunn
    Nov 23 at 20:12










  • @EesaMunir Please stop sending comments. You can see they are being removed and I have said all that is needed to be said on the subject. You should run the code in the answer, see that it works and learn from it, accept the answer and move on. And most importantly READ the answer, because the point of this site is not about "getting code from people" but learning instead.
    – Neil Lunn
    Nov 24 at 20:53













Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53438087%2fcollections-missing-with-fawn-transactions%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









0














Using Fawn



The issue is one of usage with the Fawn library and comes from some misconceptions about the naming of mongoose models and how these interact with the library itself. As such the best way to demonstrate is with a minimal example of working code:



const { Schema } = mongoose = require('mongoose');
const Fawn = require('fawn');

const uri = 'mongodb://localhost:27017/fawndemo';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const oneSchema = new Schema({
name: String
});

const twoSchema = new Schema({
counter: Number
});

// don't even need vars since we access model by name
mongoose.model('One', oneSchema);
mongoose.model('Two', twoSchema);

// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));


(async function() {

try {

const conn = await mongoose.connect(uri, opts);

// init fawm
Fawn.init(mongoose);

// Clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)

// run test
let task = Fawn.Task();

let results = await task
.save('One', { name: 'Bill' })
.save('Two', { counter: 0 })
.update('Two', { }, { "$inc": { "counter": 1 } })
.run({ useMongoose: true });

log(results);

// List objects in models
for ( [k,m] of Object.entries(conn.models) ) {
let result = await m.find();
log(result);
}


} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}

})()


Note how the mongoose models are registered here:



mongoose.model('One', oneSchema);
mongoose.model('Two', twoSchema);


That first argument is the registered name which mongoose uses for the model in it's internal logic. From the perspective of mongoose itself, once you have registered the model name with the schema as above, you can actually call an instance of the model as follows:



const One = mongoose.model('One');


Typically people export the result of the initial registration and then just use the returned value which is a reference to mongoose's own internal storage of the model details and attached schema. But the line of code is equivalent to that same thing as long as the registration code has already been run.



A typical exports considering this can therefore be used as:



require('./models/one');
require('./models/two');

let results = await mongoose.model('One').find();


So you might not see that often in other code examples, but that is really to show what is actually happening from the perspective of the Fawn library with later code.



With that knowledge you can consider the following code in the listing:



    let task = Fawn.Task();

let results = await task
.save('One', { name: 'Bill' })
.save('Two', { counter: 0 })
.update('Two', { }, { "$inc": { "counter": 1 } })
.run({ useMongoose: true });


Here the methods of update() and save() familiar to mongoose and MongoDB users actually have a different first argument specific to their implementation on the Fawn.Task() result. That first argument is the "registered model name" for mongoose, which is what we just explained with the previous example.



What the Fawn library is actually doing is calling similar code to:



mongoose.model('One').save({ name: 'Bill' })


Well actually it's doing something a lot more complicated than that as is evidenced in the output of the example listing. It's actually doing a lot of other things related to two phase commits and writing temporary entries in another collection and eventually moving those over to the target collections. But when it does actually go to the collections for the registered models, then that is basically how it is doing it.



So the core issue in the code in the question is that you are not using the names that were actually registered to the mongoose models, and a few other things are missing from the documentation steps.



You're also not awaiting asynchronous functions correctly, and the try..catch within the question code is not doing anything with calls in this context. The listing here however demonstrates how to do that correctly using async/await.



You can alternately just use the native Promise.then(...).catch(...) aproach if your NodeJS version does not have async/await support, but there really is little other change than doing that and of course removing the try..catch since promises in that form will ignore it. Which is why you catch() instead.




NOTE - With some brief testing there appear to be a number of things which are supported mongoose/mongodb features which are not actually implemented and supported on this library's methods. Notably "upserts" was a prime example of a useful and common thing which the "two phase commit" system implemented here does not appear to support at all.



This partly seems an oversight in the code of the library where certain "options" to the methods are actually being ignored or stripped completely. This is a concern for getting the most out of MongoDB features.




Transactions



The whole usage of this library though at least seems suspicious to me that you picked it up because you "thought" this was "Transactions". Put plainly the two phase commit is NOT a transaction. Furthermore the implementation of any attempt at such control and rollback etc seem very loose at best.



If you have a modern MongoDB 4.0 server or above, and where you actually configured it to be named as a "replica set" ( which you can also do for a single member, where a common misconception is you need more than one ) then there is support for real transactions, and they are very easy to implement:



const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/trandemo';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const orderSchema = new Schema({
name: String
});

const orderItemsSchema = new Schema({
order: { type: Schema.Types.ObjectId, ref: 'Order' },
itemName: String,
price: Number
});

const Order = mongoose.model('Order', orderSchema);
const OrderItems = mongoose.model('OrderItems', orderItemsSchema);

// log helper

const log = data => console.log(JSON.stringify(data, undefined, 2));

// main

(async function() {

try {

const conn = await mongoose.connect(uri, opts);

// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)

let session = await conn.startSession();
session.startTransaction();

// Collections must exist in transactions
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.createCollection())
);

let [order] = await Order.create([{ name: 'Bill' }], { session });

let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);

// update an item
let result1 = await OrderItems.updateOne(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ session }
);
log(result1);

// commit
await session.commitTransaction();

// start another
session.startTransaction();

// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);

await session.abortTransaction();

/*
* $lookup join - expect Milk to be price: 4
*
*/

let joined = await Order.aggregate([
{ '$match': { _id: order._id } },
{ '$lookup': {
'from': OrderItems.collection.name,
'foreignField': 'order',
'localField': '_id',
'as': 'orderitems'
}}
]);
log(joined);


} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}

})()


That is really just a simple listing with the class Order and related OrderItems. There really is nothing special in the code and you should see that it's basically the same as most listing examples you will see with a few small changes.



Notably we initialize a session and also session.startTransaction() as an indicator that a transaction should be in progress. Note that session would generally have a wider scope where you would typically re-use that object for more than just a few operations.



Now you have session and the transaction is started, this is simply added to the "options" of the various statements being executed:



let [order] = await Order.create([{ name: 'Bill' }], { session });

let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);


Admittedly this is a brief example that does not fully cover all write error possibilities and how to handle that within separate try..catch blocks. But as a very basic example should any error occur before the session.commitTransaction() is called, then none of the operations since the transaction was started will actually be persisted within the session.



Also there is "causal consistency" in that once a normal write acknowledgement has been confirmed, then within the scope of the session the data appears written to the respective collections right up until the transaction commit or rollback.



In the event of a rollback ( as demonstrated in the final operation ):



// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);

await session.abortTransaction();


These writes though reported to be made as seen in the operation result, are indeed "rolled back" and further operations see the state of the data before these changes were made.



The full example code demonstrates this by adding the items with another update action in one transaction, then beginning another to alter data and read it then abort the transaction. The final data state shows of course only what was actually committed.




NOTE Operations like find() and findOne() or anything that retrieves data must include the session whilst a transaction is active in order to see the current state, just in the same way that write operations are doing as shown in the listing.



Without including the session, these changes in state are not visible in the "global" scope until the transaction is resolved.






Listing Outputs



Code listings given produce the following output when run, for reference.



fawndemo



Mongoose: ones.deleteMany({}, {})
Mongoose: twos.deleteMany({}, {})
Mongoose: ojlinttaskcollections.deleteMany({}, {})
Mongoose: ojlinttaskcollections.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a"), steps: [ { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030d"), index: 0, type: 'save', state: 0, name: 'One', data: { name: 'Bill' } }, { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030c"), index: 1, type: 'save', state: 0, name: 'Two', data: { counter: 0 } }, { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030b"), index: 2, type: 'update', state: 0, name: 'Two', data: { '*_**ojlint**escape$*__tx__00***___string$inc': { counter: 1 } } } ], __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.0.state': 1 } })
Mongoose: ones.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030e"), name: 'Bill', __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.0.state': 2 } })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.1.state': 1 } })
Mongoose: twos.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030f"), counter: 0, __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.1.state': 2 } })
Mongoose: twos.find({})
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.2.state': 1 } })
Mongoose: twos.update({}, { '$inc': { counter: 1 } }, {})
(node:24494) DeprecationWarning: collection.update is deprecated. Use updateOne, updateMany, or bulkWrite instead.
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.2.state': 2 } })
Mongoose: ojlinttaskcollections.deleteOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") })
[
{
"_id": "5bf765f7e5c71c5fae77030e",
"name": "Bill",
"__v": 0
},
{
"_id": "5bf765f7e5c71c5fae77030f",
"counter": 0,
"__v": 0
},
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626877488230301707",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626877488230301707",
"$clusterTime": {
"clusterTime": "6626877488230301707",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
]
Mongoose: ones.find({}, { projection: {} })
[
{
"_id": "5bf765f7e5c71c5fae77030e",
"name": "Bill",
"__v": 0
}
]
Mongoose: twos.find({}, { projection: {} })
[
{
"_id": "5bf765f7e5c71c5fae77030f",
"counter": 1,
"__v": 0
}
]
Mongoose: ojlinttaskcollections.find({}, { projection: {} })



transdemo



Mongoose: orders.deleteMany({}, {})
Mongoose: orderitems.deleteMany({}, {})
Mongoose: orders.insertOne({ _id: ObjectId("5bf7661c3f60105fe48d076e"), name: 'Bill', __v: 0 }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
Mongoose: orderitems.insertMany([ { _id: 5bf7661c3f60105fe48d076f, order: 5bf7661c3f60105fe48d076e, itemName: 'Cheese', price: 1, __v: 0 }, { _id: 5bf7661c3f60105fe48d0770, order: 5bf7661c3f60105fe48d076e, itemName: 'Bread', price: 2, __v: 0 }, { _id: 5bf7661c3f60105fe48d0771, order: 5bf7661c3f60105fe48d076e, itemName: 'Milk', price: 3, __v: 0 } ], { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
Mongoose: orderitems.updateOne({ order: ObjectId("5bf7661c3f60105fe48d076e"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626877647144091652",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626877647144091652",
"$clusterTime": {
"clusterTime": "6626877647144091652",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: orderitems.findOneAndUpdate({ order: ObjectId("5bf7661c3f60105fe48d076e"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334"), upsert: false, remove: false, projection: {}, returnOriginal: false })
{
"_id": "5bf7661c3f60105fe48d0771",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Milk",
"price": 5,
"__v": 0
}
Mongoose: orders.aggregate([ { '$match': { _id: 5bf7661c3f60105fe48d076e } }, { '$lookup': { from: 'orderitems', foreignField: 'order', localField: '_id', as: 'orderitems' } } ], {})
[
{
"_id": "5bf7661c3f60105fe48d076e",
"name": "Bill",
"__v": 0,
"orderitems": [
{
"_id": "5bf7661c3f60105fe48d076f",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Cheese",
"price": 1,
"__v": 0
},
{
"_id": "5bf7661c3f60105fe48d0770",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Bread",
"price": 2,
"__v": 0
},
{
"_id": "5bf7661c3f60105fe48d0771",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Milk",
"price": 4,
"__v": 0
}
]
}
]





share|improve this answer





















  • Neil, I have renamed to the exact same name as registered.
    – Eesa Munir
    Nov 23 at 17:06










  • @EesaMunir You have complete program listings and also included output as the result of "me running those programs". What you are expected to do is to use those same listings yourself in a fresh install and see them working for yourself. You can "compare" to your code to see what you are doing wrong. But we will not debug your entire project. You also have some really good advice here that you would "appear" to have chosen some library on the presumption it gave you "transactions". It does not, and you have instruction on how to do it correctly. I consider your question answered.Learn from it.
    – Neil Lunn
    Nov 23 at 20:12










  • @EesaMunir Please stop sending comments. You can see they are being removed and I have said all that is needed to be said on the subject. You should run the code in the answer, see that it works and learn from it, accept the answer and move on. And most importantly READ the answer, because the point of this site is not about "getting code from people" but learning instead.
    – Neil Lunn
    Nov 24 at 20:53


















0














Using Fawn



The issue is one of usage with the Fawn library and comes from some misconceptions about the naming of mongoose models and how these interact with the library itself. As such the best way to demonstrate is with a minimal example of working code:



const { Schema } = mongoose = require('mongoose');
const Fawn = require('fawn');

const uri = 'mongodb://localhost:27017/fawndemo';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const oneSchema = new Schema({
name: String
});

const twoSchema = new Schema({
counter: Number
});

// don't even need vars since we access model by name
mongoose.model('One', oneSchema);
mongoose.model('Two', twoSchema);

// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));


(async function() {

try {

const conn = await mongoose.connect(uri, opts);

// init fawm
Fawn.init(mongoose);

// Clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)

// run test
let task = Fawn.Task();

let results = await task
.save('One', { name: 'Bill' })
.save('Two', { counter: 0 })
.update('Two', { }, { "$inc": { "counter": 1 } })
.run({ useMongoose: true });

log(results);

// List objects in models
for ( [k,m] of Object.entries(conn.models) ) {
let result = await m.find();
log(result);
}


} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}

})()


Note how the mongoose models are registered here:



mongoose.model('One', oneSchema);
mongoose.model('Two', twoSchema);


That first argument is the registered name which mongoose uses for the model in it's internal logic. From the perspective of mongoose itself, once you have registered the model name with the schema as above, you can actually call an instance of the model as follows:



const One = mongoose.model('One');


Typically people export the result of the initial registration and then just use the returned value which is a reference to mongoose's own internal storage of the model details and attached schema. But the line of code is equivalent to that same thing as long as the registration code has already been run.



A typical exports considering this can therefore be used as:



require('./models/one');
require('./models/two');

let results = await mongoose.model('One').find();


So you might not see that often in other code examples, but that is really to show what is actually happening from the perspective of the Fawn library with later code.



With that knowledge you can consider the following code in the listing:



    let task = Fawn.Task();

let results = await task
.save('One', { name: 'Bill' })
.save('Two', { counter: 0 })
.update('Two', { }, { "$inc": { "counter": 1 } })
.run({ useMongoose: true });


Here the methods of update() and save() familiar to mongoose and MongoDB users actually have a different first argument specific to their implementation on the Fawn.Task() result. That first argument is the "registered model name" for mongoose, which is what we just explained with the previous example.



What the Fawn library is actually doing is calling similar code to:



mongoose.model('One').save({ name: 'Bill' })


Well actually it's doing something a lot more complicated than that as is evidenced in the output of the example listing. It's actually doing a lot of other things related to two phase commits and writing temporary entries in another collection and eventually moving those over to the target collections. But when it does actually go to the collections for the registered models, then that is basically how it is doing it.



So the core issue in the code in the question is that you are not using the names that were actually registered to the mongoose models, and a few other things are missing from the documentation steps.



You're also not awaiting asynchronous functions correctly, and the try..catch within the question code is not doing anything with calls in this context. The listing here however demonstrates how to do that correctly using async/await.



You can alternately just use the native Promise.then(...).catch(...) aproach if your NodeJS version does not have async/await support, but there really is little other change than doing that and of course removing the try..catch since promises in that form will ignore it. Which is why you catch() instead.




NOTE - With some brief testing there appear to be a number of things which are supported mongoose/mongodb features which are not actually implemented and supported on this library's methods. Notably "upserts" was a prime example of a useful and common thing which the "two phase commit" system implemented here does not appear to support at all.



This partly seems an oversight in the code of the library where certain "options" to the methods are actually being ignored or stripped completely. This is a concern for getting the most out of MongoDB features.




Transactions



The whole usage of this library though at least seems suspicious to me that you picked it up because you "thought" this was "Transactions". Put plainly the two phase commit is NOT a transaction. Furthermore the implementation of any attempt at such control and rollback etc seem very loose at best.



If you have a modern MongoDB 4.0 server or above, and where you actually configured it to be named as a "replica set" ( which you can also do for a single member, where a common misconception is you need more than one ) then there is support for real transactions, and they are very easy to implement:



const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/trandemo';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const orderSchema = new Schema({
name: String
});

const orderItemsSchema = new Schema({
order: { type: Schema.Types.ObjectId, ref: 'Order' },
itemName: String,
price: Number
});

const Order = mongoose.model('Order', orderSchema);
const OrderItems = mongoose.model('OrderItems', orderItemsSchema);

// log helper

const log = data => console.log(JSON.stringify(data, undefined, 2));

// main

(async function() {

try {

const conn = await mongoose.connect(uri, opts);

// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)

let session = await conn.startSession();
session.startTransaction();

// Collections must exist in transactions
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.createCollection())
);

let [order] = await Order.create([{ name: 'Bill' }], { session });

let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);

// update an item
let result1 = await OrderItems.updateOne(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ session }
);
log(result1);

// commit
await session.commitTransaction();

// start another
session.startTransaction();

// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);

await session.abortTransaction();

/*
* $lookup join - expect Milk to be price: 4
*
*/

let joined = await Order.aggregate([
{ '$match': { _id: order._id } },
{ '$lookup': {
'from': OrderItems.collection.name,
'foreignField': 'order',
'localField': '_id',
'as': 'orderitems'
}}
]);
log(joined);


} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}

})()


That is really just a simple listing with the class Order and related OrderItems. There really is nothing special in the code and you should see that it's basically the same as most listing examples you will see with a few small changes.



Notably we initialize a session and also session.startTransaction() as an indicator that a transaction should be in progress. Note that session would generally have a wider scope where you would typically re-use that object for more than just a few operations.



Now you have session and the transaction is started, this is simply added to the "options" of the various statements being executed:



let [order] = await Order.create([{ name: 'Bill' }], { session });

let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);


Admittedly this is a brief example that does not fully cover all write error possibilities and how to handle that within separate try..catch blocks. But as a very basic example should any error occur before the session.commitTransaction() is called, then none of the operations since the transaction was started will actually be persisted within the session.



Also there is "causal consistency" in that once a normal write acknowledgement has been confirmed, then within the scope of the session the data appears written to the respective collections right up until the transaction commit or rollback.



In the event of a rollback ( as demonstrated in the final operation ):



// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);

await session.abortTransaction();


These writes though reported to be made as seen in the operation result, are indeed "rolled back" and further operations see the state of the data before these changes were made.



The full example code demonstrates this by adding the items with another update action in one transaction, then beginning another to alter data and read it then abort the transaction. The final data state shows of course only what was actually committed.




NOTE Operations like find() and findOne() or anything that retrieves data must include the session whilst a transaction is active in order to see the current state, just in the same way that write operations are doing as shown in the listing.



Without including the session, these changes in state are not visible in the "global" scope until the transaction is resolved.






Listing Outputs



Code listings given produce the following output when run, for reference.



fawndemo



Mongoose: ones.deleteMany({}, {})
Mongoose: twos.deleteMany({}, {})
Mongoose: ojlinttaskcollections.deleteMany({}, {})
Mongoose: ojlinttaskcollections.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a"), steps: [ { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030d"), index: 0, type: 'save', state: 0, name: 'One', data: { name: 'Bill' } }, { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030c"), index: 1, type: 'save', state: 0, name: 'Two', data: { counter: 0 } }, { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030b"), index: 2, type: 'update', state: 0, name: 'Two', data: { '*_**ojlint**escape$*__tx__00***___string$inc': { counter: 1 } } } ], __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.0.state': 1 } })
Mongoose: ones.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030e"), name: 'Bill', __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.0.state': 2 } })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.1.state': 1 } })
Mongoose: twos.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030f"), counter: 0, __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.1.state': 2 } })
Mongoose: twos.find({})
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.2.state': 1 } })
Mongoose: twos.update({}, { '$inc': { counter: 1 } }, {})
(node:24494) DeprecationWarning: collection.update is deprecated. Use updateOne, updateMany, or bulkWrite instead.
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.2.state': 2 } })
Mongoose: ojlinttaskcollections.deleteOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") })
[
{
"_id": "5bf765f7e5c71c5fae77030e",
"name": "Bill",
"__v": 0
},
{
"_id": "5bf765f7e5c71c5fae77030f",
"counter": 0,
"__v": 0
},
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626877488230301707",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626877488230301707",
"$clusterTime": {
"clusterTime": "6626877488230301707",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
]
Mongoose: ones.find({}, { projection: {} })
[
{
"_id": "5bf765f7e5c71c5fae77030e",
"name": "Bill",
"__v": 0
}
]
Mongoose: twos.find({}, { projection: {} })
[
{
"_id": "5bf765f7e5c71c5fae77030f",
"counter": 1,
"__v": 0
}
]
Mongoose: ojlinttaskcollections.find({}, { projection: {} })



transdemo



Mongoose: orders.deleteMany({}, {})
Mongoose: orderitems.deleteMany({}, {})
Mongoose: orders.insertOne({ _id: ObjectId("5bf7661c3f60105fe48d076e"), name: 'Bill', __v: 0 }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
Mongoose: orderitems.insertMany([ { _id: 5bf7661c3f60105fe48d076f, order: 5bf7661c3f60105fe48d076e, itemName: 'Cheese', price: 1, __v: 0 }, { _id: 5bf7661c3f60105fe48d0770, order: 5bf7661c3f60105fe48d076e, itemName: 'Bread', price: 2, __v: 0 }, { _id: 5bf7661c3f60105fe48d0771, order: 5bf7661c3f60105fe48d076e, itemName: 'Milk', price: 3, __v: 0 } ], { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
Mongoose: orderitems.updateOne({ order: ObjectId("5bf7661c3f60105fe48d076e"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626877647144091652",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626877647144091652",
"$clusterTime": {
"clusterTime": "6626877647144091652",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: orderitems.findOneAndUpdate({ order: ObjectId("5bf7661c3f60105fe48d076e"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334"), upsert: false, remove: false, projection: {}, returnOriginal: false })
{
"_id": "5bf7661c3f60105fe48d0771",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Milk",
"price": 5,
"__v": 0
}
Mongoose: orders.aggregate([ { '$match': { _id: 5bf7661c3f60105fe48d076e } }, { '$lookup': { from: 'orderitems', foreignField: 'order', localField: '_id', as: 'orderitems' } } ], {})
[
{
"_id": "5bf7661c3f60105fe48d076e",
"name": "Bill",
"__v": 0,
"orderitems": [
{
"_id": "5bf7661c3f60105fe48d076f",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Cheese",
"price": 1,
"__v": 0
},
{
"_id": "5bf7661c3f60105fe48d0770",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Bread",
"price": 2,
"__v": 0
},
{
"_id": "5bf7661c3f60105fe48d0771",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Milk",
"price": 4,
"__v": 0
}
]
}
]





share|improve this answer





















  • Neil, I have renamed to the exact same name as registered.
    – Eesa Munir
    Nov 23 at 17:06










  • @EesaMunir You have complete program listings and also included output as the result of "me running those programs". What you are expected to do is to use those same listings yourself in a fresh install and see them working for yourself. You can "compare" to your code to see what you are doing wrong. But we will not debug your entire project. You also have some really good advice here that you would "appear" to have chosen some library on the presumption it gave you "transactions". It does not, and you have instruction on how to do it correctly. I consider your question answered.Learn from it.
    – Neil Lunn
    Nov 23 at 20:12










  • @EesaMunir Please stop sending comments. You can see they are being removed and I have said all that is needed to be said on the subject. You should run the code in the answer, see that it works and learn from it, accept the answer and move on. And most importantly READ the answer, because the point of this site is not about "getting code from people" but learning instead.
    – Neil Lunn
    Nov 24 at 20:53
















0












0








0






Using Fawn



The issue is one of usage with the Fawn library and comes from some misconceptions about the naming of mongoose models and how these interact with the library itself. As such the best way to demonstrate is with a minimal example of working code:



const { Schema } = mongoose = require('mongoose');
const Fawn = require('fawn');

const uri = 'mongodb://localhost:27017/fawndemo';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const oneSchema = new Schema({
name: String
});

const twoSchema = new Schema({
counter: Number
});

// don't even need vars since we access model by name
mongoose.model('One', oneSchema);
mongoose.model('Two', twoSchema);

// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));


(async function() {

try {

const conn = await mongoose.connect(uri, opts);

// init fawm
Fawn.init(mongoose);

// Clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)

// run test
let task = Fawn.Task();

let results = await task
.save('One', { name: 'Bill' })
.save('Two', { counter: 0 })
.update('Two', { }, { "$inc": { "counter": 1 } })
.run({ useMongoose: true });

log(results);

// List objects in models
for ( [k,m] of Object.entries(conn.models) ) {
let result = await m.find();
log(result);
}


} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}

})()


Note how the mongoose models are registered here:



mongoose.model('One', oneSchema);
mongoose.model('Two', twoSchema);


That first argument is the registered name which mongoose uses for the model in it's internal logic. From the perspective of mongoose itself, once you have registered the model name with the schema as above, you can actually call an instance of the model as follows:



const One = mongoose.model('One');


Typically people export the result of the initial registration and then just use the returned value which is a reference to mongoose's own internal storage of the model details and attached schema. But the line of code is equivalent to that same thing as long as the registration code has already been run.



A typical exports considering this can therefore be used as:



require('./models/one');
require('./models/two');

let results = await mongoose.model('One').find();


So you might not see that often in other code examples, but that is really to show what is actually happening from the perspective of the Fawn library with later code.



With that knowledge you can consider the following code in the listing:



    let task = Fawn.Task();

let results = await task
.save('One', { name: 'Bill' })
.save('Two', { counter: 0 })
.update('Two', { }, { "$inc": { "counter": 1 } })
.run({ useMongoose: true });


Here the methods of update() and save() familiar to mongoose and MongoDB users actually have a different first argument specific to their implementation on the Fawn.Task() result. That first argument is the "registered model name" for mongoose, which is what we just explained with the previous example.



What the Fawn library is actually doing is calling similar code to:



mongoose.model('One').save({ name: 'Bill' })


Well actually it's doing something a lot more complicated than that as is evidenced in the output of the example listing. It's actually doing a lot of other things related to two phase commits and writing temporary entries in another collection and eventually moving those over to the target collections. But when it does actually go to the collections for the registered models, then that is basically how it is doing it.



So the core issue in the code in the question is that you are not using the names that were actually registered to the mongoose models, and a few other things are missing from the documentation steps.



You're also not awaiting asynchronous functions correctly, and the try..catch within the question code is not doing anything with calls in this context. The listing here however demonstrates how to do that correctly using async/await.



You can alternately just use the native Promise.then(...).catch(...) aproach if your NodeJS version does not have async/await support, but there really is little other change than doing that and of course removing the try..catch since promises in that form will ignore it. Which is why you catch() instead.




NOTE - With some brief testing there appear to be a number of things which are supported mongoose/mongodb features which are not actually implemented and supported on this library's methods. Notably "upserts" was a prime example of a useful and common thing which the "two phase commit" system implemented here does not appear to support at all.



This partly seems an oversight in the code of the library where certain "options" to the methods are actually being ignored or stripped completely. This is a concern for getting the most out of MongoDB features.




Transactions



The whole usage of this library though at least seems suspicious to me that you picked it up because you "thought" this was "Transactions". Put plainly the two phase commit is NOT a transaction. Furthermore the implementation of any attempt at such control and rollback etc seem very loose at best.



If you have a modern MongoDB 4.0 server or above, and where you actually configured it to be named as a "replica set" ( which you can also do for a single member, where a common misconception is you need more than one ) then there is support for real transactions, and they are very easy to implement:



const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/trandemo';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const orderSchema = new Schema({
name: String
});

const orderItemsSchema = new Schema({
order: { type: Schema.Types.ObjectId, ref: 'Order' },
itemName: String,
price: Number
});

const Order = mongoose.model('Order', orderSchema);
const OrderItems = mongoose.model('OrderItems', orderItemsSchema);

// log helper

const log = data => console.log(JSON.stringify(data, undefined, 2));

// main

(async function() {

try {

const conn = await mongoose.connect(uri, opts);

// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)

let session = await conn.startSession();
session.startTransaction();

// Collections must exist in transactions
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.createCollection())
);

let [order] = await Order.create([{ name: 'Bill' }], { session });

let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);

// update an item
let result1 = await OrderItems.updateOne(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ session }
);
log(result1);

// commit
await session.commitTransaction();

// start another
session.startTransaction();

// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);

await session.abortTransaction();

/*
* $lookup join - expect Milk to be price: 4
*
*/

let joined = await Order.aggregate([
{ '$match': { _id: order._id } },
{ '$lookup': {
'from': OrderItems.collection.name,
'foreignField': 'order',
'localField': '_id',
'as': 'orderitems'
}}
]);
log(joined);


} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}

})()


That is really just a simple listing with the class Order and related OrderItems. There really is nothing special in the code and you should see that it's basically the same as most listing examples you will see with a few small changes.



Notably we initialize a session and also session.startTransaction() as an indicator that a transaction should be in progress. Note that session would generally have a wider scope where you would typically re-use that object for more than just a few operations.



Now you have session and the transaction is started, this is simply added to the "options" of the various statements being executed:



let [order] = await Order.create([{ name: 'Bill' }], { session });

let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);


Admittedly this is a brief example that does not fully cover all write error possibilities and how to handle that within separate try..catch blocks. But as a very basic example should any error occur before the session.commitTransaction() is called, then none of the operations since the transaction was started will actually be persisted within the session.



Also there is "causal consistency" in that once a normal write acknowledgement has been confirmed, then within the scope of the session the data appears written to the respective collections right up until the transaction commit or rollback.



In the event of a rollback ( as demonstrated in the final operation ):



// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);

await session.abortTransaction();


These writes though reported to be made as seen in the operation result, are indeed "rolled back" and further operations see the state of the data before these changes were made.



The full example code demonstrates this by adding the items with another update action in one transaction, then beginning another to alter data and read it then abort the transaction. The final data state shows of course only what was actually committed.




NOTE Operations like find() and findOne() or anything that retrieves data must include the session whilst a transaction is active in order to see the current state, just in the same way that write operations are doing as shown in the listing.



Without including the session, these changes in state are not visible in the "global" scope until the transaction is resolved.






Listing Outputs



Code listings given produce the following output when run, for reference.



fawndemo



Mongoose: ones.deleteMany({}, {})
Mongoose: twos.deleteMany({}, {})
Mongoose: ojlinttaskcollections.deleteMany({}, {})
Mongoose: ojlinttaskcollections.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a"), steps: [ { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030d"), index: 0, type: 'save', state: 0, name: 'One', data: { name: 'Bill' } }, { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030c"), index: 1, type: 'save', state: 0, name: 'Two', data: { counter: 0 } }, { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030b"), index: 2, type: 'update', state: 0, name: 'Two', data: { '*_**ojlint**escape$*__tx__00***___string$inc': { counter: 1 } } } ], __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.0.state': 1 } })
Mongoose: ones.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030e"), name: 'Bill', __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.0.state': 2 } })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.1.state': 1 } })
Mongoose: twos.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030f"), counter: 0, __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.1.state': 2 } })
Mongoose: twos.find({})
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.2.state': 1 } })
Mongoose: twos.update({}, { '$inc': { counter: 1 } }, {})
(node:24494) DeprecationWarning: collection.update is deprecated. Use updateOne, updateMany, or bulkWrite instead.
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.2.state': 2 } })
Mongoose: ojlinttaskcollections.deleteOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") })
[
{
"_id": "5bf765f7e5c71c5fae77030e",
"name": "Bill",
"__v": 0
},
{
"_id": "5bf765f7e5c71c5fae77030f",
"counter": 0,
"__v": 0
},
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626877488230301707",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626877488230301707",
"$clusterTime": {
"clusterTime": "6626877488230301707",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
]
Mongoose: ones.find({}, { projection: {} })
[
{
"_id": "5bf765f7e5c71c5fae77030e",
"name": "Bill",
"__v": 0
}
]
Mongoose: twos.find({}, { projection: {} })
[
{
"_id": "5bf765f7e5c71c5fae77030f",
"counter": 1,
"__v": 0
}
]
Mongoose: ojlinttaskcollections.find({}, { projection: {} })



transdemo



Mongoose: orders.deleteMany({}, {})
Mongoose: orderitems.deleteMany({}, {})
Mongoose: orders.insertOne({ _id: ObjectId("5bf7661c3f60105fe48d076e"), name: 'Bill', __v: 0 }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
Mongoose: orderitems.insertMany([ { _id: 5bf7661c3f60105fe48d076f, order: 5bf7661c3f60105fe48d076e, itemName: 'Cheese', price: 1, __v: 0 }, { _id: 5bf7661c3f60105fe48d0770, order: 5bf7661c3f60105fe48d076e, itemName: 'Bread', price: 2, __v: 0 }, { _id: 5bf7661c3f60105fe48d0771, order: 5bf7661c3f60105fe48d076e, itemName: 'Milk', price: 3, __v: 0 } ], { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
Mongoose: orderitems.updateOne({ order: ObjectId("5bf7661c3f60105fe48d076e"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626877647144091652",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626877647144091652",
"$clusterTime": {
"clusterTime": "6626877647144091652",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: orderitems.findOneAndUpdate({ order: ObjectId("5bf7661c3f60105fe48d076e"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334"), upsert: false, remove: false, projection: {}, returnOriginal: false })
{
"_id": "5bf7661c3f60105fe48d0771",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Milk",
"price": 5,
"__v": 0
}
Mongoose: orders.aggregate([ { '$match': { _id: 5bf7661c3f60105fe48d076e } }, { '$lookup': { from: 'orderitems', foreignField: 'order', localField: '_id', as: 'orderitems' } } ], {})
[
{
"_id": "5bf7661c3f60105fe48d076e",
"name": "Bill",
"__v": 0,
"orderitems": [
{
"_id": "5bf7661c3f60105fe48d076f",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Cheese",
"price": 1,
"__v": 0
},
{
"_id": "5bf7661c3f60105fe48d0770",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Bread",
"price": 2,
"__v": 0
},
{
"_id": "5bf7661c3f60105fe48d0771",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Milk",
"price": 4,
"__v": 0
}
]
}
]





share|improve this answer












Using Fawn



The issue is one of usage with the Fawn library and comes from some misconceptions about the naming of mongoose models and how these interact with the library itself. As such the best way to demonstrate is with a minimal example of working code:



const { Schema } = mongoose = require('mongoose');
const Fawn = require('fawn');

const uri = 'mongodb://localhost:27017/fawndemo';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const oneSchema = new Schema({
name: String
});

const twoSchema = new Schema({
counter: Number
});

// don't even need vars since we access model by name
mongoose.model('One', oneSchema);
mongoose.model('Two', twoSchema);

// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));


(async function() {

try {

const conn = await mongoose.connect(uri, opts);

// init fawm
Fawn.init(mongoose);

// Clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)

// run test
let task = Fawn.Task();

let results = await task
.save('One', { name: 'Bill' })
.save('Two', { counter: 0 })
.update('Two', { }, { "$inc": { "counter": 1 } })
.run({ useMongoose: true });

log(results);

// List objects in models
for ( [k,m] of Object.entries(conn.models) ) {
let result = await m.find();
log(result);
}


} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}

})()


Note how the mongoose models are registered here:



mongoose.model('One', oneSchema);
mongoose.model('Two', twoSchema);


That first argument is the registered name which mongoose uses for the model in it's internal logic. From the perspective of mongoose itself, once you have registered the model name with the schema as above, you can actually call an instance of the model as follows:



const One = mongoose.model('One');


Typically people export the result of the initial registration and then just use the returned value which is a reference to mongoose's own internal storage of the model details and attached schema. But the line of code is equivalent to that same thing as long as the registration code has already been run.



A typical exports considering this can therefore be used as:



require('./models/one');
require('./models/two');

let results = await mongoose.model('One').find();


So you might not see that often in other code examples, but that is really to show what is actually happening from the perspective of the Fawn library with later code.



With that knowledge you can consider the following code in the listing:



    let task = Fawn.Task();

let results = await task
.save('One', { name: 'Bill' })
.save('Two', { counter: 0 })
.update('Two', { }, { "$inc": { "counter": 1 } })
.run({ useMongoose: true });


Here the methods of update() and save() familiar to mongoose and MongoDB users actually have a different first argument specific to their implementation on the Fawn.Task() result. That first argument is the "registered model name" for mongoose, which is what we just explained with the previous example.



What the Fawn library is actually doing is calling similar code to:



mongoose.model('One').save({ name: 'Bill' })


Well actually it's doing something a lot more complicated than that as is evidenced in the output of the example listing. It's actually doing a lot of other things related to two phase commits and writing temporary entries in another collection and eventually moving those over to the target collections. But when it does actually go to the collections for the registered models, then that is basically how it is doing it.



So the core issue in the code in the question is that you are not using the names that were actually registered to the mongoose models, and a few other things are missing from the documentation steps.



You're also not awaiting asynchronous functions correctly, and the try..catch within the question code is not doing anything with calls in this context. The listing here however demonstrates how to do that correctly using async/await.



You can alternately just use the native Promise.then(...).catch(...) aproach if your NodeJS version does not have async/await support, but there really is little other change than doing that and of course removing the try..catch since promises in that form will ignore it. Which is why you catch() instead.




NOTE - With some brief testing there appear to be a number of things which are supported mongoose/mongodb features which are not actually implemented and supported on this library's methods. Notably "upserts" was a prime example of a useful and common thing which the "two phase commit" system implemented here does not appear to support at all.



This partly seems an oversight in the code of the library where certain "options" to the methods are actually being ignored or stripped completely. This is a concern for getting the most out of MongoDB features.




Transactions



The whole usage of this library though at least seems suspicious to me that you picked it up because you "thought" this was "Transactions". Put plainly the two phase commit is NOT a transaction. Furthermore the implementation of any attempt at such control and rollback etc seem very loose at best.



If you have a modern MongoDB 4.0 server or above, and where you actually configured it to be named as a "replica set" ( which you can also do for a single member, where a common misconception is you need more than one ) then there is support for real transactions, and they are very easy to implement:



const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/trandemo';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const orderSchema = new Schema({
name: String
});

const orderItemsSchema = new Schema({
order: { type: Schema.Types.ObjectId, ref: 'Order' },
itemName: String,
price: Number
});

const Order = mongoose.model('Order', orderSchema);
const OrderItems = mongoose.model('OrderItems', orderItemsSchema);

// log helper

const log = data => console.log(JSON.stringify(data, undefined, 2));

// main

(async function() {

try {

const conn = await mongoose.connect(uri, opts);

// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)

let session = await conn.startSession();
session.startTransaction();

// Collections must exist in transactions
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.createCollection())
);

let [order] = await Order.create([{ name: 'Bill' }], { session });

let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);

// update an item
let result1 = await OrderItems.updateOne(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ session }
);
log(result1);

// commit
await session.commitTransaction();

// start another
session.startTransaction();

// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);

await session.abortTransaction();

/*
* $lookup join - expect Milk to be price: 4
*
*/

let joined = await Order.aggregate([
{ '$match': { _id: order._id } },
{ '$lookup': {
'from': OrderItems.collection.name,
'foreignField': 'order',
'localField': '_id',
'as': 'orderitems'
}}
]);
log(joined);


} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}

})()


That is really just a simple listing with the class Order and related OrderItems. There really is nothing special in the code and you should see that it's basically the same as most listing examples you will see with a few small changes.



Notably we initialize a session and also session.startTransaction() as an indicator that a transaction should be in progress. Note that session would generally have a wider scope where you would typically re-use that object for more than just a few operations.



Now you have session and the transaction is started, this is simply added to the "options" of the various statements being executed:



let [order] = await Order.create([{ name: 'Bill' }], { session });

let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);


Admittedly this is a brief example that does not fully cover all write error possibilities and how to handle that within separate try..catch blocks. But as a very basic example should any error occur before the session.commitTransaction() is called, then none of the operations since the transaction was started will actually be persisted within the session.



Also there is "causal consistency" in that once a normal write acknowledgement has been confirmed, then within the scope of the session the data appears written to the respective collections right up until the transaction commit or rollback.



In the event of a rollback ( as demonstrated in the final operation ):



// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);

await session.abortTransaction();


These writes though reported to be made as seen in the operation result, are indeed "rolled back" and further operations see the state of the data before these changes were made.



The full example code demonstrates this by adding the items with another update action in one transaction, then beginning another to alter data and read it then abort the transaction. The final data state shows of course only what was actually committed.




NOTE Operations like find() and findOne() or anything that retrieves data must include the session whilst a transaction is active in order to see the current state, just in the same way that write operations are doing as shown in the listing.



Without including the session, these changes in state are not visible in the "global" scope until the transaction is resolved.






Listing Outputs



Code listings given produce the following output when run, for reference.



fawndemo



Mongoose: ones.deleteMany({}, {})
Mongoose: twos.deleteMany({}, {})
Mongoose: ojlinttaskcollections.deleteMany({}, {})
Mongoose: ojlinttaskcollections.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a"), steps: [ { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030d"), index: 0, type: 'save', state: 0, name: 'One', data: { name: 'Bill' } }, { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030c"), index: 1, type: 'save', state: 0, name: 'Two', data: { counter: 0 } }, { dataStore: , _id: ObjectId("5bf765f7e5c71c5fae77030b"), index: 2, type: 'update', state: 0, name: 'Two', data: { '*_**ojlint**escape$*__tx__00***___string$inc': { counter: 1 } } } ], __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.0.state': 1 } })
Mongoose: ones.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030e"), name: 'Bill', __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.0.state': 2 } })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.1.state': 1 } })
Mongoose: twos.insertOne({ _id: ObjectId("5bf765f7e5c71c5fae77030f"), counter: 0, __v: 0 })
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.1.state': 2 } })
Mongoose: twos.find({})
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.2.state': 1 } })
Mongoose: twos.update({}, { '$inc': { counter: 1 } }, {})
(node:24494) DeprecationWarning: collection.update is deprecated. Use updateOne, updateMany, or bulkWrite instead.
Mongoose: ojlinttaskcollections.updateOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") }, { '$set': { 'steps.2.state': 2 } })
Mongoose: ojlinttaskcollections.deleteOne({ _id: ObjectId("5bf765f7e5c71c5fae77030a") })
[
{
"_id": "5bf765f7e5c71c5fae77030e",
"name": "Bill",
"__v": 0
},
{
"_id": "5bf765f7e5c71c5fae77030f",
"counter": 0,
"__v": 0
},
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626877488230301707",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626877488230301707",
"$clusterTime": {
"clusterTime": "6626877488230301707",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
]
Mongoose: ones.find({}, { projection: {} })
[
{
"_id": "5bf765f7e5c71c5fae77030e",
"name": "Bill",
"__v": 0
}
]
Mongoose: twos.find({}, { projection: {} })
[
{
"_id": "5bf765f7e5c71c5fae77030f",
"counter": 1,
"__v": 0
}
]
Mongoose: ojlinttaskcollections.find({}, { projection: {} })



transdemo



Mongoose: orders.deleteMany({}, {})
Mongoose: orderitems.deleteMany({}, {})
Mongoose: orders.insertOne({ _id: ObjectId("5bf7661c3f60105fe48d076e"), name: 'Bill', __v: 0 }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
Mongoose: orderitems.insertMany([ { _id: 5bf7661c3f60105fe48d076f, order: 5bf7661c3f60105fe48d076e, itemName: 'Cheese', price: 1, __v: 0 }, { _id: 5bf7661c3f60105fe48d0770, order: 5bf7661c3f60105fe48d076e, itemName: 'Bread', price: 2, __v: 0 }, { _id: 5bf7661c3f60105fe48d0771, order: 5bf7661c3f60105fe48d076e, itemName: 'Milk', price: 3, __v: 0 } ], { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
Mongoose: orderitems.updateOne({ order: ObjectId("5bf7661c3f60105fe48d076e"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334") })
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626877647144091652",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626877647144091652",
"$clusterTime": {
"clusterTime": "6626877647144091652",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: orderitems.findOneAndUpdate({ order: ObjectId("5bf7661c3f60105fe48d076e"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("e146c6074bb046faa7b70ed787e1a334"), upsert: false, remove: false, projection: {}, returnOriginal: false })
{
"_id": "5bf7661c3f60105fe48d0771",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Milk",
"price": 5,
"__v": 0
}
Mongoose: orders.aggregate([ { '$match': { _id: 5bf7661c3f60105fe48d076e } }, { '$lookup': { from: 'orderitems', foreignField: 'order', localField: '_id', as: 'orderitems' } } ], {})
[
{
"_id": "5bf7661c3f60105fe48d076e",
"name": "Bill",
"__v": 0,
"orderitems": [
{
"_id": "5bf7661c3f60105fe48d076f",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Cheese",
"price": 1,
"__v": 0
},
{
"_id": "5bf7661c3f60105fe48d0770",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Bread",
"price": 2,
"__v": 0
},
{
"_id": "5bf7661c3f60105fe48d0771",
"order": "5bf7661c3f60105fe48d076e",
"itemName": "Milk",
"price": 4,
"__v": 0
}
]
}
]






share|improve this answer












share|improve this answer



share|improve this answer










answered Nov 23 at 2:39









Neil Lunn

96.9k22170181




96.9k22170181












  • Neil, I have renamed to the exact same name as registered.
    – Eesa Munir
    Nov 23 at 17:06










  • @EesaMunir You have complete program listings and also included output as the result of "me running those programs". What you are expected to do is to use those same listings yourself in a fresh install and see them working for yourself. You can "compare" to your code to see what you are doing wrong. But we will not debug your entire project. You also have some really good advice here that you would "appear" to have chosen some library on the presumption it gave you "transactions". It does not, and you have instruction on how to do it correctly. I consider your question answered.Learn from it.
    – Neil Lunn
    Nov 23 at 20:12










  • @EesaMunir Please stop sending comments. You can see they are being removed and I have said all that is needed to be said on the subject. You should run the code in the answer, see that it works and learn from it, accept the answer and move on. And most importantly READ the answer, because the point of this site is not about "getting code from people" but learning instead.
    – Neil Lunn
    Nov 24 at 20:53




















  • Neil, I have renamed to the exact same name as registered.
    – Eesa Munir
    Nov 23 at 17:06










  • @EesaMunir You have complete program listings and also included output as the result of "me running those programs". What you are expected to do is to use those same listings yourself in a fresh install and see them working for yourself. You can "compare" to your code to see what you are doing wrong. But we will not debug your entire project. You also have some really good advice here that you would "appear" to have chosen some library on the presumption it gave you "transactions". It does not, and you have instruction on how to do it correctly. I consider your question answered.Learn from it.
    – Neil Lunn
    Nov 23 at 20:12










  • @EesaMunir Please stop sending comments. You can see they are being removed and I have said all that is needed to be said on the subject. You should run the code in the answer, see that it works and learn from it, accept the answer and move on. And most importantly READ the answer, because the point of this site is not about "getting code from people" but learning instead.
    – Neil Lunn
    Nov 24 at 20:53


















Neil, I have renamed to the exact same name as registered.
– Eesa Munir
Nov 23 at 17:06




Neil, I have renamed to the exact same name as registered.
– Eesa Munir
Nov 23 at 17:06












@EesaMunir You have complete program listings and also included output as the result of "me running those programs". What you are expected to do is to use those same listings yourself in a fresh install and see them working for yourself. You can "compare" to your code to see what you are doing wrong. But we will not debug your entire project. You also have some really good advice here that you would "appear" to have chosen some library on the presumption it gave you "transactions". It does not, and you have instruction on how to do it correctly. I consider your question answered.Learn from it.
– Neil Lunn
Nov 23 at 20:12




@EesaMunir You have complete program listings and also included output as the result of "me running those programs". What you are expected to do is to use those same listings yourself in a fresh install and see them working for yourself. You can "compare" to your code to see what you are doing wrong. But we will not debug your entire project. You also have some really good advice here that you would "appear" to have chosen some library on the presumption it gave you "transactions". It does not, and you have instruction on how to do it correctly. I consider your question answered.Learn from it.
– Neil Lunn
Nov 23 at 20:12












@EesaMunir Please stop sending comments. You can see they are being removed and I have said all that is needed to be said on the subject. You should run the code in the answer, see that it works and learn from it, accept the answer and move on. And most importantly READ the answer, because the point of this site is not about "getting code from people" but learning instead.
– Neil Lunn
Nov 24 at 20:53






@EesaMunir Please stop sending comments. You can see they are being removed and I have said all that is needed to be said on the subject. You should run the code in the answer, see that it works and learn from it, accept the answer and move on. And most importantly READ the answer, because the point of this site is not about "getting code from people" but learning instead.
– Neil Lunn
Nov 24 at 20:53




















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.





Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


Please pay close attention to the following guidance:


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53438087%2fcollections-missing-with-fawn-transactions%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

What visual should I use to simply compare current year value vs last year in Power BI desktop

Alexandru Averescu

Trompette piccolo