Patterns & Javascript

2017 Edition

Hi, I am Thanasis

  • Nearly 50 NPM published packages.
  • Over 100 OSS repos.
  • A zillion contributions in major and small OSS projects.
  • Retired troll.

Patterns

On patterns...

When you trust your patterns and you loyally abide by them, then the only problems you will have will be so big you can't miss them

On Patterns...

  • Being opinionated has nothing to do with patterns. 
  • You should have a strong, pragmatic reason behind every pattern, every choice you make.
  • Follow best practices until you know why they are there.
  • Non-conformism is a lifestyle, we are doing engineering.
  • Patterns are a living, breathing organism that adjusts to its environment.

In Javascript, patterns will save your ass!

Today's Menu

  • Module composition & types
  • Inheritance Patterns
  • Working with Promises

The Module Layout

Module Description

Dependencies

System Deps

NPM Deps

Project Deps

Export Statement

/**
 * @fileOverview Create Camp Configuration mutation.
 */
const crypto = require('crypto');
const url = require('url');

const { coroutine } = require('bluebird');
const { GraphQLError } = require('graphql/error');
const {
  GraphQLString,
  GraphQLNonNull,
} = require('graphql');


const campConfigTypes = require('../types/camp-config.type');
const campConfigModel = require('../models/camp-config.model');
const accountModel = require('../../account/model');
const log = require('../../../logger').getChild(__filename);
const {
  mutation,
  mutationInputType,
  mutationPayloadType,
  isAdmin,
} = require('../../../schema/helpers');
const {
  RecordIDType,
  CurrencyInputType,
} = require('../../../schema/types');

/**
 * Create Camp Configuration mutation.
 * 
 * @type {Object}
 */
const mut = module.exports = {};

Spread statements after whole module ones

The Dependencies

  1. System Dependencies
  2. NPM Dependencies
  3. Project Dependencies
/**
 * @fileOverview Cors Middleware.
 */
var util = require('util');
var fs = require('fs');

var Promise = require('bluebird');
var _ = require('lodash');

var Middleware = require('./middleware');
var helpers = require('../util/helpers');

1.

2.

3.

Method Definition

/**
 * Check if provided viewer can mutate the defined album.
 *
 * @param {Object} viewer The viewer account object.
 * @param {string} albumUuid The album uuid to be mutated.
 * @param {string} mutationName A human readable unique name to identify
 *   the mutation.
 * @return {Promise(Object)} A Promise with two keys:
 *   @param {Object} albumToUpdate The album to be updated.
 *   @param {Object} albumOwner The album owner.
 * @throws {GraphQLError} if no privileges.
 */
albumPrivs.canMutate = Promise.method(function (viewer, albumUuid,
  mutationName) {

The Module Types

Constructors

Objects

Only 2 types of Modules

Module Types

Constructors

const Cors = module.exports = function(){
  Middleware.apply(this, arguments);
};
util.inherits(Cors, Middleware);

Objects

const cors = module.exports = {};

Module Types

Constructor

/**
 * The Cors Middleware.
 *
 * @contructor
 * @extends {cc.Middleware}
 */
const Cors = module.exports = function(){
  Middleware.apply(this, arguments);
};
util.inherits(Cors, Middleware);

/**
 * CORS Middleware
 *
 * @param {Object} req The request Object.
 * @param {Object} res The response Object.
 * @param {Function(Error=)} next pass ctrl.
 */
Cors.prototype.allowCrossDomain = function(req, res, next) {...};

Don't freak out! solution incoming...

Module Types

Module

/**
 * The Cors Middleware.
 *
 */
const cors = module.exports = {};

/**
 * CORS Middleware
 *
 * @param {Object} req The request Object.
 * @param {Object} res The response Object.
 * @param {Function(Error=)} next pass ctrl.
 */
cors.use = function (req, res, next) { ... };
  • If you want state --> Ctor
  • If you want inheritance --> Ctor
  • Helpers / Util --> Obj
  • Enums / Dicts --> Obj
  • Config Files --> Obj

When to use which

It's all about the state

Module Inheritance

The classical way

Module Inheritance

The Classical way

var util = require('util');
var EventEmitter = require('events').EventEmitter;

var MiddlewareBase = module.exports = function() {
  EventEmitter.apply(this, arguments);

  this.foo = 1;
};
util.inherits(MiddlewareBase, EventEmitter);
middleware-base.midd.js
var util = require('util');
var MiddleWareBase = require('./middleware-base.midd');

var Cors = module.exports = function() {
  MiddlewareBase.apply(this, arguments);

  console.log(this.foo); // 1
};
util.inherits(Cors, MiddlewareBase);
cors.midd.js

Beware of context

Cors.prototype.use = function(req, res, next) {
  // good
  doSomeAsync(this._handleAsync.bind(this));

  // bad
  doSomeAsync(this._handleAsync);
};

Do not abuse fat arrow

Cors.prototype.use = function(req, res, next) {
  // good
  doSomeAsync((res) => {
    console.log(res);
  });
};

// VERY bad
Cors.prototype.use = (req, res, next) => {

}

function readNews (cb) {
  // Bad - context is not required here
  fetchNews((news) => {
    cb(null, news);
  });
}

Beware of performance

Cors.prototype.use = function(req, res, next) {
  while(true) { // highly repetitive operation
    // good
    var self = this;
    doSomeAsync(function(result) {
      self._handleAsync(result);
    });

    // bad
    doSomeAsync(this._handleAsync.bind(this));
  }
};

Shameless Plug Time

CIP!

CIP

Classical Inheritance Pattern at its Best

var EventEmitter = require('events').EventEmitter;
var cip = require('cip');

var CeventEmitter = cip.cast(EventEmitter);

module.exports = CeventEmitter.extend();

Base Constructors using CIP

const cip = require('cip');

const Ctor module.exports = cip.extend();

Base Usage

CIP

Classical Inheritance Pattern at its Best

const cip = require('cip');

const Ctor module.exports = cip.extend();

/** In another module far far away ... */


const Ctor = require('../foo/ctor');

const CtorChild = module.exports = Ctor.extend(function () {
  /* Constructor */
});

Inheritance using CIP

CIP

Classical Inheritance Pattern at its Best

const MiddlewareBase = require('./middleware-base.midd');

const Cors = module.exports = MiddlewareBase.extendSingleton(function () {
  /* Ctor */
});

/** ... in another module far far away... */

const cors = require('../middleware/cors.midd').getInstance();

cors.use();

Singletons

CIP

Classical Inheritance Pattern at its Best

var MiddlewareBase = require('./middleware-base.midd');
var MiddlewareTraits = require('./middleware-traits.midd');

var Cors = module.exports = MiddlewareBase.extend(function () {
  /* Ctor */
});
Cors.mixin(MiddlewareTraits);

Mixins!

var Cors = require('./cors.midd');

var SuperCors = module.exports = Cors.extend(function () {
  /* Ctor */
});

Inheritance Goes on...

CIP

/thanpolas/cip

Y U NO USE ES2015 CLASSES?

  • Short answer: Lack of async/await.
  • Long answer on next chapter about Promises

Working with Promises

2 + 1 ways to create Promises

  • Novel Promises
  • Chained Promises
  • Coroutines 'n Promises

Novel Promises

Promises

const Promise = require('bluebird');

/* ... */

ClipEntity.prototype.fetchVideos = function() {
    return new Promise(function(resolve, reject) {
        fs.read(file, function(err, res) {
            if (err) {
                reject(err);
            } else {
                resolve(res);
            }
        });
    });
};

Typically interfacing with vanilla callbacks

Chained Promises

Promises

const Promise = require('bluebird');

/* ... */

ClipEntity.prototype.showVideos = Promise.method(function() {
    return this.fetchVideos();
});

When you are only dealing with other promise-returning methods

Coroutine Promises

Promises

const Promise = require('bluebird');

/* ... */

Foo.prototype.bar = Promise.coroutine(function* () {
    const videos = yield this.fetchVideos();
    return videos;
});

Thank you ES2015 and `yield`

Why wrapping matters

Promises

Foo.prototype.bar = function () {
  throw new Error('Unexpected');
});
const Promise = require('bluebird');

Foo.prototype.bar = Promise.method(function() {
  throw new Error('Unexpected');
});

When invoked, kiss your server goodbye!

Everything is under control 

Why wrapping matters

Promises

Well... yea... you can't wrap your ES2015 Class methods...

Composing Promises

Promises

  return dropboxTokenModel.get(this.uid)
    .bind(this)
    .then(this._processAccessToken)
    .then(this._checkDropboxAccess)
    .then(this._startProcess)
    .then(this._generateResponse)
    .then(this._save)
    .then(this._createDownloadJobs)
    .then(this._purgeSyncFilesForKafka)
    .catch((err) => {
      log.error('start() :: Processing Failed:', err);
      throw err;
    });

Summarize your business logic

Advanced Patterns

Promises

  // use a temporary custom context for promise execution chain.
  const context = {
    albumToUpdate: null,
    albumOwner: null,
  };

  return Promise.resolve(albumModel.Album.fetch(viewer, albumUuid))
    .bind(context)
    .then(function (albumToUpdate) {
      if (!albumToUpdate) {
        throw new GraphQLError('Album not found');
      }

      this.albumToUpdate = albumToUpdate;

      return accountModel.Account.fetchById(viewer, albumToUpdate.creator_id);
    })

Custom context

Sanitize unknown promises

Return Custom Value

Promises

ClipEntity.prototype._process = function(data) {
  var clipProcessEnt = new ClipProcessEnt(data);

  return clipProcessEnt.parseFiles()
    .bind(clipProcessEnt)
    .then(clipProcessEnt.isFileVideo)
    .then(clipProcessEnt.storeClip)
    .then(clipProcessEnt.storeThumb)
    .then(clipProcessEnt.updateDatabase)
    .then(clipProcessEnt.removeSourceFiles)
    .return(data)
    .catch(clipProcessEnt.rollback);
};

Running in Parallel

Promises

ClipEntity.prototype._process = function(data) {
  var clipProcessEnt = new ClipProcessEnt(data);

  var promises = [];

  promises.push(clipProcessEnt.isFileVideo());
  promises.push(clipProcessEnt.storeClip());
  promises.push(clipProcessEnt.storeThumb());
  promises.push(clipProcessEnt.updateDatabase());

  return Promise.all(promises)
    .catch(clipProcessEnt.rollback);
};

Running in Parallel Throttled

Promises

ClipEntity.prototype._process = function(data) {
  var clipProcessEnt = new ClipProcessEnt(data);

  var promises = [];

  promises.push(clipProcessEnt.isFileVideo());
  promises.push(clipProcessEnt.storeClip());
  promises.push(clipProcessEnt.storeThumb());
  promises.push(clipProcessEnt.updateDatabase());

  return Promise.all(promises, {concurrency: 10})
    .catch(clipProcessEnt.rollback);
};

Recap

The Patterns employed, is what draws the line between a talented engineer and a copy paste warrior

Thank you

Thanasis Polychronakis

@thanpolas

https://speakerdeck.com/thanpolas

Questions?

Thanasis Polychronakis

@thanpolas

https://speakerdeck.com/thanpolas

Javascript Patterns 2017

By thanpolas

Javascript Patterns 2017

How to author modules in node.js

  • 925