The purpose of this package is to provide a framework for aggregating simple, independent services into a complete system. Let's take an example:
var ziploc = require('ziploc');
var redis = require('redis');
var client = redis.createClient();
var UserController = {
getUserIdFromUsername: function (username, done) {
client.hget('usernames', username, done);
},
getUserFromUserId: function (id, done) {
client.hgetall('user:' + id, done);
},
getEmailAddressFromUser: function (user) {
return user.email;
}
};
ziploc
.use(UserController)
.given('Username', 'john')
.resolve('EmailAddress', function (error, email) {
console.log(email); // john@doe.com
});
To understand why and how this works, we should compare this approach to a more
traditional approach. Lets assume we have a redis database containing a
collection of users. Lets also assume that users are indexed by a unique id
property, as well as a unique username
property. A simple users.js
might be
the following:
var redis = require('redis');
var client = redis.createClient();
exports.getUserIdFromUsername = function (username, done) {
client.hget('usernames', username, done);
};
exports.getUserFromUserId = function (id, done) {
client.hgetall('user:' + id, done);
};
More often than not, the majority of our time developing software is spent
combining these independent functions into larger, more complex functions. For
example, in addition to the users.js
above, one might want to create another
function that combines the two as follows:
var users = require('./users');
exports.getUserFromUsername = function (username, done) {
users.getUserIdFromUsername(username, function (error, id) {
if (error) {
return done(error);
}
users.getUserFromUserId(id, done);
});
};
While this may not seem like an issue at first, eventually you will realize that there is no value added from this additional function. Moreover, functions like this can often make up more half of our code base.
If we can assume a consistent naming convention for functions, then we can create these intermediate functions on the fly. This package assumes the following naming conventions:
Function Name | Produces | Consumes |
---|---|---|
getFoo | Foo | |
getFooFromBar | Foo | Bar |
getFooFromBarAndBaz | Foo | Bar, Baz |
Here is a basic example:
var ziploc = require('ziploc');
var instance = {
getUserIdFromUsername: function (username) {
return username + '1234';
},
getEmailFromUserId: function (id) {
return id + '@example.com';
}
};
ziploc
.use(instance)
.given('Username', 'john')
.resolve('Email', function (error, email) {
console.log(email); // john1234@example.com
});
As you can see, the intermediate getEmailFromUsername
function is no longer
required. The package handles the transition automatically.
Templates are another feature provided by this package. They are used to eliminate duplicate code. Let's look at another example:
var ziploc = require('ziploc');
var instance = {
get$FromNullable$: function ($, value) {
if (value) {
return value;
}
throw new ReferenceError($);
}
};
ziploc
.use(instance)
.given('NullableUsername', 'john')
.resolve('Username', function (error, username) {
console.log(username); // john
});
ziploc
.use(instance)
.given('NullableEmailAddress', null)
.resolve('EmailAddress', function (error, email) {
console.error(error); // ReferenceError: EmailAddress
});
This package also provides convenience methods for creating express middleware. Let's look at an example:
var ziploc = require('ziploc')
var express = require('express');
var app = express();
app.get('/users/:username', ziploc
.express('ExpressRequest') // request type (defaults to 'Request')
.status(200) // status code to respond with on success
.json('User')); // the type to resolve
app.listen(3000);
For a more complete example using express, take a look here. Pull requests and bug reports are welcome, as always.