JavaScript used to be confined to the browser. But these days, it's becoming increasingly popular in server-side applications in the form of Node.js. Node.js provides event-driven, non-blocking I/O model that supposedly makes it easy to build scalable network application. In this talk you will learn about the consequences of combining the event-driven programming model with a prototype-based, weakly typed, dynamic language. We will share our perspective as a server-side Java developer who wasn’t entirely happy about JavaScript in the browser, let alone on the server. You will learn how to use Node.js effectively in modern, polyglot applications.
Watch the video: http://www.youtube.com/watch?v=CN0jTnSROsk&feature=youtu.be
NodeJS: the good parts? A skeptic’s view (jax jax2013)
1. @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
15. @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
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
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
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
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 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
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 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
27. @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
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
30. @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
31. @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
32. @crichardson
Benefits:
Separation of concerns - event handlers separated
from low-level mechanism
More efficient - no thread context switching
Simplified concurrency - single threaded
36. @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
37. @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
38. @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
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
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
41. @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
45. @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
47. @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
52. @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
53. @crichardson
Evolving from a monolithic
architecture....
WAR
Shipping
Service
Accounting
Service
Inventory
Service
StoreFrontUI
54. @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
57. @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
67. @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
68. @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
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 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
76. @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
77. @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
79. @crichardson
Read contents of directory
function findFilesInDir(dir) {
var directoryContents =
nodefn.call(self.fs.readdir, dir);
...
}
Returns promise containing
an array of file names
80. @crichardson
Create absolute paths
function findFilesInDir(dir) {
var directoryContents = ...
var toAbsolute = join.bind(undefined, dir)
var absolutePaths = when.map(directoryContents, toAbsolute);
...
}
Partially apply
join()
81. @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
82. @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
83. @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);
}
84. @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