souvenir

1.1.1 • Public • Published

souvenir Circle CI

Unobtrusive caching for asynchronous functions.

Installation

$ npm install souvenir

Example

Souvenir is a library for caching the results of asynchronous functions. Instead of cluttering your function's logic with cache implementation, the souvenir cache layer is above the code:

Before souvenir

user.js
var database = require("...");
 
// options: object with
// user_id
function get_user_details(options, callback) {
    get_cache(options, function(error, result) {
        if (result)
            return callback(null, result);
 
        database.query("SELECT * FROM user WHERE ...", function(error2, result2) {
            if (error2)
                return callback(error2);
 
            save_cache(options, result2, function() {
                callback(null, result2);
            });
        });
    });
}
 
exports.get_user_details = get_user_details;
elsewhere.js
var user = require("./user.js");
user.get_user_details({ "user_id": 123 }, ...);

After souvenir

user.js
var database = require("...");
var souvenir = require("souvenir");
var cache = new souvenir.cache(new souvenir.cache_providers.memory());
 
// options: object with
// user_id
function get_user_details(options, callback) {
    database.query("SELECT * FROM user WHERE ...", callback);
}
 
exports.get_user_details = cache.wrap(get_user_details);
elsewhere.js
var user = require("./user.js");
user.get_user_details({ "user_id": 123 }, ...);

Notice that

  1. there is no change for the external consumer of get_user_details.
  2. the bare function now includes only the pertinent logic.

Usage

A souvenir cache is created by

var souvenir = require("souvenir");
var cache_provider = new souvenir.cache_providers.memory();
var cache = new souvenir.cache(cache_provider);

Here we cache a slow function my_slow_computation, keeping the cached result for an hour:

var my_cached_computation = cache.wrap(my_slow_computation, { "namespace": "my_slow_computation_namespace", "ttl": 60 * 60 });
my_cached_computation({ "a": 1, "b": 7 }, function() {
    // do something with the result
});

The first time it's called, the cached computation will be as slow as usual, but any subsequent calls in the next hour (with the same options, { "a": 1, "b": 7 }) will be very quick.

Now some information has come in which makes the cached result outdated. Invalidate the cache:

cache.invalidate("my_slow_computation_namespace", { "a": 1, "b": 7 });

The next call to my_cached_computation will have to do the slow re-compute.

Cache providers

A cache provider tells souvenir how to store and retrieve cached data. This is freeform -- you can supply your own. Souvenir comes with two cache providers out-of-the-box (with souvenir = require("souvenir")):

  • souvenir.cache_providers.memory for storing cache in memory. This is useful in development but less useful when the cache should be shared across systems (because invalidation is tricky).
  • souvenir.cache_providers.redis for storing cache in redis. This can be used to share cache across systems.

A cache provider is a javascript object exposing methods get, set and invalidate. Here is a valid (but useless) cache provider:

var my_cool_cache_provider = {
    "get": function(key, callback) { callback(null, "fake data"); },
    "set": function(key, value, ttl, callback) { callback(); },
    "invalidate": function(key, callback) { callback(); }
};
 
var souvenir = require("souvenir");
var cache = new souvenir.cache(my_cool_cache_provider);

Any function wrapped by this cache will always return the string fake data and the underlying function will never be invoked.

Cache-wrapped function

Souvenir currently only supports wrapping functions with signature function(options, callback) where callback has the conventional signature function(error, result).

API

With souvenir = require("souvenir"):

new souvenir.cache(cache_provider[, cache_options])

Creates a souvenir cache, which is the point of contact between your code and the cached data.

Arguments
  • cache_provider a cache provider (see above)
  • cache_options optional object with
    • default_ttl: optional integer (default 300, which is 5 minutes), the default time-to-live (in seconds) for cache entries in this cache. This can be overridden on a per-method basis.
Example
var souvenir = require("souvenir");
var cache = new souvenir.cache(new souvenir.cache_providers.memory());

With cache = new souvenir.cache(...):

cache.wrap(bare_method[, wrap_options])

Wraps bare_method in a souvenir caching layer, using the options given in wrap_options. Returns the cache-wrapped function, which is a drop-in replacement for bare_method elsewhere in the code.

Arguments
  • bare_method function(options, callback), the function to be cached.
  • wrap_options optional object with
    • namespace optional string for namespacing cache entries to prevent collision. Cache entries are created based on a hash of the options being passed to bare_method. It's easy to imagine two methods (e.g. get_username and get_user_email) which take identical options (e.g. { "user_id": 123 }) which may collide in cache. Namespacing the wrapped methods resolves this issue.
    • ttl optional integer (defaults to the cache's default_ttl), the time-to-live (in seconds) for cache entries for this function. This is the number of seconds before the cache entry should expire under normal circumstances (i.e. aside from explicitly invalidating the cache entry).
Example
function foo(options, callback) { ... }
// Cache results of `foo` for 30 seconds.
exports.foo = cache.wrap(foo, { "namespace": "foo", "ttl": 30 });

cache.invalidate(namespace, options[, callback])

Explicitly remove an item from cache.

Arguments
  • namespace string, the namespace of the cache-wrapped function whose entry needs to be invalidated.
  • options
  • callback optional function() invoked after the cache entry has been invalidated.
Example
function get_username(options, callback) { ... }
var cached_get_username = cache.wrap(get_username, { "namespace": "get_username" });
 
function update_username(options, callback) {
    ...
    cache.invalidate("get_username", { "user_id": options.user_id }, callback);
}

Custom cache provider

A cache provider is an interface to a key/value store, exposing the following API:

get(key, callback)

Retrieve an item from the cache.

Arguments
  • key string, identifier for the cache entry to retrieve.
  • callback function(error, result).
    • should be invoked when the data has been retrieved from the cache.
    • if key is not found in the cache, no error or result should be passed.
    • if key is found in the cache, the cached value should be passed as result.

set(key, value, ttl, callback)

Write an item to the cache.

Arguments
  • key string, identifier for the cache entry to write.
  • value arbitrary, the data to be written to the cache.
  • ttl integer, the number of seconds before the cache entry should expire.
  • callback function(error) to call when the data has been written.

Note that it is the cache provider's job to expire cache entries based on TTL.


invalidate(key, callback)

Remove an item from the cache.

Arguments
  • key string, identifier for the cache entry to remove.
  • callback function(error) to call when the item has been removed.

redis cache provider

The redis cache provider, souvenir.cache_providers.redis takes one argument, a node_redis client. You are responsible for initializing the client (providing credentials, optionally selecting a database, etc.) with require("redis").createClient().

Example
var redis = require("redis");
var souvenir = require("souvenir");
var redis_client = redis.createClient();
var cache = new souvenir.cache(new souvenir.cache_providers.redis(redis_client));

Development

The test suite can be run with npm test. Tests run on every push in CircleCI: https://circleci.com/gh/MakerStudios/souvenir

The tests for the redis cache provider expect a redis server to be running at localhost:6379. You can specify a REDIS_HOST environment variable to instruct the tests to look elsewhere, e.g. REDIS_HOST=a.b.c.d npm test.

Readme

Keywords

none

Package Sidebar

Install

npm i souvenir

Weekly Downloads

8

Version

1.1.1

License

none

Last publish

Collaborators

  • makerstudios