ziploc

An extensible nano-service framework

Usage no npm install needed!

<script type="module">
  import ziploc from 'https://cdn.skypack.dev/ziploc';
</script>

README

ziploc

npm package build code climate coverage issues dependencies devDependencies downloads

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.