Giter Club home page Giter Club logo

backbone-api-client-redis's Introduction

backbone-api-client-redis Build status

Mixins that add Redis caching on top of backbone-api-client

This was built to provide an easy way to add caching to Backbone resources. Cache mechanism details can be found in the Caching documentation.

twemproxy is supported. Redis commands used: sadd, smembers, del.

Getting Started

Install the module with: npm install backbone-api-client-redis

// Create a base model to add caching to
var _ = require('underscore');
var Backbone = require('backbone');
var BackboneApiClient = require('backbone-api-client');
var BackboneApiClientRedis = require('backbone-api-client-redis');
var Github = require('github');
var redis = require('redis');
var _GithubModel = BackboneApiClient.mixinModel(Backbone.Model).extend({
  callApiClient: function (methodKey, options, cb) {
    // Prepare headers with data
    var params = _.clone(options.data) || {};
    if (options.headers) {
      params.headers = options.headers;
    }

    // Find the corresponding resource method and call it
    var method = this.methodMap[methodKey];
    return this.apiClient[this.resourceName][method](params, cb);
  }
});

// Add caching to our GithubModel and crete RepoModel
var GithubModel = BackboneApiClientRedis.mixinModel(_GithubModel);
var RepoModel = GithubModel.extend({
  resourceName: 'repos',
  methodMap: {
    read: 'get'
  },
  cachePrefix: 'repo',
  cacheTtl: 60 * 10 // 10 minutes
});

// Generate an API client for the user
var apiClient = new Github({
  version: '3.0.0'
});
apiClient.authenticate({
  type: 'basic',
  username: process.env.GITHUB_USERNAME,
  password: process.env.GITHUB_PASSWORD
});

// Create a common set of options for Backbone models
// DEV: This should be done on a per-request basis
var backboneOptions = {
  apiClient: apiClient,
  userIdentifier: 1,
  redis: redis.createClient()
};

// Fetch information for a repo with user-specific settings
var repo = new RepoModel(null, backboneOptions);
repo.fetch({
  data: {
    user: 'uber',
    repo: 'backbone-api-client-redis'
  }
}, function (err, repo, options) {
  console.log(repo.attributes);
  // Logs: { id: 19302684, name: 'backbone-api-client-redis', ...}

  // If we fetch again in another request, we will get cached data
  var repo2 = new RepoModel(null, backboneOptions);
  repo2.fetch({
    data: {
      user: 'uber',
      repo: 'backbone-api-client-redis'
    }
  }, function (err, repo2, options2) {
    console.log(repo2.attributes);
    // Logs: { id: 19302684, name: 'backbone-api-client-redis', ...}
  });
});

Documentation

backbone-api-client-redis exposes exports.mixinModel and exports.mixinCollection.

Caching

We currently cache via a fixed expiration fault-tolerant pass-through cache, request-redis-cache. This means:

  1. Look up data in Redis
  2. If it isn't found, via the normal read logic
  3. Save redis for a given TTL in Redis

If Redis has any issues, we will ignore it and talk directly to the backend via the normal read logic.

When any alterations occur (e.g. new model, update model, delete model), we will cache bust the relevant model(s) and collection(s) information.

Since we are on the backend, each request could be tied to user specific information. As a result, we require a userIdentifier for each model/collection to prevent leakage between users. If you don't care for this, feel free to use a common identifier for all items (e.g. your service's name).

The naming scheme is:

{{userIdentifier}}-{{cachePrefix}}-model-{{id}}-{{requestHash}}
{{userIdentifier}}-{{cachePrefix}}-collection-{{requestHash}}
  • userIdentifier, unique identifier for user where request originated
  • cachePrefix, namespace for resources of a given type
  • id, id of the model
  • requestHash, object-hash of the request parameters

mixinModel(ModelKlass)

Extends ModelKlass, via ModelKlass.extend, and adds caching logic on top of callApiClient.

It is expected that you have set up the core functionality of callApiClient for your API use case before using mixinModel. This is because we rely on options to be stable to guarantee a hash that is unique to the request (e.g. if a data parameter changes, it will be a different cache key).

Returns:

  • ChildModel BackboneModel, Model class extended from ModelKlass

ChildModel#cachePrefix

Namespace for resources of this type. Used in cache keys.

You must define this on the class prototype.

  • cachePrefix String|Function, prefix for keys
    • Functions should return a String

ChildModel#cacheTtl

Amount of time to save resources in cache for.

You must define this on the class prototype.

  • cacheTtl Number|Function, time in seconds to cache item for
    • Functions should return a Number

ChildModel#initialize(attrs, options)

We overwrite initialize the requisites for a few more new properties.

  • attrs Object|null, attributes to set up on the model
  • options Object, container for model options
    • userIdentifier Mixed, unique identifier for user to prevent item bleeding between users
    • redis Redis, instance of redis client
    • requestCache RequestRedisCache, optional instance of request-redis-cache
      • If this is not provided, one will be instantiated

ChildModel#callApiClient(method, options, callback)

We overwrite callApiClient with some pre/post logic for cache handling.

If this is a read request, we will attempt to read from cache and fallback to the server.

Otherwise, we will delete the relevant cache items (any associated collections of the same type and relevant models). Then, we will perform the action. Currently, we do not cache this response as if it were read data due to the requestHash logic. See #1 for discussion.

ChildModel#clearCache(method, options, callback)

In order to delete cache without taking a callApiClient action, we provide the clearCache method. Under the hood, callApiClient leverages this for non-read actions.

This will clear associated collections and relevant models from Redis.

  • method String, method to clear cache on behalf of (as if it were coming from callApiClient)
    • Possible values are: create, update, delete
    • We optimize on behalf of this parameter (e.g. create will not search/delete model items since they don't exist)
  • options Object, options that would be received by callApiClient
  • callback Function, error-first, (err), callback to handle any errors that arose during cache removal

mixinCollection(CollectionKlass)

Similar setup as mixinModel; extends CollectionKlass and adds caching logic.

This should be done after callApiClient is locked in since request parameters are taken into consideration during cache interaction.

Returns:

  • ChildCollection BackboneCollection, Collection class extended from ModelKlass

cachePrefix, cacheTtl, initialize, callApiClient, clearCache

These methods are all the same as mixinModel except for two things. Instead of requiring cachePrefix/cacheTtl, these are resolved by default via ChildCollection.Model.

For example:

var RepoModel = GithubModel.extend({
  cachePrefix: 'repo',
  cacheTtl: 10 * 60 // 1 hour
});
var RepoCollection = GithubModel.extend({
  Model: RepoModel
  // Automatically resolve {cachePrefix: 'repo', cacheTtl: 10 * 60}
});

_prepareModel(attrs, options)

We override _prepareModel as this is the way Backbone instantiated new models when fetched/created.

https://github.com/jashkenas/backbone/blob/1.1.2/backbone.js#L909-L919

It invokes the Model constructor so we add userIdentifier, redis, and requestCache to that list. This allows for using CacheModels as collection.Model without consequence.

  • attrs Object|null, attributes to set on the new model
  • object Object|null, options to pass to new model constructor

Contributing

In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint via grunt and test via npm test.

License

Copyright (c) 2014 Uber Technologies, Inc.

Licensed under the MIT license.

backbone-api-client-redis's People

Contributors

twolfson avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

twolfson

backbone-api-client-redis's Issues

Contemplate alternative integrations into `backbone-api-client`

Currently, it is a little annoying that we more/less lock in callApiClient functionality before allowing caching mixin. Should we be doing this and is there a better way (e.g. via sync overrides)?

For reference, we are currently structuring it this way to pick up changes from adjustApiClientOptions which could affect parameters and thus the hash for the request. We want the hash to be as sensitive to what is actually sent out/important as possible.

Implement `stale-fresh-cache`

While request-redis-cache is great, it is a little too naive. I would love to support stale-fresh-cache which is intended to work via:

Let's work with an example TTL (fresh = 30 minutes, stale = 60 minutes).

If we have no cached information, get it and cache it for 60 minutes.

If there is cached information, send back whatever we have.

In addition, check the TTL. If the TTL is over 30 minutes (e.g. the cache has turned stale), fetch the information in the background and update our cache.

The benefit to this mechanism is it prevents completely running out of data to serve without sacrificing performance. Additionally, it guarantees the data will expire at some point. However, it is still possible to cache stampede =/

Expose `clearCache` method

Sometimes, actions at one-off endpoints affect your data. While this is non-atomic and disgusting itself, it happens. To make sure we keep our cache accurate at these points, we should expose the cache deletion logic from callApiClient as clearCache.

clearCache(method, options, cb);

Add caching of create/save data

Currently, we only cache read responses and invalidate on everything else. However, some server support sending back the resource upon create/update. As a result, we should cache/serve that data.

Unfortunately, we guarantee requests are stored against the hash of their request options. As a result, it is impossible for us to predict what save data would line up with, if at all.

Additionally, this hash is used to switch between server upgrades easily (e.g. new attributes on request options).

Please discuss possible options in this thread.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.