Transcript
Page 1: Build a Node.js Client for Your REST+JSON API

Building a Node.js Clientfor Your REST+JSON API

Les Hazlewood @lhazlewoodCTO, Stormpath stormpath.com

Page 2: Build a Node.js Client for Your REST+JSON API

.com• User Management and Authentication

API• Security for your applications• User security workflows• Security best practices• Developer tools, SDKs, libraries

Page 3: Build a Node.js Client for Your REST+JSON API

Overview• Resources• Public / Private API• Proxy Design• Active Record• Fluent API• Configuration• Caching• Authentication• Pluggability• Lessons Learned

Page 4: Build a Node.js Client for Your REST+JSON API

HATEOAS

• Hypermedia

• As

• The

• Engine

• Of

• Application

• State

Learn more at Stormpath.com

Page 5: Build a Node.js Client for Your REST+JSON API

Resources

Learn more at Stormpath.com

Page 6: Build a Node.js Client for Your REST+JSON API

Resources

• Nouns, not verbs• Coarse-grained, not fine-grained• Support many use cases• Globally unique HREF

Learn more at Stormpath.com

Page 7: Build a Node.js Client for Your REST+JSON API

Collection Resource

• Example: /applications

• First class resource w/ own properties:• offset• limit• items• first, next, previous, last• etc

• items contains instance resources

Learn more at Stormpath.com

Page 8: Build a Node.js Client for Your REST+JSON API

Instance Resource

• Example:/applications/8sZxUoExA30mP74

• Child of a collection• RUD (no Create - done via parent collection)

Learn more at Stormpath.com

Page 9: Build a Node.js Client for Your REST+JSON API

Translating to Code

Learn more at Stormpath.com

Page 10: Build a Node.js Client for Your REST+JSON API

Resource

var util = require('util');

function Resource(...) { ... }util.inherits(Resource, Object);

someResource.href

Learn more at Stormpath.com

Page 11: Build a Node.js Client for Your REST+JSON API

Instance Resourcefunction InstanceResource(...) {...}util.inherits(InstanceResource, Resource);

anInstanceResource.save(function (err, saved) { ...});

anInstanceResource.delete(function (err) { ...});

Learn more at Stormpath.com

Page 12: Build a Node.js Client for Your REST+JSON API

Collection Resourcefunction CollectionResource(...) {...}util.inherits(CollectionResource, Resource);

aCollResource.each(function (item, callback) { ...}, function onCompletion(err) { ... });

aCollResource.eachSeriesaCollResource.mapaCollResource.filter... other async.js methods ...

Learn more at Stormpath.com

Page 13: Build a Node.js Client for Your REST+JSON API

Example: ApplicationListapplications.each(function(app, callback){ console.log(app); callback();}, function finished(err) { if (err) console.log(‘Error: ‘ + err);});

Learn more at Stormpath.com

Page 14: Build a Node.js Client for Your REST+JSON API

Design!

Learn more at Stormpath.com

Page 15: Build a Node.js Client for Your REST+JSON API

Encapsulation

• Public API• Internal/Private Implementations• Extensions

• Allows for change w/ minimal impacthttp://semver.org

Learn more at Stormpath.com

Page 16: Build a Node.js Client for Your REST+JSON API

Encapsulation in practice

• Use an underscore prefix: _• Super clear code comments:

mark @public or @private• Public API docs: don’t display private

classes/methods/functions

Learn more at Stormpath.com

Page 17: Build a Node.js Client for Your REST+JSON API

Public API

Learn more at Stormpath.com

Page 18: Build a Node.js Client for Your REST+JSON API

Public API

• All non-@private functions/vars• Builder methods (method chaining)• Object literals (config) is public too!

Learn more at Stormpath.com

Page 19: Build a Node.js Client for Your REST+JSON API

Public prototypical OO Classes

• Client• ApiKey• Application• Directory• Account• Group• etc

Learn more at Stormpath.com

Page 20: Build a Node.js Client for Your REST+JSON API

Classes with static helper methodsClient client = Clients.builder() ... .build();

• Create multiple helper classesseparation of concerns

Learn more at Stormpath.com

Page 21: Build a Node.js Client for Your REST+JSON API

Builder methods (method chaining)

client.getApplications() .where(name).startsWith(‘*foo’) .orderBy(name).asc() .limit(10) .execute(function (err, apps){ ... });

clients.getApplications() ApplicationRequestBuilder

Single Responsibility Principle!

Learn more at Stormpath.com

Page 22: Build a Node.js Client for Your REST+JSON API

Private API

• Implementations + SPI interfaces• Builder implementations• Implementation Plugins

Learn more at Stormpath.com

Page 23: Build a Node.js Client for Your REST+JSON API

Resource Implementations• Create a base Resource class:• Property manipulation methods• Dirty checking• Reference to DataStore• Lazy Loading

• Create base InstanceResource and CollectionResource implementations

• Extend from InstanceResource or CollectionResource

Learn more at Stormpath.com

Page 24: Build a Node.js Client for Your REST+JSON API

Resourcevar utils = require(’utils');

function Resource(data, dataStore) { var DataStore = require('../ds/DataStore'); if (!dataStore && data instanceof DataStore){ dataStore = data; data = null; } data = data || {};

for (var key in data) { if (data.hasOwnProperty(key)) { this[key] = data[key]; } }

var ds = null; //private var, not enumerable Object.defineProperty(this, 'dataStore', { get: function getDataStore() { return ds; }, set: function setDataStore(dataStore) { ds = dataStore; } }); if (dataStore) { this.dataStore = dataStore; }}utils.inherits(Resource, Object);

module.exports = Resource;

Learn more at Stormpath.com

Page 25: Build a Node.js Client for Your REST+JSON API

InstanceResourcevar utils = require(‘utils');var Resource = require('./Resource');

function InstanceResource() { InstanceResource.super_.apply(this, arguments);}utils.inherits(InstanceResource, Resource);

InstanceResource.prototype.save = function saveResource(callback) { this.dataStore.saveResource(this, callback);};

InstanceResource.prototype.delete = function deleteResource(callback) { this.dataStore.deleteResource(this, callback);};

Learn more at Stormpath.com

Page 26: Build a Node.js Client for Your REST+JSON API

Applicationvar utils = require(‘utils');var InstanceResource = require('./InstanceResource');

function Application() { Application.super_.apply(this, arguments);}utils.inherits(Application, InstanceResource);

Application.prototype.getAccounts = function getApplicationAccounts(/* [options,] callback */) { var self = this; var args = Array.prototype.slice.call(arguments); var callback = args.pop(); var options = (args.length > 0) ? args.shift() : null;

return self.dataStore.getResource(self.accounts.href, options, require('./Account'), callback);};

Learn more at Stormpath.com

Page 27: Build a Node.js Client for Your REST+JSON API

Usage Paradigm

Learn more at Stormpath.com

Page 28: Build a Node.js Client for Your REST+JSON API

Account JSON Resource{ “href”: “https://api.stormpath.com/v1/accounts/x7y8z9”, “givenName”: “Tony”, “surname”: “Stark”, …, “directory”: { “href”: “https://api.stormpath.com/v1/directories/g4h5i6” }}

Learn more at Stormpath.com

Page 29: Build a Node.js Client for Your REST+JSON API

Proxy PatternString href = “https://api.stormpath.com/v1/....”;

client.getAccount(href, function(err, acct) { if (err) throw err;

account.getDirectory(function(err, dir) { if (err) throw err; console.log(dir); });

});

Learn more at Stormpath.com

Page 30: Build a Node.js Client for Your REST+JSON API

Proxy Pattern

Learn more at Stormpath.com

Page 31: Build a Node.js Client for Your REST+JSON API

Component Architecture

Learn more at Stormpath.com

Page 32: Build a Node.js Client for Your REST+JSON API

Component Architectureaccount .save()

Learn more at Stormpath.com

Page 33: Build a Node.js Client for Your REST+JSON API

Component Architectureaccount .save()

DataStore

Learn more at Stormpath.com

Page 34: Build a Node.js Client for Your REST+JSON API

Component Architectureaccount .save()

CacheManager

DataStore

Learn more at Stormpath.com

Page 35: Build a Node.js Client for Your REST+JSON API

Component Architectureaccount .save()

CacheManager

DataStore

CacheCacheCache

Learn more at Stormpath.com

Page 36: Build a Node.js Client for Your REST+JSON API

Component Architectureaccount .save()

RequestExecutorCacheManager

DataStore

CacheCacheCache

Learn more at Stormpath.com

Page 37: Build a Node.js Client for Your REST+JSON API

Component Architectureaccount .save()

RequestExecutor

AuthenticationStrategy

CacheManager

DataStore

RequestAuthenticator

CacheCacheCache

Learn more at Stormpath.com

Page 38: Build a Node.js Client for Your REST+JSON API

Component Architectureaccount

API Server

.save()

RequestExecutor

AuthenticationStrategy

CacheManager

DataStore

RequestAuthenticator

CacheCacheCache

Learn more at Stormpath.com

Page 39: Build a Node.js Client for Your REST+JSON API

Component Architectureaccount

API Server

.save()

RequestExecutor ResourceFactory JSON Resource

AuthenticationStrategy

CacheManager

DataStore

RequestAuthenticator

CacheCacheCache

Learn more at Stormpath.com

Page 40: Build a Node.js Client for Your REST+JSON API

Caching

Learn more at Stormpath.com

Page 41: Build a Node.js Client for Your REST+JSON API

Cachingvar cache = cacheManager.getCache(regionName);

cache.ttl //time to livecache.tti //time to idlecache.get(href, function(err, obj) { ...});

Learn more at Stormpath.com

Page 42: Build a Node.js Client for Your REST+JSON API

Cachingclient.getAccount(href, function(err, acct) {...});

// in the DataStore:var cache = cacheManager.getCache(‘accounts’);cache.get(href, function(err, entry) { if (err) return callback(err); if (entry) { ... omitted for brevity ... return callback(entry.value); } //otherwise, cache miss – execute a request: requestExecutor.get(href, function(err, body) { //1. cache body //2. convert to Resource instance //3. invoke callback w/ instance }}

Learn more at Stormpath.com

Page 43: Build a Node.js Client for Your REST+JSON API

Queries

Learn more at Stormpath.com

Page 44: Build a Node.js Client for Your REST+JSON API

Queriesaccount.getGroups(function(err,groups){...});//results in a request to://https://api.stormpath.com/v1/accounts/a1b2c3/groups

• What about query parameters?

Learn more at Stormpath.com

Page 45: Build a Node.js Client for Your REST+JSON API

Queriesaccount.getGroups( { name: ‘foo*’, description: ‘*test*’, orderBy: ‘name desc’, limit: 100 }, function onResult(err, groups) { ... });

//results in a request to:

https://api.stormpath.com/v1/accounts/a1b2c3/groups? name=foo*&description=*test*&orderBy=name%20desc&limit=100

Learn more at Stormpath.com

Page 46: Build a Node.js Client for Your REST+JSON API

Queries

Use a Fluent API!

Learn more at Stormpath.com

Page 47: Build a Node.js Client for Your REST+JSON API

Queriesaccount.getGroups().where() .name().startsWith(“foo”) .description().contains(“test”) .orderBy(“name”).desc() .limitTo(100));//results in a request to:

https://api.stormpath.com/v1/accounts/a1b2c3/groups? name=foo*&description=*test*&orderBy=name%20desc&limit=100

Learn more at Stormpath.com

Page 48: Build a Node.js Client for Your REST+JSON API

Authentication

Learn more at Stormpath.com

Page 49: Build a Node.js Client for Your REST+JSON API

Authentication• Favor a digest algorithm over HTTP Basic• Prevents Man-in-the-Middle attacks (SSL won’t guarantee

this!)

• Also support Basic for environments that require it (Dammit Google!)• ONLY use Basic over SSL

• Represent this as an AuthenticationScheme to your Client / RequestExecutor

Learn more at Stormpath.com

Page 50: Build a Node.js Client for Your REST+JSON API

Authentication• AuthenticationScheme.SAUTHC1• AuthenticationScheme.BASIC• AuthenticationScheme.OAUTH10a• ... etc ...

Client client = new stormpath.Client({ //defaults to sauthc1 authcScheme: ‘basic’});

Client/RequestExecutor uses a Sauthc1RequestAuthenticator or BasicRequestAuthenticator or OAuth10aRequestAuthenticator, etc.

Learn more at Stormpath.com

Page 51: Build a Node.js Client for Your REST+JSON API

Plugins

Learn more at Stormpath.com

Page 52: Build a Node.js Client for Your REST+JSON API

Plugins

• Plugins or Extensions module• One sub-module per plugin• Keep dependencies to a minimum

plugins/|- request/|- foo/

Learn more at Stormpath.com

Page 53: Build a Node.js Client for Your REST+JSON API

Lessons Learned

Learn more at Stormpath.com

Page 54: Build a Node.js Client for Your REST+JSON API

Lessons Learned

• Recursive caching if you support resource expansion

• Dirty checking logic is not too hard, but it does add complexity. Start off without it.

Learn more at Stormpath.com

Page 55: Build a Node.js Client for Your REST+JSON API

Lessons Learned: Promisesvar promise = account.getGroups();

promise.then(function() { //called on success}, function() { //called on error}, function() { //called during progress});

Learn more at Stormpath.com

Page 56: Build a Node.js Client for Your REST+JSON API

Lessons Learned: async.jsasync.waterfall([ function(callback){ callback(null, 'one', 'two'); }, function(arg1, arg2, callback){ // arg1 now equals 'one' and arg2 now equals 'two' callback(null, 'three'); }, function(arg1, callback){ // arg1 now equals 'three' callback(null, 'done'); }], function (err, result) { // result now equals 'done' });

Learn more at Stormpath.com

Page 57: Build a Node.js Client for Your REST+JSON API

Code

$ git clone https://github.com/stormpath/stormpath-sdk-node.git

$ cd stormpath-sdk-node

$ npm install$ grunt

Learn more at Stormpath.com

Page 58: Build a Node.js Client for Your REST+JSON API

Thank You!

[email protected]• Twitter: @lhazlewood• http://www.stormpath.com

Learn more at Stormpath.com


Recommended