(use spacebar to navigate through all the slides)

Dnode - why?

First let's explore the usual characters

Socket.IO

PushR

ZeroMQ

(use spacebar to navigate through all the slides)

These are "Payload" Solutions

Socket.IO

PushR

ZeroMQ

Basically a hosted function whose arguments can only be things that are "standardly" JSON serializable

String

Number

Boolean

Object

Such as this "Easily Serializable but restrictive function"

  var x = function(a, b, c) {
      // where a, b, c can be 
      // JSON serializable
  }
  var ee = require('events').EventEmitter;
  var foo = new ee;
  foo.on('bar', x)
  foo.emit('bar', 3, 'cat', false) 

(use spacebar to navigate through all the slides)

The problem?

Regardless if you use any of the standard JSON serializable payload event message passing systems, you have to plan ahead how you want to handle asynchronous requests in a tedious and non-iterative manner.

This is Fragile and overly delicate way to develop

Recieving data on either side of the wire requires planning and code, planning and code that could change, and ultimately require multiple rewrites of a lot of boilerplatish code

emit('register', {name:'John Smith', username:'JSmith'})  

.on('register-response', function(err, resp) {
}) 

emit('register', {name:'John Smith', username:'JSmith'})  

.on('register-response', function(err, resp) {
}) 
  • do we have to use a UUID or some kind of router to correctly match up this response to some desired request?
  • what if we need to do have another emit / request here?
  • then we have to have another response handler?
  • this goes on and on and on

(use spacebar to navigate through all the slides)

What we'd really like to do


  remote.register({name:'John Smith', username:'JSmith'}, 
  function(err, resp) {})

(use spacebar to navigate through all the slides)

Dnode


  remote.register({name:'John Smith', username:'JSmith'}, 
  function(err, resp) {})
Where... register is a function on the other side that looks like this :

  {
      register : function(obj, cb) {
          if (name.match(/^John/)) {
              cb(new Error("We don't accept new users named John"))
          } else {
              cb(null, {
                  username: obj.username
              })
          }
      }
  }
And if we really wanted, we should be able to just write javascript, where we can pass functions in parameters, as arguments, freely.

When .register is called, it dynamically associates all the functions in lexical scope that are presented like myfunction


var myfunction = function(fn,cb) {
  fn(3,cb);
}; 

{
  register : function(obj, cb) {
      if (name.match(/^John/)) {
          cb(new Error("We don't accept new users named John"))
      } else {
          cb(null, {
              username: obj.username,
              somefunc: myfunction
          })
      }
  }
}
So back to our caller, we can do something like this

  var squared = function(x) { return x*x };
  remote.register({name:'John Smith', username:'JSmith'}, 
  function(err, resp) {
      resp.somefunc(squared,function(val) {
        // val == 9
      });
  });

(use spacebar to navigate through all the slides)

Convinced? Minimal Boilerplate (SERVER)


var http = require('http');
var shoe = require('shoe')
var dnode = require('dnode')
var ecstatic = require('ecstatic')({root:__dirname})
var server = http.createServer(ecstatic)
var remote;
var sock = shoe(function (stream) {
  var d = dnode({})
  d.pipe(stream).pipe(d);
  d.on('remote', function(_r) {
    // here you'd actually store the remote by client or some scheme
    remote = _r;
  })
})
sock.install(server, '/stream');
server.listen(5300)

(use spacebar to navigate through all the slides)

Minimal Boilerplate (CLIENT)


var dnode = require('dnode')
var shoe = require('shoe')
var remote;

$(window).ready(function() {
  var d = dnode({})
  var stream = shoe('/stream');
  d.pipe(stream).pipe(d);
  d.on('remote', function(_r) {
    remote = _r;
  })
})

(use spacebar to navigate through all the slides)

Actual server


var http = require('http');
var shoe = require('shoe')
var dnode = require('dnode')
var ecstatic = require('ecstatic')({root:__dirname})
var server = http.createServer(ecstatic)
var sock = shoe(function (stream) {
  var d = dnode({
    foo:function() {
      console.log("FOO!")
    },
    bar:function(val,cb) {
      cb(val.toUpperCase(), function(opinion) {
          console.log("Recieved opinion for capitalizing "+ val + " :" + opinion)
      }, function(fn,cb) {
        fn(5,cb)
      })
    }
  })
  d.pipe(stream).pipe(d);
  d.on('remote',function(_r) {
    console.log("Server got client remote!", _r)
    _r.alertme("Hi this is the server saying hello")
  })
})
sock.install(server, '/stream');
server.listen(5300)

(use spacebar to navigate through all the slides)

Actual Client


var dnode = require('dnode')
var shoe = require('shoe')
var remote;

$(window).ready(function() {
  var d = dnode({alertme:function(msg) { 
    alert("SERVER SAYS:"+ msg); }
  })
  var stream = shoe('/stream');
  d.pipe(stream).pipe(d);
  d.on('remote', function(_r) {
    console.log("Got remote:", _r)
    remote = _r;
    remote.foo()
  })
  $('input#mybutton').click(function() {
    var val = $('input#mytext').val();
    var myfunction = function(sendOpinion) {
      var opinion = window.prompt("Please enter your "+
      "opinion of this capitalization service:")
      sendOpinion(opinion) 
    }
    var squared = function(x,cb) { 
      cb(x*x)
    }
    remote.bar(val, function(result, sendOpinion, somefunc ) {
      $('div#results').html(result);
      myfunction(sendOpinion)
      somefunc(squared, function(val) {
        console.log("val:", val)
      })
    })
  })
})
Visit this repo for examples github.com/rook2pawn/talk-rpc