Entities on Node.js

/thanpolas/entity

Entities use

  • CIP
    • for Classical Inheritance
    •    /thanpolas/cip
  • Bluebird
    • for the 100% Promises API
    •    /petkaantonov/bluebird
  • Middlewarify
    • for creating middleware
    •    /thanpolas/middlewarify

@thanpolas

Entities extend

events.EventEmitter

...and that's the only thing they do

@thanpolas

Creating an Entity

var entity = require('entity');

var EntityChild = entity.extend(function() {
  this.a = 1;
});

var EntityGrandChild = EntityChild.extend();

entity.extend

var entity = require('entity');

var UserEntity = entity.extendSingleton(function() {});

/* ... */

var userEnt = UserEntity.getInstance();

entity.extendSingleton

@thanpolas

Entities Adaptors

Mongoose

  • MongoDB ORM
  • http://mongoosejs.com/

Sequelize

  • PostgreSQL
  • MySQL
  • MariaDB
  • SQLite
  • http://sequelizejs.com/

@thanpolas

CRUD Primitives

create(data)

read(query=)

readOne(query)

readLimit(?query, offset, limit)

update(query, updateValues)

delete(query)

count(query=)

@thanpolas

CRUD Primitives

create()

entity.create({name: 'thanasis'})
  .then(function(udo) {
    udo.name === 'thanasis'; // true
  })
  .catch(function(error) {
    // deal with error.
  });

... so on and so forth ...

@thanpolas

Entity Hooks

Middlewarify in action

  • before
  • after
  • last

@thanpolas

Entity Hooks

before

// a middleware with synchronous resolution
entity.read.before(function(data){
  if (!data.name) {
    throw new TypeError('No go my friend');
  }
});

// then...
entity.read({}).then(function(document) {
  // you'll never get here
}, function(err) {
  err instanceof Error; // true
  err.message === 'No go my friend'; // true
});

@thanpolas

Hooks are FiFo

Order MATTERS

@thanpolas

Hooks are Middleware

@thanpolas

Before Hooks

Get the exact same number or arguments

After & Last Hooks

Gets the result plus the original number or arguments

@thanpolas

Entity Hooks

Asynchronicity

entity.create.before(function(data){
  return promiseReturningFn(function(result) {
     resolve(result + 1);
  });
});

@thanpolas

Extending Entities

adding new methods

@thanpolas

Extending Entities

Just use the prototype

var Entity = require('entity');

var UserEntity = module.exports = Entity.extend();

UserEntity.prototype.report = function(userId) {
  return promiseReturningAction(userId);
};

@thanpolas

Extending Entities

Using method()

var Entity = require('entity');

var UserEntity = module.exports = Entity.extend(function() {
  this.method('report', this._report.bind(this));

  this.report.before(this._checkUserId.bind(this));
  this.report.after(this._normalize.bind(this));
});

UserEntity.prototype._report = function(userId) {
  return promiseReturningAction(userId);
};

@thanpolas

Let's combine all up

@thanpolas

var ClipEntity = module.exports = EntityBase.extendSingleton(function() {
  this.setModel(clipModel.Model);

  this.method('readOneApi', this.readOne);
  this.method('readLimitApi', this.readLimit);
  this.method('updateApi', this.update);
  this.method('readApi', this.read);

  // Apply system wide (global) filters
  this.readLimitApi.before(this.systemFilter.bind(this));
  this.readOneApi.before(this.systemFilter.bind(this));

  // Clip Creation middleware
  this.create.before(this._populateActiveEvent.bind(this));
  this.create.after(this._processNewClip.bind(this));

  // Record sanitization middleware
  this.updateApi.after(helpers.skipArgs(this.sanitizeResult, 2, this));
  this.readLimitApi.after(helpers.skipArgs(this.sanitizeResults, 3, this));
  this.readOneApi.after(helpers.skipArgs(this.sanitizeResult, 1, this));
});

A Production-ish Entity

@thanpolas

Entity Hands On

this.readLimitApi.before(this.systemFilter.bind(this));
/**
 * Apply system filters in all incoming READ queries 
 * to exclude deleted and corrupt items.
 *
 * @param {Object} query The query.
 */
ClipEntity.prototype.systemFilter = function(query) {
  query.notFound = { ne: true };
  query.processed = true;
};

@thanpolas

Entity Hands On

this.create.after(this._processNewClip.bind(this));
/**
 * Post creation clip processing.
 *
 * @param {Object} data Item used to create the record.
 * @param {app.entity.ClipProcess} processEnt The process entity.
 * @param {mongoose.Document} clipItem The result.
 * @return {Promise} A promise.
 * @private
 */
ClipEntity.prototype._processNewClip = Promise.method(function(data, processEnt,
  clipItem) {
  processEnt.clipId = clipItem.id;
  log.finest('_processNewClip() :: Clip Saved to DB, starting FS save...', clipItem.id);

  return this._checkWatermarkAndStore(processEnt, clipItem)
    .bind(processEnt)
    .then(processEnt.createThumbnail)
    .then(processEnt.checkS3)
    .then(processEnt.updateDatabase)
    .then(function() {
      log.fine('_processNewClip() :: Clip processing finished:', 
        processEnt.sourceFilePath);
    })
    .catch(processEnt.moveToTrash)
    .catch(processEnt._deleteRecord);
});

@thanpolas

Now let's get crazy

@thanpolas

Entities

+

Crude

  • POST /user
  • GET /user
  • GET /user/:id
  • PUT /user/:id
  • PATCH /user/:id
  • DELETE /user/:id

/thanpolas/crude

@thanpolas

... but not today

@thanpolas

Thank you

(here is where you applaud)

Thanasis Polychronakis

@thanpolas

speakerdeck.com/thanpolas

Questions?

Be a critic

Thanasis Polychronakis

@thanpolas

speakerdeck.com/thanpolas

Shameless Plug Time

Promo Code

bgwebsummit

From 60€ --> 40€

15/5/2015

@ Thessaloniki Greece

devitconf.org

Entities on Node.js, BG WebSummit

By thanpolas

Entities on Node.js, BG WebSummit

The Entities implementation for Node.js

  • 867