54
BRINGING CHAOS TO ORDER IN YOUR NODE.JS APP DAN JENKINS NIMBLE APE https://nimblea.pe

Bringing choas to order in your node.js app

Embed Size (px)

Citation preview

BRINGING CHAOS TO ORDER IN YOUR NODE.JS APP

DAN JENKINS

NIMBLE APE

https://nimblea.pe

WHO AM I?

WHO AM I?DAN JENKINS

NODE.JS DEVELOPER / ARCHITECT

GOOGLE DEVELOPER EXPERT IN WEBRTC

LOVE LEGO & TECHNIC

GENERAL GEEK

FOUNDER OF NIMBLE APE LTD

❤ OPEN SOURCE

3

4

/danjenkins

5

@dan_jenkins

6

nimblea.pe | @nimbleapeltd

NODE.JS & I

STARTED WITH NODE.JS WHEN IT WAS A BABY - 0.4

BUILT MANY, MANY MICROSERVICES AND REST APIS WITH NODE

SPENT THE PAST YEAR WORKING ON A PLATFORM CALLED RESPOKE - WEBRTC

8

FUNNY STORY…

9

H E L L Omy name is

DanG O O G L E I / O

WARNING…

SOME BAD PRACTICES ARE USED IN SOME OF THE EXAMPLES.

I KNOW YOU SHOULDN’T DO THESE THINGS.

BUT, ITS THE EASIEST WAY TO SHOW THE POINT IN THE SMALL CONFINES OF A SLIDE DECK.

I PROMISE I KNOW WHAT I’M DOING…

10

NODE.JS APPS CAN GET IN A REAL MESS…

WHO’S WRITTEN CODE LIKE THIS ?

12

createAdministrator: function (req, res) {

Administrators.create({some: data}, function cb(error, admin) {

if (error) { console.error('Error creating Admin'); res.send(500) return; }

res.admin = admin;

doAnotherCall(res, function cb2(err2){ res.send(200, res.admin); });

}); }

OR LIKE THIS

13

var http = require('http'); var uuid = require('uuid');

function respond(res) { console.log('Got request with ID', res.id); res.end('Response'); }

var server = http.createServer(function handleRequest(req, res){ res.id = uuid(); respond(res) });

server.listen(8080, function(){ console.log("Server listening on: http://localhost:8080"); });

OR LIKE THIS

14

var http = require('http'); var uuid = require('uuid');

function doSomething(res, id) { console.log('Do something with request with ID', id); respond(res, id); }

function respond(res, id) { console.log('Responding to request with ID', id); res.end('Response'); }

var server = http.createServer(function handleRequest(req, res){ var id = uuid(); doSomething(res, id); });

server.listen(8080, function(){ console.log("Server listening on: http://localhost:8080"); });

EVERYONE HAS AT SOME POINT…

DON’T TRY AND DENY IT…

WHAT PROBLEM ARE WE TRYING TO SOLVE?

PASSING DATA AROUND TO OTHER FUNCTIONS FOR USE LATER

19

req.id = ‘foo’;

console.log(req.id);

FOR EXAMPLE, SERVICE BASED LOGGING

21

ASSIGN A REQUEST ID HERE

AND HAVE IT SENT ON TO ALL OTHER SERVICES

WHICH MEANS ALL YOUR LOGS FROM ALL YOUR SERVICES ALL TIE TOGETHER

WHAT’S THE ANSWER?

23

OBVIOUSLY…

CLS

NODE-CONTINUATION-LOCAL-STORAGE

USERLAND MODULE

UTILISES ASYNC-LISTENER

24

/othiym23/node-continuation-local-storage

/ continuation-local-storage

/package/async-listener

IT HAS NOTHING TO DO WITH LOCAL-STORAGE IN THE BROWSER

“Continuation-local storage works like thread-local storage in

threaded programming, but is based on chains of Node-style callbacks instead of threads.”

26

IT MEANS YOU CAN STOP DOING THIS…

27

var http = require('http'); var uuid = require('uuid');

function respond(res) { console.log('Got request with ID', res.id); res.end('Response'); }

var server = http.createServer(function handleRequest(req, res){ res.id = uuid(); respond(res); });

server.listen(8080, function(){ console.log("Server listening on: http://localhost:8080"); });

AND START DOING THIS…

28

var http = require('http'); var uuid = require('uuid'); var createNamespace = require('continuation-local-storage').createNamespace; var namespace = createNamespace('request-life');

function respond(res) { console.log('Got request with ID', namespace.get('requestId')); res.end('Response'); }

var server = http.createServer(function handleRequest(req, res){ namespace.set('requestId', uuid()); respond(res); });

server.listen(8080, function(){ console.log('Server listening on: http://localhost:8080'); });

IT’S MORE IMPRESSIVE THAN IT LOOKS IN THIS SIMPLE EXAMPLE

I PROMISE

THAT’S AWESOME! WHAT DO I NEED TO DO?

1

31

var createNamespace = require('continuation-local-storage').createNamespace; var session = createNamespace('my session');

var db = require('./lib/db.js');

function start(options, next) { db.fetchUserById(options.id, function (error, user) { if (error) return next(error);

session.set('user', user);

next(); }); }

2

32

var getNamespace = require('continuation-local-storage').getNamespace; var session = getNamespace('my session');

var render = require('./lib/render.js')

function finish(response) { var user = session.get('user'); render({user: user}).pipe(response); }

SO IT JUST WORKS?I DON’T HAVE TO DO ANYTHING FUNKY?

KINDA…

BEWARE NATIVE INTERACTIONS

ANYTHING THAT TOUCHES NATIVE CODE; YOU’LL NEED TO SHIM

SHIM ALL THE THINGS

/package/shimmer

/othiym23/shimmer

39

var http = require('http'); var shimmer = require('shimmer');

shimmer.wrap(http, 'request', function (original) { return function () { console.log("Starting request!"); var returned = original.apply(this, arguments) console.log("Done setting up request -- OH YEAH!"); return returned; }; });

OR USE AN EXISTING ONE

41

require('cls-mysql')(ns); require('cls-redis')(ns); require('cls-q')(ns); require('cls-bluebird')(ns); require('cls-es6-promise')(ns); require('cls-bcrypt')(ns);

42

YOU NEED TO BE WARY!

44

// Copyright (c) 2015. David M. Lee, II 'use strict';

var shimmer = require('shimmer');

// require mysql first; otherwise you can get some bizarre // "object is not a function" errors if cls-mysql is loaded first. require('mysql'); var Protocol = require('mysql/lib/protocol/Protocol'); var Pool = require('mysql/lib/Pool');

module.exports = function(ns) { shimmer.wrap(Protocol.prototype, '_enqueue', function(enqueue) { return function(sequence) { sequence._callback = ns.bind(sequence._callback); return enqueue.call(this, sequence); }; });

shimmer.wrap(Pool.prototype, 'getConnection', function(getConnection) { return function(cb) { return getConnection.call(this, ns.bind(cb)); }; }); };

YOU HAVE TO KNOW WHAT TO SHIM!

AND BEWARE THE INTERNALS OF THE MODULE CHANGING

AN INTERNAL MODULE CHANGE !== A PUBLIC API

CHANGE

WHICH MEANS YOU COULD BE HIT BY A MINOR VERSION BUMP

WHICH COULD RESULT IN A FAILING APPLICATION

IT ALSO HAS A PERFORMANCE COST

THE MORE YOU HAVE, THE MORE YOU’LL IMPACT YOUR PERFORMANCE

COST IS AGAINST A NAMESPACE, NOT DATA IN THE NAMESPACE

KEEP THINGS TO A MINIMUM

50

SO WHY USE IT?

IF YOU’RE DOING IT RIGHT…YOU SHOULD BE USING SHRINKWRAP ANYWAY

(OR ANOTHER TECHNIQUE TO LOCK DEPENDENCIES)

SO BREAKAGES SHOULD BE LIMITED TO YOUR DEVELOPMENT ENVIRONMENT

WHICH MEANS MOAR BONUS FOR LITTLE TO NO RISK

VERY LOW PERFORMANCE COST

52

THANKS!DAN JENKINS

@dan_jenkins

MOST EXAMPLES WERE TAKEN FROM

https://github.com/othiym23/node-continuation-local-storage

https://github.com/othiym23/shimmer

https://github.com/building5/cls-mysql

54