Mongo is swiftly becoming the default, general purpose database du jour. In green field Node.JS systems, Mongo is almost the default persistence mechanism. At the same time, going completely schema-less raises it's own issues.
Enter the NoSQL ORM solution. By encoding schema information in easily changeable JavaScript definitions, Node.JS systems can get the benefits of both worlds. High velocity change throughput, with just enough guardrails to keep things on the tracks.
Mongoose has traditionally been the go-to ORM package, providing lightweight schema definitions on top of Mongo. Waterline has recently come out of the Sails.JS project with some interesting innovations. In this presentation we compare and contrast the two packages through a few typical ORM use cases.
2. JavaScript ORM
• ORM: Object/relational impedance mismatch
• DAO: Centralized data access
• In JavaScript: A really easy way to configure
collections
11. /usr/local/bin/node waterline.js
Error (E_VALIDATION) :: 1 attribute is invalid
at WLValidationError.WLError (techdt.la-mvw/node_modules/waterline/
lib/waterline/error/WLError.js:26:15)
…
Invalid attributes sent to foo:
• name
• `undefined` should be a string (instead of "null", which is a
object)
• "required" validation rule failed for input: null
Process finished with exit code 1
12. Round 2: Constraints
Mongoose
• Flexible constraints to handle
most situations
• Easy to setup
• Somewhat hard to read
validation error messages
• Does not provide stack
information
Waterline
• Flexible constraints to handle
most situations
• Easy to setup
• Easy to read validation error
messages
• Provides stack information to
help debugging
13. Mongoose Visibility [1]
Foo=mongoose.model('Foo',{
name:{type:String,required:true},
secret:{type:String,select:false}
});
…
function testSecret(){
var foo=new Foo({name:'Test Foo',secret:'123456'});
foo.save(function(err,foo){
if(err) return console.log(err);
console.log(
'Saved foo to database, %s.',JSON.stringify(foo,null,2));
Foo.findOne({_id:foo._id})
.exec(function(err,foo){
if(err) return console.log(err);
if(!foo) console.log(
'Mongoose returns undefined when findOne returns nothing.');
console.log(
'Got foo from database, %s.',JSON.stringify(foo,null,2));
process.exit(0);
});
})
}
14. /usr/local/bin/node mongoose.js
Saved foo to database, {
"__v": 0,
"name": "Test Foo",
"secret": "123456",
"_id": "55864fdf4e5d5e94a1af5a14"
}.
Got foo from database, {
"_id": "55864fdf4e5d5e94a1af5a14",
"name": "Test Foo",
"__v": 0
}.
Process finished with exit code 0
15. Mongoose Visibility [2]
FooSchema=new mongoose.Schema({
name:{type:String,required:true},
secret:{type:String,select:false}
},{toJSON:{transform: function (doc,ret) {delete ret.secret;}}}
),
Foo=mongoose.model('Foo',FooSchema);
/usr/local/bin/node mongoose.js
Saved foo to database, {
"__v": 0,
"name": "Test Foo",
"_id": "558654d2945f15a1a1a2d840"
}.
Got foo from database, {
"_id": "558654d2945f15a1a1a2d840",
"name": "Test Foo",
"__v": 0
}.
Process finished with exit code 0
18. Round 3: Visibility
Mongoose
• Two methods to hide
document properties
• One doesn’t work in all cases
• The other is a bit tough to
configure
Waterline
• One method to hide document
properties
• Works in all cases
• Simple to configure
19. Mongoose Associations
BarSchema=new mongoose.Schema({
name:String,foo:{type:mongoose.Schema.Types.ObjectId,ref:'Foo'}
}),
Bar=mongoose.model('Bar',BarSchema);
…
function testAssociations(){
var foo=new Foo({name:'Test Foo'});
foo.save(function(err,foo){
if(err) return console.log(err);
var bar=new Bar({name:'Test Bar',foo:foo});
bar.save(function(err,bar){
if(err) return console.log(err);
Bar.findOne({_id:bar._id},function(err,bar){
console.log(
'Got bar from database %s.',JSON.stringify(bar,null,2));
bar.populate('foo',function(err,bar){
if(err) return console.log(err);
console.log(
'Populated foo on bar %s.',JSON.stringify(bar,null,2));
process.exit(0);
});
});
});
});
}
20. /usr/local/bin/node mongoose.js
Got bar from database {
"_id": "55865d0e96b702dba1cecc85",
"name": "Test Bar",
"foo": "55865d0d96b702dba1cecc84",
"__v": 0
}.
Populated foo from database {
"_id": "55865d0e96b702dba1cecc85",
"name": "Test Bar",
"foo": {
"_id": "55865d0d96b702dba1cecc84",
"name": "Test Foo",
"__v": 0
},
"__v": 0
}.
Process finished with exit code 0
21. Waterline Associations
orm.loadCollection(Waterline.Collection.extend({
identity: 'bar',
connection: 'localhostMongo',
attributes: {
name:{type:'string',required:true},
foo:{model:'foo'}
}
}));
…
function testAssociations(Foo,Bar){
Foo.create({name:'Test Foo'}).then(function(foo){
Bar.create({name:'Test Bar',foo:foo}).then(function(bar){
console.log('Saved bar to database %s.',JSON.stringify(bar,0,2));
Bar.findOne({id:bar.id}).populate('foo').then(function(bar){
console.log('Populated foo on bar %s.',JSON.stringify(bar,0,2));
process.exit(0);
});
});
});
}
22. /usr/local/bin/node waterline.js
Saved bar to database {
"name": "Test Bar",
"foo": "558660a765f025eba1fbb7af",
"createdAt": "2015-06-21T06:58:47.079Z",
"updatedAt": "2015-06-21T06:58:47.079Z",
"id": "558660a765f025eba1fbb7b0"
}.
Populated foo on bar {
"foo": {
"name": "Test Foo",
"createdAt": "2015-06-21T06:58:47.071Z",
"updatedAt": "2015-06-21T06:58:47.071Z",
"id": "558660a765f025eba1fbb7af"
},
"name": "Test Bar",
"createdAt": "2015-06-21T06:58:47.079Z",
"updatedAt": "2015-06-21T06:58:47.079Z",
"id": "558660a765f025eba1fbb7b0"
}.
Process finished with exit code 0
23. Round 4: Associations
Mongoose
• Easy to setup document
associations
• Can call populate on find, or
on a document
• Associated documents may or
may not be populated
Waterline
• Easy to setup document
associations
• Associated documents may
live in other databases
• Can only call populate on find
• Associated documents only
populated when requested
24. Wrap Up
Mongoose
• Mongo ORM for JavaScript
• Has some rough edges
• Only option for highly
concurrent document updates
• Consistently more code to do
the same things
Waterline
• JavaScript ORM for many
popular databases
• More modern, today
JavaScript framework
• Less rough edges
• Does not support document
isolation