Collections Missing with Fawn Transactions
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
|
show 7 more comments
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
mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true})
is invalid and would throw an error. You need to include the port with theuseNewUrlParser
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 yourvidly
database when in compass, or of course you are looking inrental
when mongoose is actually usingrentals
. 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
|
show 7 more comments
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
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
javascript node.js mongodb mongoose
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 theuseNewUrlParser
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 yourvidly
database when in compass, or of course you are looking inrental
when mongoose is actually usingrentals
. 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
|
show 7 more comments
mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true})
is invalid and would throw an error. You need to include the port with theuseNewUrlParser
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 yourvidly
database when in compass, or of course you are looking inrental
when mongoose is actually usingrentals
. 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
|
show 7 more comments
1 Answer
1
active
oldest
votes
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()
andfindOne()
or anything that retrieves data must include thesession
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
}
]
}
]
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
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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()
andfindOne()
or anything that retrieves data must include thesession
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
}
]
}
]
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
add a comment |
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()
andfindOne()
or anything that retrieves data must include thesession
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
}
]
}
]
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
add a comment |
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()
andfindOne()
or anything that retrieves data must include thesession
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
}
]
}
]
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()
andfindOne()
or anything that retrieves data must include thesession
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
}
]
}
]
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
add a comment |
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
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
mongoose.connect('mongodb://localhost/vidly', { useNewUrlParser: true})
is invalid and would throw an error. You need to include the port with theuseNewUrlParser
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 inrental
when mongoose is actually usingrentals
. 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