souvenir
v1.1.1
Published
Unobtrusive caching for async functions
Downloads
16
Readme
souvenir
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
- there is no change for the external consumer of
get_user_details
. - 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 withdefault_ttl
: optional integer (default300
, 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 withnamespace
optional string for namespacing cache entries to prevent collision. Cache entries are created based on a hash of theoptions
being passed tobare_method
. It's easy to imagine two methods (e.g.get_username
andget_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'sdefault_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
optionalfunction()
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, noerror
orresult
should be passed. - if
key
is found in the cache, the cached value should be passed asresult
.
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
.