testling - automated browser tests
browserling - interactive browser testing
commit c6aa8c4b0ef5656192188e04fbdc01a9b2be759e
Author: James Halliday
Date: Thu Feb 9 11:05:34 2012 +0000

Pushing out incremental changes to a service-oriented cluster can be tricky, especially when changes span multiple services.

Introducing seaport, a service registry written in node.js based on semvers.

With seaport, services are brought up with a name@version string and other processes can connect to services that match a name@semver pattern.

First spin up a new seaport server on a port:

$ seaport 9090 --secret='beep boop'

Here's an example seaport service that registers a new service web@1.2.3:

var seaport = require('seaport');
var ports = seaport.connect('localhost', 9090, { secret : 'beep boop' });
var http = require('http');

var server = http.createServer(function (req, res) {
    res.end('beep boop\r\n');
});

ports.service('web@1.2.3', function (port, ready) {
    server.listen(port, ready);
});

and here's some code that connects to the web@1.2.3 service:

var seaport = require('seaport');
var ports = seaport.connect('localhost', 9090, { secret : 'beep boop' });
var request = require('request');

ports.get('web@1.2.x', function (ps) {
    var u = 'http://' + ps[0].host + ':' + ps[0].port;
    request(u).pipe(process.stdout);
});

Running the last script prints out beep boop from the web@1.2.3 http server.

You can create seaport servers programatically. This is useful for integrating seaport with an http host router. Here's an example using bouncy:

var seaport = require('seaport');
var ports = seaport.createServer().listen(5001);

var bouncy = require('bouncy');
bouncy(function (req, bounce) {
    var domains = (req.headers.host || '').split('.');
    var service = 'http@' + ({
        unstable : '0.1.x',
        stable : '0.0.x',
    }[domains[0]] || '0.0.x');

    var ps = ports.query(service);

    if (ps.length === 0) {
        var res = bounce.respond();
        res.end('service not available\n');
    }
    else {
        bounce(ps[0].host, ps[0].port);
    }
}).listen(5000);

We can then spin up http services for the semvers 0.1.x and 0.0.x:

var seaport = require('seaport');
var ports = seaport.connect('localhost', 5001);
var http = require('http');

var server = http.createServer(function (req, res) {
    res.end('version 0.0.0\r\n');
});

ports.service('http@0.0.0', function (port, ready) {
    server.listen(port, ready);
});

and the other server...

var seaport = require('seaport');
var ports = seaport.connect('localhost', 5001);
var http = require('http');

var server = http.createServer(function (req, res) {
    res.end('version 0.1.0\r\n');
});

ports.service('http@0.1.0', function (port, ready) {
    server.listen(port, ready);
});

Now we can dynamically route to semvers based on the http host header!

$ curl -H host:stable.localhost localhost:5000
version 0.0.0
$ curl -H host:unstable.localhost localhost:5000
version 0.1.0

These http servers could themselves have dependencies on other services in the cluster. By using seaport, new code can be pushed out that spans multiple servers with very explicit backwards compatability that tolerates and encourages multiple versions of services to satisfy dependencies. The easier it is to push out crazy new experiments, the more often it will happen!

Seaport is a component for these hypothetical cluster commands that would make continuous deployment for clusters super simple.

Check out the seaport source on github or npm install seaport!

more
git clone http://substack.net/blog.git