Battle of the ORM
V
JavaScript ORM
• ORM: Object/relational impedance mismatch
• DAO: Centralized data access
• In JavaScript: A really easy way to configure
collections
Comparison Points
• Getting setup
• Applying constraints
• White/blacklisting properties, data visibility
• Document associations
• Lifecycle hooks
Mongoose Setup
var mongoose=require('mongoose'),

Foo=mongoose.model('Foo',{name:String});

function setupFoo(){

var foo=new Foo({name:'Test Foo'});

foo.save(function(err,foo){

if(err) return console.log(err);

Foo.findOne({_id:foo._id})

.exec(function(err,foo){

if(err) return console.log(err);

if(!foo) console.log(

'Mongoose returns null when findOne returns nothing.');

console.log('Got foo from database,
%s.',JSON.stringify(foo,null,2));

process.exit(0);

});

});

}

mongoose.connect('mongodb://localhost/test');

var db = mongoose.connection;

db.on('error', console.error.bind(console, 'connection error:'));

db.once('open', function () {

setupFoo();

});
Waterline Setup
var Waterline=require('waterline'),orm=new Waterline(),

mongoAdapter=require('sails-mongo'),

config={

adapters:{default:mongoAdapter,mongo:mongoAdapter},

connections:{

localhostMongo:{

adapter: 'mongo',host: 'localhost',port: 27017,database: 'test'

}}};

orm.loadCollection(Waterline.Collection.extend({

identity: 'foo',

connection: 'localhostMongo',

attributes: {name: 'string'}

}));

function setupFoo(Foo){

Foo.create({name:'Test Foo'}).then(function(foo){

Foo.findOne().where({id:foo._id}).then(function(foo){

if(!foo) console.log(

'Waterline returns undefined when findOne returns nothing.');

console.log('Got foo from database, %s.',JSON.stringify(foo,null,2));

process.exit(0);

});

});

}

orm.initialize(config,function(err,models){

if(err) return console.log(err);

setupFoo(models.collections.foo);

});
Setup Results
/usr/local/bin/node mongoose.js
Got foo from database, {
"_id": "558646750d141363a10539ee",
"name": "Test Foo",
"__v": 0
}.
Process finished with exit code 0
/usr/local/bin/node waterline.js
Got foo from database, {
"name": "Test Foo",
"createdAt": "2015-06-21T05:07:35.012Z",
"updatedAt": "2015-06-21T05:07:35.012Z",
"id": "5586469768455964a130adf4"
}.
Process finished with exit code 0
Round 1: Setup
Mongoose
• Fast to setup
• Uses callbacks natively
• Thin wrapper for mongodb
driver
• Provides __v property for
document versioning
Waterline
• Slightly more configuration
• Uses promises natively
• Abstracts data store from
document definition
• Provides created & modified
timestamps
Mongoose Constraints
Foo=mongoose.model('Foo',{name:{type:String,required:true}});

…
function testConstraints(){

var foo=new Foo({});

foo.save(function(err){

console.log(err);

process.exit(1);

});

}

…
db.once('open', function () {

testConstraints();

});
/usr/local/bin/node mongoose.js
{ [ValidationError: Foo validation failed]
message: 'Foo validation failed',
name: 'ValidationError',
errors:
{ name:
{ [ValidatorError: Path `name` is required.]
properties: [Object],
message: 'Path `name` is required.',
name: 'ValidatorError',
kind: 'required',
path: 'name',
value: undefined } } }
Process finished with exit code 1
Waterline Constraints
orm.loadCollection(Waterline.Collection.extend({

identity: 'foo',

connection: 'localhostMongo',

attributes: {name:{type:'string',required:true}}

}));

…
function testConstraints(Foo){

Foo.create({}).catch(function(err){

console.log(err);

process.exit(1);

});

}

orm.initialize(config,function(err,models){

if(err) return console.log(err);

testConstraints(models.collections.foo);

});
/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
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
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);

});

})

}
/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
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
Waterline Visibility
orm.loadCollection(Waterline.Collection.extend({

identity: 'foo',

connection: 'localhostMongo',

attributes: {

name:{type:'string',required:true},

secret:{type:'string'},

toJSON: function() {

var foo= this.toObject();

delete foo.secret;

return foo;

}

}

}));
…
function testSecret(Foo){

Foo.create({name:'Test Foo',secret:123456}).then(function(foo){

console.log(
'Saved foo to database, %s.',JSON.stringify(foo,null,2));

})

}
/usr/local/bin/node waterline.js
Saved foo to database, {
"name": "Test Foo",
"createdAt": "2015-06-21T06:30:44.374Z",
"updatedAt": "2015-06-21T06:30:44.374Z",
"id": "55865a146954cbb4a16fe33e"
}.
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
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);

});

});

});

});

}
/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
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);

});

});

});

}
/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
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
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

Big Data Day LA 2015 - Mongoose v/s Waterline: Battle of the ORM by Tim Fulmer of HopSkipDrive

  • 1.
  • 2.
    JavaScript ORM • ORM:Object/relational impedance mismatch • DAO: Centralized data access • In JavaScript: A really easy way to configure collections
  • 3.
    Comparison Points • Gettingsetup • Applying constraints • White/blacklisting properties, data visibility • Document associations • Lifecycle hooks
  • 4.
    Mongoose Setup var mongoose=require('mongoose'),
 Foo=mongoose.model('Foo',{name:String});
 functionsetupFoo(){
 var foo=new Foo({name:'Test Foo'});
 foo.save(function(err,foo){
 if(err) return console.log(err);
 Foo.findOne({_id:foo._id})
 .exec(function(err,foo){
 if(err) return console.log(err);
 if(!foo) console.log(
 'Mongoose returns null when findOne returns nothing.');
 console.log('Got foo from database, %s.',JSON.stringify(foo,null,2));
 process.exit(0);
 });
 });
 }
 mongoose.connect('mongodb://localhost/test');
 var db = mongoose.connection;
 db.on('error', console.error.bind(console, 'connection error:'));
 db.once('open', function () {
 setupFoo();
 });
  • 5.
    Waterline Setup var Waterline=require('waterline'),orm=newWaterline(),
 mongoAdapter=require('sails-mongo'),
 config={
 adapters:{default:mongoAdapter,mongo:mongoAdapter},
 connections:{
 localhostMongo:{
 adapter: 'mongo',host: 'localhost',port: 27017,database: 'test'
 }}};
 orm.loadCollection(Waterline.Collection.extend({
 identity: 'foo',
 connection: 'localhostMongo',
 attributes: {name: 'string'}
 }));
 function setupFoo(Foo){
 Foo.create({name:'Test Foo'}).then(function(foo){
 Foo.findOne().where({id:foo._id}).then(function(foo){
 if(!foo) console.log(
 'Waterline returns undefined when findOne returns nothing.');
 console.log('Got foo from database, %s.',JSON.stringify(foo,null,2));
 process.exit(0);
 });
 });
 }
 orm.initialize(config,function(err,models){
 if(err) return console.log(err);
 setupFoo(models.collections.foo);
 });
  • 6.
    Setup Results /usr/local/bin/node mongoose.js Gotfoo from database, { "_id": "558646750d141363a10539ee", "name": "Test Foo", "__v": 0 }. Process finished with exit code 0 /usr/local/bin/node waterline.js Got foo from database, { "name": "Test Foo", "createdAt": "2015-06-21T05:07:35.012Z", "updatedAt": "2015-06-21T05:07:35.012Z", "id": "5586469768455964a130adf4" }. Process finished with exit code 0
  • 7.
    Round 1: Setup Mongoose •Fast to setup • Uses callbacks natively • Thin wrapper for mongodb driver • Provides __v property for document versioning Waterline • Slightly more configuration • Uses promises natively • Abstracts data store from document definition • Provides created & modified timestamps
  • 8.
    Mongoose Constraints Foo=mongoose.model('Foo',{name:{type:String,required:true}});
 … function testConstraints(){
 varfoo=new Foo({});
 foo.save(function(err){
 console.log(err);
 process.exit(1);
 });
 }
 … db.once('open', function () {
 testConstraints();
 });
  • 9.
    /usr/local/bin/node mongoose.js { [ValidationError:Foo validation failed] message: 'Foo validation failed', name: 'ValidationError', errors: { name: { [ValidatorError: Path `name` is required.] properties: [Object], message: 'Path `name` is required.', name: 'ValidatorError', kind: 'required', path: 'name', value: undefined } } } Process finished with exit code 1
  • 10.
    Waterline Constraints orm.loadCollection(Waterline.Collection.extend({
 identity: 'foo',
 connection:'localhostMongo',
 attributes: {name:{type:'string',required:true}}
 }));
 … function testConstraints(Foo){
 Foo.create({}).catch(function(err){
 console.log(err);
 process.exit(1);
 });
 }
 orm.initialize(config,function(err,models){
 if(err) return console.log(err);
 testConstraints(models.collections.foo);
 });
  • 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}
 });
 … functiontestSecret(){
 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 footo 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=newmongoose.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
  • 16.
    Waterline Visibility orm.loadCollection(Waterline.Collection.extend({
 identity: 'foo',
 connection:'localhostMongo',
 attributes: {
 name:{type:'string',required:true},
 secret:{type:'string'},
 toJSON: function() {
 var foo= this.toObject();
 delete foo.secret;
 return foo;
 }
 }
 })); … function testSecret(Foo){
 Foo.create({name:'Test Foo',secret:123456}).then(function(foo){
 console.log( 'Saved foo to database, %s.',JSON.stringify(foo,null,2));
 })
 }
  • 17.
    /usr/local/bin/node waterline.js Saved footo database, { "name": "Test Foo", "createdAt": "2015-06-21T06:30:44.374Z", "updatedAt": "2015-06-21T06:30:44.374Z", "id": "55865a146954cbb4a16fe33e" }.
  • 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);
 … functiontestAssociations(){
 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 barfrom 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 barto 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 • MongoORM 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