@crichardson
NodeJS: the good parts?
A skeptic’s view
Chris Richardson
Author of POJOs in Action
Founder of the original CloudFoundry.com
@crichardson
chris.richardson@springsource.com
http://plainoldobjects.com
@crichardson
Presentation goal
How a curmudgeonly
server-side Java developer
discovered an appreciation
for NodeJS and JavaScript
@crichardson
About Chris
@crichardson
(About Chris)
@crichardson
About Chris()
@crichardson
About Chris
@crichardson
About Chris
http://www.theregister.co.uk/2009/08/19/springsource_cloud_foundry/
@crichardson
vmc push About-Chris
Developer Advocate
Signup at http://cloudfoundry.com
@crichardson
Agenda
Overview of NodeJS
Building a front-end server with NodeJS
Taming tangled asynchronous code with promises
@crichardson
What’s NodeJS?
Designed for DIRTy apps
@crichardson
Small but growing rapidly
Busy!
@crichardson
NodeJS Hello World
app.js
$ node app.js
$ curl http://localhost:1337
http://nodejs.org/
Load a module
request handler
@crichardson
NodeJS
JavaScript
Reactor
pattern
Modules
@crichardson
NodeJS
JavaScript
Reactor
pattern
Modules
@crichardson
Dynamic, weakly-typed
Dynamic:
Types are associated with values - not variables
Define new program elements at runtime
Weakly typed:
Leave out arguments to methods
Access non-existent object properties
Weird implicit conversions: 99 == “99”!
truthy and falsy values
Comprehensive tests are essential
@crichardson
JavaScript is object-oriented
> var fred = {name: “Fred”, gender: “Male”};
undefined
> fred.name
“Fred”
> console.log("reading age=" + fred.age);
reading age=undefined
undefined
> fred.age = 99;
99
> fred
{ name: 'Fred',
gender: 'Male',
age: 99 }
> delete fred.age
true
> fred
{ name: 'Fred', gender: 'Male' }
Unordered key-value pairs
Keys = properties
Add property
Delete property
@crichardson
JavaScript “classes”
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () { console.log("Hello " + this.name); };
var chris = new Person("Chris");
chris.sayHello();
Looks like a
constructor?!?
What’s that?!?!
This Java-like syntax is a mess
because JavaScript isn’t class
based
Looks familiar
@crichardson
overrides
JavaScript is a prototypal
language
__proto__
a 99
__proto__
a 100
b 200
inherited
Prototype
X
Y
@crichardson
Prototypal code
$ node
> var person = {};
undefined
> person.sayHello = function () { console.log("Hello " + this.name); };
[Function]
> var chris = Object.create(person, {name: {value: "Chris"}});
undefined
> var sarah = Object.create(person, {name: {value: "Sarah"}});
undefined
> chris.sayHello();
Hello Chris
undefined
> sarah.sayHello();
Hello Sarah
undefined
> chris.sayHello = function () { console.log("Hello mate: " + this.name); };
[Function]
> chris.sayHello();
Hello mate: Chris
undefined
Not defined
here
prototype properties
@crichardson
JavaScript is Functional
function makeGenerator(nextFunction) {
var value = 0;
return function() {
var current = value;
value = nextFunction(value);
return current;
};
}
var inc = makeGenerator(function (x) {return x + 1; });
> inc()
0
> inc()
1
Pass function as an
argument
Return a function
closure
@crichardson
Partial function application
> var join = require("path").join;
undefined
> join("/x", "y")
'/x/y'
> var withinx = join.bind(undefined, "/x");
undefined
> withinx("y");
'/x/y'
>
partially apply join
@crichardson
Created in a hurry with the
goal of looking like Java
The ‘Java...’ name creates expectations that it can’t satisfy
Fake classes: Hides prototypes BUT still seems weird
global namespace
scope of vars is confusing
Missing return statement = confusion
‘function’ is really verbose
‘this’ is dynamically scoped
@crichardson
JavaScript is the only way to
get things done in the
browser
@crichardson
Stockholm syndrome
“Stockholm syndrome ... is a psychological
phenomenon in which hostages ... have
positive feelings toward their captors,
sometimes to the point of defending them...”
http://en.wikipedia.org/wiki/Stockholm_syndrome
@crichardson
Martin Fowler once said:
"...I'm one of those who despairs that a
language with such deep flaws plays such an
important role in computation. Still the
consequence of this is that we must take
javascript seriously as a first-class language
and concentrate on how to limit the damage
its flaws cause. ...."
http://martinfowler.com/bliki/gotoAarhus2012.html
@crichardson
Use just the good parts
@crichardson
Use a better language that
compiles to JavaScript
TypeScript
Typed parameters and fields
Classes and interfaces (dynamic structural typing)
Dart
Class-based OO
Optional static typing
Bidirectional binding with DOM elements
@crichardson
CoffeeScript Hello World
http = require('http')
class HttpRequestHandler
constructor: (@message) ->
handle: (req, res) =>
res.writeHead(200, {'Content-Type': 'text/plain'})
res.end(@message + 'n')
handler = new HttpRequestHandler "Hi There from CoffeeScript"
server = http.createServer(handler.handle)
server.listen(1338, '127.0.0.1')
console.log('Server running at http://127.0.0.1:1338/')
Classes :-)
Bound method
@crichardson
NodeJS
JavaScript
Reactor
pattern
Modules
@crichardson
About the Reactor pattern
Defined by Doug Schmidt in 1995
Pattern for writing scalable servers
Alternative to thread-per-connection model
Single threaded event loop dispatches events on
handles (e.g. sockets, file descriptors) to event handlers
@crichardson
Reactor pattern structure
Event Handler
handle_event(type)
get_handle()
Initiation Dispatcher
handle_events()
register_handler(h)
select(handlers)
for each h in handlers
h.handle_event(type)
end loop
handle
Synchronous Event
Demultiplexer
select()
owns
notifies
uses
handlers
@crichardson
Benefits:
Separation of concerns - event handlers separated
from low-level mechanism
More efficient - no thread context switching
Simplified concurrency - single threaded
@crichardson
Drawbacks:
Non-pre-emptive - handlers can’t block/take a long
time
Difficult to understand and debug - inverted flow of
control
@crichardson
NodeJS event loop implements
the Reactor pattern
@crichardson
Application code
Event-driven architecture
NodeJS event loop
Basic networking/file-system/etc.
HTTP DB driver ...
Event
listener
Callback
function
One
time
events
Recurring
events
@crichardson
Getting notified: Callback
example
var fs = require("fs");
function statFile(path) {
fs.stat(path, function (err, stat) {
if (err) {
console.log("Stat failed: " + path, err);
throw err;
}
console.log("stat result=" + path, stat);
});
};
By convention: first
param is error object
By convention: Last
arg is a callback
Callbacks are
good for one
time
notifications
@crichardson
Getting notified: event
listeners
EventEmitter class - inherit or use
Listener registration methods:
on(eventName, listener)
once(eventName, listener)
Emitting events
emit(eventName, args...)
‘error’ event = special case: no listener print stack trace and
exit!
Good for
recurring
events
@crichardson
Event listener example
var fs = require("fs");
var readStream = fs.createReadStream("events.js", {encoding: "utf-8"});
// ReadStream << ReadableStream << EventEmitter
readStream.on('open', function (fd) {
console.log("opened with fd=", fd);
});
// Node v0.10 has readable instead: this is deprecated
readStream.on('data', function (data) {
console.log("data=", data);
});
Register listener
Register listener
@crichardson
Callback hell
function times2(x, callback) {
setTimeout(function () {
callback(x * 2)}, 500);
}
function plus3(x, callback) {
setTimeout(function (){
callback(x + 3)}, 500);
}
function displayResult(z) {
console.log("The result is=", z);
}
function plus3AndThenTimes2(x, callback)
{
plus3(x, function (y) {
times2(y, callback)
})
}
plus3AndThenTimes2(10, displayResults);
function sum(a, b, callback) {
setTimeout(function () {
callback(a + b);
}, 500);
}
function plus3PlusTimes2(x, callback) {
var p3, t2;
function perhapsDone() {
if (p3 & t2)
sum(p3, t2, callback);
};
plus3(x, function (y) {
p3 = y;
perhapsDone();
});
times2(x, function (y) {
t2 = y;
perhapsDone();
});
}
plus3PlusTimes2(10, displayResult);
times2(plus3(x)) times2(x) + plus3(x)
@crichardson
Long running computations
Long running computation blocks event loop for
other requests
Need to run outside of main event loop
Options:
Community: web workers threads
Built-in: NodeJS child processes
@crichardson
Using child processes
var child = require('child_process').fork('child.js');
function sayHelloToChild() {
child.send({hello: "child"});
}
setTimeout(sayHelloToChild, 1000);
child.on('message', function(m) {
console.log('parent received:', m);
});
function kill() {
child.kill();
}
setTimeout(kill, 2000);
process.on('message', function (m) {
console.log("child received message=", m);
process.send({ihateyou: "you ruined my life"})
});
parent.js
child.js
Create child process
Send message to child
@crichardson
NodeJS
JavaScript
Reactor
pattern
Modules
Core built-in modules
Basic networking
HTTP(S)
Filesystem
Events
Timers
...
@crichardson
Thousands of community
developed modules
https://npmjs.org/
web frameworks, SQL/NoSQL
database, drivers, messaging, utilities...
@crichardson
What’s a module?
One or more JavaScript files
Optional native code:
Compiled during installation
JavaScript != systems programming language
Package.json - metadata including dependencies
exports.sayHello = function () {
console.log(“Hello”);
}
foo.js
@crichardson
Easy to install
$ npm install package-name
@crichardson
Easy to use
var http = require(“http”)
var server = http.createServer...
Core module OR
Path to file OR
module in node_modules
Module’s exports
@crichardson
Developing with NodeJS
modules
Core modules
Community modules
Your modules
Application code
@crichardson
Lots of modules BUT...
Variable quality
Multiple incomplete MySQL drivers, e.g. without
connection pooling!
Often abandoned
...
@crichardson
To summarize
NodeJS
JavaScript
Reactor patternModules
Flawed and
misunderstood
Scalable yet
costly and
often
unnecessary
Rich but
variable quality
@crichardson
Alternative technologies
Atmosphere - portable event delivery
Netty - asynchronous I/O
Vert.x
SpringSource’s Reactor project
...
@crichardson
So why care about
NodeJS?
Easy to write scalable network services
Easy to push events to the browser
Easy to get (small) stuff done
It has a role to play in modern
application architecture
@crichardson
Evolving from a monolithic
architecture....
WAR
Shipping
Service
Accounting
Service
Inventory
Service
StoreFrontUI
@crichardson
... to a micro-service architecture
Store front web application
shipping web application
inventory web application
Accounting
Service
StoreFrontUI
accounting web application
Shipping
Service
Inventory
Service
@crichardson
Browser
WAR
StoreFrontUI
Model
View Controller
Presentation layer evolution....
HTML / HTTP
+
JavaScript
@crichardson
Browser Web application
RESTful
EndpointsModel
View Controller
...Presentation layer evolution
JSON-REST
HTML 5 -
JavaScript
No elaborate, server-side web framework
required
Event
publisher
Events
Static
content
@crichardson
NodeJS as an API gateway
Browser
Service 1
Service 2
Message
Bus
HTML 5/
Java
Script
Socket.io
client
Events
RESTful WS
Server
application
Socket.io
server
Node JS
Service 3
RESTful
WS
@crichardson
Agenda
Overview of NodeJS
Building a front-end server with NodeJS
Taming tangled asynchronous code with promises
@crichardson
Serving static content
@crichardson
Using low-level APIs
var http = require('http'), path = require('path'), mime = require('mime'), fs = require("fs");
var server = http.createServer(function (req, res) {
var filePath;
if (req.url == '/') {
filePath = 'public/index.html';
} else {
filePath = 'public' + req.url;
}
fs.exists(filePath, function (exists) {
if (exists) {
res.writeHead(200, {"content-type": mime.lookup(path.basename(filePath)) });
fs.createReadStream(filePath).pipe(res);
} else {
res.writeHead(404, {'Content-Type': 'text/plain'});
res.write('Error 404: resource not found.');
res.end();
}
});
}
);
server.listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
Uses file
extension
Cool!
@crichardson
Using the express web
framework
var express = require('express')
, http = require('http')
, app = express()
, server = http.createServer(app)
;
app.configure(function(){
app.use(express.static(__dirname + '/public'));
});
server.listen(8081);
@crichardson
RESTful web services
@crichardson
Implementing RESTful WS
with Express
var express = require('express'),
http = require('http'),
path = require('path');
var app = express();
var server = http.createServer(app);
app.get('/portfolio/:userId', function (req, res) {
var portfolio = retrievePortfolio(req.params.userId);
res.json(portfolio);
});
Easy URL
routing and
destructuring
@crichardson
Proxying to backend server
express = require('express')
request = require('request')
app = express.createServer()
proxyToBackend = (baseUrl) ->
(req, res) ->
callback = (error, response, body) -> console.log("error=", error)
originRequest = request(baseUrl + req.url, callback)
req.pipe(originRequest)
originRequest.pipe(res)
app.get('/restaurant/*', proxyToBackend('http://available-restaurant....com'))
app.post('/orders', proxyToBackend('http://restaurant-management...com'))
app.get('/orders', proxyToBackend(''http://restaurant-management...com'))
Returns a request handler
that proxies to baseUrl
@crichardson
Delivering events to the
browser
@crichardson
NodeJS clock example
Increments every
second
@crichardson
Socket.io - Server side
var express = require('express')
, http = require('http')
, app = express()
, server = http.createServer(app)
, io = require('socket.io').listen(server)
;
app.configure(function(){
app.use(express.static(__dirname + '/public'));
});
server.listen(8081);
io.sockets.on('connection', function (socket) {
	 var counter = 0;
	 function tick() {
	 	 counter = counter + 1;
	 	 socket.emit('tick', counter);
	 };
	 setInterval(tick, 1000);
});
handle new
connection
Send tick event to
browser every 1 sec
initializes socket.io
@crichardson
Socket.io - client side using the
knockout.js MVVM framework
var socket = io.connect(location.hostname);
function ClockModel() {
self.ticker = ko.observable(1);
socket.on('tick', function (data) {
self.ticker(data);
});
};
ko.applyBindings(new ClockModel());
<html>
<body>
The event is <span data-bind="text: ticker"></span>
<script src="/socket.io/socket.io.js"></script>
<script src="/knockout-2.0.0.js"></script>
<script src="/clock.js"></script>
</body>
</html>
clock.js
Connect to
socket.io
Subscribe
to tick event
Bind to model
Update
model
@crichardson
Distributed NodeJS clock
example
Browser NodeJS NodeJS
RabbitMQ
ProducerConsumer
socket.iosocket.io
@crichardson
AMQP Publisher
var amqp = require('amqp'),
amqpConnection = amqp.createConnection(...),
tickTockExchange;
function tick() {
var message = { tick: Date.now() };
tickTockExchange.publish("tickTock", message, {
mandatory: true,
contentType: "text/plain"
});
};
amqpConnection.on('ready',
function () {
tickTockExchange =
amqpConnection.exchange("tickTock",
options = {passive: false, type: 'fanout'});
tickTockExchange.on('open', function () { setInterval(tick, 1000) });
});
Connect to
AMQP
Ensure
exchange
exists
Publish
message
Initialize timer
@crichardson
AMQP socket.iovar express = require('express')
, http = require('http')
, amqp = require(‘amqp’)
....;
server.listen(8081);
...
var amqpCon = amqp.createConnection(...);
io.sockets.on('connection', function (socket) {
function amqpMessageHandler(message, headers, deliveryInfo) {
var m = JSON.parse(message.data.toString());
socket.emit(‘tick’, m);
};
amqpCon.queue(“”, {},
function(queue) {
queue.bind(“tickTock”, “”);
queue.subscribe(amqpMessageHandler);
});
});
Connect to
AMQP queue
Subscribe to
AMQP queue
Republish
as socket.io
event
https://github.com/cer/nodejs-clock
@crichardson
Agenda
Overview of NodeJS
Building a front-end server with NodeJS
Taming tangled asynchronous code with promises
@crichardson
Async code = callback hell
Scenarios:
Sequential: A B C
Fork and join: A and B C
Code quickly becomes very messy
@crichardson
Simplifying code with
Promises (a.k.a. Futures)
Functions return a promise - no callback parameter
A promise represents an eventual outcome
Use a library of functions for transforming and
composing promises
Promises/A+ specification - http://promises-aplus.github.io/promises-spec
when.js (part of cujo.js by SpringSource) is a popular
implementation
@crichardson
Taming callback hell 1
function times2(x) {
var deferred = when.defer();
setTimeout(function () {
deferred.resolve(x * 2)}, 500);
return deferred.promise;
}
times2(plus3(x)) Create a deferred
Return a promise
Eventually supply a value
function plus3AndThenTimes2(x) {
return plus3(x).then(times2);
}
plus3AndThenTimes2(10).
then(displayResult);
Transform value in
promise
function plus3(x) {
var deferred = when.defer();
setTimeout(function () {
deferred.resolve(x + 3) }, 500);
return deferred.promise;
}
Simpler, almost
synchronous-style code
@crichardson
Taming callback hell 2
function sum(a, b) {
var deferred = when.defer();
setTimeout(function () {
deferred.resolve(a + b);
}, 500);
return deferred.promise;
}
function plus3PlusTimes2(x) {
var p3 = plus3(x),
t2 = times2(x);
return when.join(p3, t2).spread(sum);
}
plus3PlusTimes2(10).then(displayResult);
times2(x) + plus3(x)
Combine results
of two promises
Call with array
elements as
arguments
@crichardson
Calling non-promise code
var deferred = when.defer();
fs.stat(path, function (err, statInfo) {
if (err)
deferred.reject(err);
else
deferred.resolve(statInfo);
}
var promise = deferred.promise;
var nodefn = require("when/node/function");
var promise = nodefn.call(fs.stat, path)
Hides
boilerplate
code
@crichardson
Filesystem scanner example
Read contents of directory
Use stat to determine if directory or file
Recurse on directories
Merge the results
@crichardson
Read contents of directory
function findFilesInDir(dir) {
var directoryContents =
nodefn.call(self.fs.readdir, dir);
...
}
Returns promise containing
an array of file names
@crichardson
Create absolute paths
function findFilesInDir(dir) {
var directoryContents = ...
var toAbsolute = join.bind(undefined, dir)
var absolutePaths = when.map(directoryContents, toAbsolute);
...
}
Partially apply
join()
@crichardson
Use stat to determine if
directory or file
function findFilesInDir(dir) {
var directoryContents = ...
var absolutePaths = ...
var statTasks = when.map(absolutePaths, makeStatTask);
var statResults = parallel(statTasks);
...
}
function makeStatTask(path) {
return function () {
function makeStatInfo(stats) {
return {path: path, isdir: stats.isDirectory(),
ctime: stats.ctime};
}
return nodefn.call(self.fs.stat, path).then(makeStatInfo);
};
}
Execute stats in
parallel
@crichardson
Recurse on directories
function findFilesInDir(dir) {
...
var statResults = ...;
var listOfListsOfFiles =
when.map(statResults, processStatInfo);
...
}
function processStatInfo(statInfo) {
if (statInfo.isdir) {
return findFilesInDir(statInfo.path);
} else {
return [statInfo.path];
}
}
Map each stat
result to a list of
files
@crichardson
Flatten array of arrays of file
paths
function findFilesInDir(dir) {
...
var listOfListsOfFiles = ...;
return when.reduce(listOfListsOfFiles,
concatenateLists, []);
}
function concatenateLists(currentResult, value, index, total)
{
return currentResult.concat(value);
}
@crichardson
Summary
JavaScript is a very flawed language
The asynchronous model is often unnecessary; very
constraining; and adds complexity
BUT despite those problems
Today, NodeJS is remarkably useful for building network-
focussed components
@crichardson
Questions?
@crichardson chris.richardson@springsource.com
http://plainoldobjects.com - code and slides
www.cloudfoundry.com

NodeJS: the good parts? A skeptic’s view (jax jax2013)

  • 1.
    @crichardson NodeJS: the goodparts? A skeptic’s view Chris Richardson Author of POJOs in Action Founder of the original CloudFoundry.com @crichardson chris.richardson@springsource.com http://plainoldobjects.com
  • 2.
    @crichardson Presentation goal How acurmudgeonly server-side Java developer discovered an appreciation for NodeJS and JavaScript
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
    @crichardson vmc push About-Chris DeveloperAdvocate Signup at http://cloudfoundry.com
  • 9.
    @crichardson Agenda Overview of NodeJS Buildinga front-end server with NodeJS Taming tangled asynchronous code with promises
  • 10.
  • 11.
  • 12.
    @crichardson NodeJS Hello World app.js $node app.js $ curl http://localhost:1337 http://nodejs.org/ Load a module request handler
  • 13.
  • 14.
  • 15.
    @crichardson Dynamic, weakly-typed Dynamic: Types areassociated with values - not variables Define new program elements at runtime Weakly typed: Leave out arguments to methods Access non-existent object properties Weird implicit conversions: 99 == “99”! truthy and falsy values Comprehensive tests are essential
  • 16.
    @crichardson JavaScript is object-oriented >var fred = {name: “Fred”, gender: “Male”}; undefined > fred.name “Fred” > console.log("reading age=" + fred.age); reading age=undefined undefined > fred.age = 99; 99 > fred { name: 'Fred', gender: 'Male', age: 99 } > delete fred.age true > fred { name: 'Fred', gender: 'Male' } Unordered key-value pairs Keys = properties Add property Delete property
  • 17.
    @crichardson JavaScript “classes” function Person(name){ this.name = name; } Person.prototype.sayHello = function () { console.log("Hello " + this.name); }; var chris = new Person("Chris"); chris.sayHello(); Looks like a constructor?!? What’s that?!?! This Java-like syntax is a mess because JavaScript isn’t class based Looks familiar
  • 18.
    @crichardson overrides JavaScript is aprototypal language __proto__ a 99 __proto__ a 100 b 200 inherited Prototype X Y
  • 19.
    @crichardson Prototypal code $ node >var person = {}; undefined > person.sayHello = function () { console.log("Hello " + this.name); }; [Function] > var chris = Object.create(person, {name: {value: "Chris"}}); undefined > var sarah = Object.create(person, {name: {value: "Sarah"}}); undefined > chris.sayHello(); Hello Chris undefined > sarah.sayHello(); Hello Sarah undefined > chris.sayHello = function () { console.log("Hello mate: " + this.name); }; [Function] > chris.sayHello(); Hello mate: Chris undefined Not defined here prototype properties
  • 20.
    @crichardson JavaScript is Functional functionmakeGenerator(nextFunction) { var value = 0; return function() { var current = value; value = nextFunction(value); return current; }; } var inc = makeGenerator(function (x) {return x + 1; }); > inc() 0 > inc() 1 Pass function as an argument Return a function closure
  • 21.
    @crichardson Partial function application >var join = require("path").join; undefined > join("/x", "y") '/x/y' > var withinx = join.bind(undefined, "/x"); undefined > withinx("y"); '/x/y' > partially apply join
  • 22.
    @crichardson Created in ahurry with the goal of looking like Java The ‘Java...’ name creates expectations that it can’t satisfy Fake classes: Hides prototypes BUT still seems weird global namespace scope of vars is confusing Missing return statement = confusion ‘function’ is really verbose ‘this’ is dynamically scoped
  • 23.
    @crichardson JavaScript is theonly way to get things done in the browser
  • 24.
    @crichardson Stockholm syndrome “Stockholm syndrome... is a psychological phenomenon in which hostages ... have positive feelings toward their captors, sometimes to the point of defending them...” http://en.wikipedia.org/wiki/Stockholm_syndrome
  • 25.
    @crichardson Martin Fowler oncesaid: "...I'm one of those who despairs that a language with such deep flaws plays such an important role in computation. Still the consequence of this is that we must take javascript seriously as a first-class language and concentrate on how to limit the damage its flaws cause. ...." http://martinfowler.com/bliki/gotoAarhus2012.html
  • 26.
  • 27.
    @crichardson Use a betterlanguage that compiles to JavaScript TypeScript Typed parameters and fields Classes and interfaces (dynamic structural typing) Dart Class-based OO Optional static typing Bidirectional binding with DOM elements
  • 28.
    @crichardson CoffeeScript Hello World http= require('http') class HttpRequestHandler constructor: (@message) -> handle: (req, res) => res.writeHead(200, {'Content-Type': 'text/plain'}) res.end(@message + 'n') handler = new HttpRequestHandler "Hi There from CoffeeScript" server = http.createServer(handler.handle) server.listen(1338, '127.0.0.1') console.log('Server running at http://127.0.0.1:1338/') Classes :-) Bound method
  • 29.
  • 30.
    @crichardson About the Reactorpattern Defined by Doug Schmidt in 1995 Pattern for writing scalable servers Alternative to thread-per-connection model Single threaded event loop dispatches events on handles (e.g. sockets, file descriptors) to event handlers
  • 31.
    @crichardson Reactor pattern structure EventHandler handle_event(type) get_handle() Initiation Dispatcher handle_events() register_handler(h) select(handlers) for each h in handlers h.handle_event(type) end loop handle Synchronous Event Demultiplexer select() owns notifies uses handlers
  • 32.
    @crichardson Benefits: Separation of concerns- event handlers separated from low-level mechanism More efficient - no thread context switching Simplified concurrency - single threaded
  • 33.
    @crichardson Drawbacks: Non-pre-emptive - handlerscan’t block/take a long time Difficult to understand and debug - inverted flow of control
  • 34.
    @crichardson NodeJS event loopimplements the Reactor pattern
  • 35.
    @crichardson Application code Event-driven architecture NodeJSevent loop Basic networking/file-system/etc. HTTP DB driver ... Event listener Callback function One time events Recurring events
  • 36.
    @crichardson Getting notified: Callback example varfs = require("fs"); function statFile(path) { fs.stat(path, function (err, stat) { if (err) { console.log("Stat failed: " + path, err); throw err; } console.log("stat result=" + path, stat); }); }; By convention: first param is error object By convention: Last arg is a callback Callbacks are good for one time notifications
  • 37.
    @crichardson Getting notified: event listeners EventEmitterclass - inherit or use Listener registration methods: on(eventName, listener) once(eventName, listener) Emitting events emit(eventName, args...) ‘error’ event = special case: no listener print stack trace and exit! Good for recurring events
  • 38.
    @crichardson Event listener example varfs = require("fs"); var readStream = fs.createReadStream("events.js", {encoding: "utf-8"}); // ReadStream << ReadableStream << EventEmitter readStream.on('open', function (fd) { console.log("opened with fd=", fd); }); // Node v0.10 has readable instead: this is deprecated readStream.on('data', function (data) { console.log("data=", data); }); Register listener Register listener
  • 39.
    @crichardson Callback hell function times2(x,callback) { setTimeout(function () { callback(x * 2)}, 500); } function plus3(x, callback) { setTimeout(function (){ callback(x + 3)}, 500); } function displayResult(z) { console.log("The result is=", z); } function plus3AndThenTimes2(x, callback) { plus3(x, function (y) { times2(y, callback) }) } plus3AndThenTimes2(10, displayResults); function sum(a, b, callback) { setTimeout(function () { callback(a + b); }, 500); } function plus3PlusTimes2(x, callback) { var p3, t2; function perhapsDone() { if (p3 & t2) sum(p3, t2, callback); }; plus3(x, function (y) { p3 = y; perhapsDone(); }); times2(x, function (y) { t2 = y; perhapsDone(); }); } plus3PlusTimes2(10, displayResult); times2(plus3(x)) times2(x) + plus3(x)
  • 40.
    @crichardson Long running computations Longrunning computation blocks event loop for other requests Need to run outside of main event loop Options: Community: web workers threads Built-in: NodeJS child processes
  • 41.
    @crichardson Using child processes varchild = require('child_process').fork('child.js'); function sayHelloToChild() { child.send({hello: "child"}); } setTimeout(sayHelloToChild, 1000); child.on('message', function(m) { console.log('parent received:', m); }); function kill() { child.kill(); } setTimeout(kill, 2000); process.on('message', function (m) { console.log("child received message=", m); process.send({ihateyou: "you ruined my life"}) }); parent.js child.js Create child process Send message to child
  • 42.
  • 43.
    Core built-in modules Basicnetworking HTTP(S) Filesystem Events Timers ...
  • 44.
    @crichardson Thousands of community developedmodules https://npmjs.org/ web frameworks, SQL/NoSQL database, drivers, messaging, utilities...
  • 45.
    @crichardson What’s a module? Oneor more JavaScript files Optional native code: Compiled during installation JavaScript != systems programming language Package.json - metadata including dependencies exports.sayHello = function () { console.log(“Hello”); } foo.js
  • 46.
    @crichardson Easy to install $npm install package-name
  • 47.
    @crichardson Easy to use varhttp = require(“http”) var server = http.createServer... Core module OR Path to file OR module in node_modules Module’s exports
  • 48.
    @crichardson Developing with NodeJS modules Coremodules Community modules Your modules Application code
  • 49.
    @crichardson Lots of modulesBUT... Variable quality Multiple incomplete MySQL drivers, e.g. without connection pooling! Often abandoned ...
  • 50.
    @crichardson To summarize NodeJS JavaScript Reactor patternModules Flawedand misunderstood Scalable yet costly and often unnecessary Rich but variable quality
  • 51.
    @crichardson Alternative technologies Atmosphere -portable event delivery Netty - asynchronous I/O Vert.x SpringSource’s Reactor project ...
  • 52.
    @crichardson So why careabout NodeJS? Easy to write scalable network services Easy to push events to the browser Easy to get (small) stuff done It has a role to play in modern application architecture
  • 53.
    @crichardson Evolving from amonolithic architecture.... WAR Shipping Service Accounting Service Inventory Service StoreFrontUI
  • 54.
    @crichardson ... to amicro-service architecture Store front web application shipping web application inventory web application Accounting Service StoreFrontUI accounting web application Shipping Service Inventory Service
  • 55.
  • 56.
    @crichardson Browser Web application RESTful EndpointsModel ViewController ...Presentation layer evolution JSON-REST HTML 5 - JavaScript No elaborate, server-side web framework required Event publisher Events Static content
  • 57.
    @crichardson NodeJS as anAPI gateway Browser Service 1 Service 2 Message Bus HTML 5/ Java Script Socket.io client Events RESTful WS Server application Socket.io server Node JS Service 3 RESTful WS
  • 58.
    @crichardson Agenda Overview of NodeJS Buildinga front-end server with NodeJS Taming tangled asynchronous code with promises
  • 59.
  • 60.
    @crichardson Using low-level APIs varhttp = require('http'), path = require('path'), mime = require('mime'), fs = require("fs"); var server = http.createServer(function (req, res) { var filePath; if (req.url == '/') { filePath = 'public/index.html'; } else { filePath = 'public' + req.url; } fs.exists(filePath, function (exists) { if (exists) { res.writeHead(200, {"content-type": mime.lookup(path.basename(filePath)) }); fs.createReadStream(filePath).pipe(res); } else { res.writeHead(404, {'Content-Type': 'text/plain'}); res.write('Error 404: resource not found.'); res.end(); } }); } ); server.listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/'); Uses file extension Cool!
  • 61.
    @crichardson Using the expressweb framework var express = require('express') , http = require('http') , app = express() , server = http.createServer(app) ; app.configure(function(){ app.use(express.static(__dirname + '/public')); }); server.listen(8081);
  • 62.
  • 63.
    @crichardson Implementing RESTful WS withExpress var express = require('express'), http = require('http'), path = require('path'); var app = express(); var server = http.createServer(app); app.get('/portfolio/:userId', function (req, res) { var portfolio = retrievePortfolio(req.params.userId); res.json(portfolio); }); Easy URL routing and destructuring
  • 64.
    @crichardson Proxying to backendserver express = require('express') request = require('request') app = express.createServer() proxyToBackend = (baseUrl) -> (req, res) -> callback = (error, response, body) -> console.log("error=", error) originRequest = request(baseUrl + req.url, callback) req.pipe(originRequest) originRequest.pipe(res) app.get('/restaurant/*', proxyToBackend('http://available-restaurant....com')) app.post('/orders', proxyToBackend('http://restaurant-management...com')) app.get('/orders', proxyToBackend(''http://restaurant-management...com')) Returns a request handler that proxies to baseUrl
  • 65.
  • 66.
  • 67.
    @crichardson Socket.io - Serverside var express = require('express') , http = require('http') , app = express() , server = http.createServer(app) , io = require('socket.io').listen(server) ; app.configure(function(){ app.use(express.static(__dirname + '/public')); }); server.listen(8081); io.sockets.on('connection', function (socket) { var counter = 0; function tick() { counter = counter + 1; socket.emit('tick', counter); }; setInterval(tick, 1000); }); handle new connection Send tick event to browser every 1 sec initializes socket.io
  • 68.
    @crichardson Socket.io - clientside using the knockout.js MVVM framework var socket = io.connect(location.hostname); function ClockModel() { self.ticker = ko.observable(1); socket.on('tick', function (data) { self.ticker(data); }); }; ko.applyBindings(new ClockModel()); <html> <body> The event is <span data-bind="text: ticker"></span> <script src="/socket.io/socket.io.js"></script> <script src="/knockout-2.0.0.js"></script> <script src="/clock.js"></script> </body> </html> clock.js Connect to socket.io Subscribe to tick event Bind to model Update model
  • 69.
    @crichardson Distributed NodeJS clock example BrowserNodeJS NodeJS RabbitMQ ProducerConsumer socket.iosocket.io
  • 70.
    @crichardson AMQP Publisher var amqp= require('amqp'), amqpConnection = amqp.createConnection(...), tickTockExchange; function tick() { var message = { tick: Date.now() }; tickTockExchange.publish("tickTock", message, { mandatory: true, contentType: "text/plain" }); }; amqpConnection.on('ready', function () { tickTockExchange = amqpConnection.exchange("tickTock", options = {passive: false, type: 'fanout'}); tickTockExchange.on('open', function () { setInterval(tick, 1000) }); }); Connect to AMQP Ensure exchange exists Publish message Initialize timer
  • 71.
    @crichardson AMQP socket.iovar express= require('express') , http = require('http') , amqp = require(‘amqp’) ....; server.listen(8081); ... var amqpCon = amqp.createConnection(...); io.sockets.on('connection', function (socket) { function amqpMessageHandler(message, headers, deliveryInfo) { var m = JSON.parse(message.data.toString()); socket.emit(‘tick’, m); }; amqpCon.queue(“”, {}, function(queue) { queue.bind(“tickTock”, “”); queue.subscribe(amqpMessageHandler); }); }); Connect to AMQP queue Subscribe to AMQP queue Republish as socket.io event https://github.com/cer/nodejs-clock
  • 72.
    @crichardson Agenda Overview of NodeJS Buildinga front-end server with NodeJS Taming tangled asynchronous code with promises
  • 73.
    @crichardson Async code =callback hell Scenarios: Sequential: A B C Fork and join: A and B C Code quickly becomes very messy
  • 74.
    @crichardson Simplifying code with Promises(a.k.a. Futures) Functions return a promise - no callback parameter A promise represents an eventual outcome Use a library of functions for transforming and composing promises Promises/A+ specification - http://promises-aplus.github.io/promises-spec when.js (part of cujo.js by SpringSource) is a popular implementation
  • 75.
    @crichardson Taming callback hell1 function times2(x) { var deferred = when.defer(); setTimeout(function () { deferred.resolve(x * 2)}, 500); return deferred.promise; } times2(plus3(x)) Create a deferred Return a promise Eventually supply a value function plus3AndThenTimes2(x) { return plus3(x).then(times2); } plus3AndThenTimes2(10). then(displayResult); Transform value in promise function plus3(x) { var deferred = when.defer(); setTimeout(function () { deferred.resolve(x + 3) }, 500); return deferred.promise; } Simpler, almost synchronous-style code
  • 76.
    @crichardson Taming callback hell2 function sum(a, b) { var deferred = when.defer(); setTimeout(function () { deferred.resolve(a + b); }, 500); return deferred.promise; } function plus3PlusTimes2(x) { var p3 = plus3(x), t2 = times2(x); return when.join(p3, t2).spread(sum); } plus3PlusTimes2(10).then(displayResult); times2(x) + plus3(x) Combine results of two promises Call with array elements as arguments
  • 77.
    @crichardson Calling non-promise code vardeferred = when.defer(); fs.stat(path, function (err, statInfo) { if (err) deferred.reject(err); else deferred.resolve(statInfo); } var promise = deferred.promise; var nodefn = require("when/node/function"); var promise = nodefn.call(fs.stat, path) Hides boilerplate code
  • 78.
    @crichardson Filesystem scanner example Readcontents of directory Use stat to determine if directory or file Recurse on directories Merge the results
  • 79.
    @crichardson Read contents ofdirectory function findFilesInDir(dir) { var directoryContents = nodefn.call(self.fs.readdir, dir); ... } Returns promise containing an array of file names
  • 80.
    @crichardson Create absolute paths functionfindFilesInDir(dir) { var directoryContents = ... var toAbsolute = join.bind(undefined, dir) var absolutePaths = when.map(directoryContents, toAbsolute); ... } Partially apply join()
  • 81.
    @crichardson Use stat todetermine if directory or file function findFilesInDir(dir) { var directoryContents = ... var absolutePaths = ... var statTasks = when.map(absolutePaths, makeStatTask); var statResults = parallel(statTasks); ... } function makeStatTask(path) { return function () { function makeStatInfo(stats) { return {path: path, isdir: stats.isDirectory(), ctime: stats.ctime}; } return nodefn.call(self.fs.stat, path).then(makeStatInfo); }; } Execute stats in parallel
  • 82.
    @crichardson Recurse on directories functionfindFilesInDir(dir) { ... var statResults = ...; var listOfListsOfFiles = when.map(statResults, processStatInfo); ... } function processStatInfo(statInfo) { if (statInfo.isdir) { return findFilesInDir(statInfo.path); } else { return [statInfo.path]; } } Map each stat result to a list of files
  • 83.
    @crichardson Flatten array ofarrays of file paths function findFilesInDir(dir) { ... var listOfListsOfFiles = ...; return when.reduce(listOfListsOfFiles, concatenateLists, []); } function concatenateLists(currentResult, value, index, total) { return currentResult.concat(value); }
  • 84.
    @crichardson Summary JavaScript is avery flawed language The asynchronous model is often unnecessary; very constraining; and adds complexity BUT despite those problems Today, NodeJS is remarkably useful for building network- focussed components
  • 85.