Skip to content

Commit

Permalink
First Glaze
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Andrejewski committed Jun 9, 2013
0 parents commit 4266e81
Show file tree
Hide file tree
Showing 7 changed files with 383 additions and 0 deletions.
44 changes: 44 additions & 0 deletions README.md
@@ -0,0 +1,44 @@
# Glaze
The database-agnostic caching layer for Mongoose.

## How and Why
Glaze was made to work seamlessly with Mongoose in providing a layer of caching on top of the already fast MongoDB. The concept of Glaze is very simple: a mongoose model has relational data is that either changing to quickly or not best stored to be queued and calculated through only MongoDB. Glaze can theoretically store any data in key-value sense, but was created with realtime(ish) counts, indexes, and joins in mind that can take advantage of the speed of caching.

Glaze is most like a cached data layer; you call for that layer to computed on top of the current Mongoose model and the data is filled in using the provided cache. That's why I named it Glaze.

Glaze is database-agnostic in how it interacts with the caching layer and its own mechanisms. Although Glaze was originally only for Redis caching, Glaze has been reformatted to work with any database that can meet the Glaze Interface Format. See the 'Glaze Interface Format' section for more details as to what is supported and what can be supported by viewers like you.

## Install
Just use NPM 'npm install glaze' in your project directory, use 'npm install glaze -g' if you want to use it anywhere.

## Glazing
To use Glaze, you really only have to migrate your relational Mongoose code into the Glaze.Cache initializer. This is very simple to do:



## Glaze Interface Format

### Current Built-in GIFs
- Redis

### How to GIF
Borrowing from Go (Golang), GIF is any object that meets the following method requirements:

- interface#init:
> (options object)
< returns undefined
This function is called on the initialization of an interface. This is where connection or client of the database should be established and in some way attached to the interface object.

- interface#write:
> (model Mongoose.model, key string, value any, next function)
< next(err error, result any)
This function will write to the database. Glaze assumes a key-value store will be used as most caching databases are, but the function itself can shoe-horn into any database. Glaze won't mind a little customization.

- interface#read:
> (key string, next function)
< next(err error, result any)
This function will read from the database. Glaze assumes a key-value store will be used as most caching databases are, but the function itself can shoe-horn into any database. Glaze won't mind a little customization. You can return 'false' for err and Glaze will calculate the value, add it to the cache, and reread it automatically.

And that is GIF. You can make your own interface and merge it into your own Glaze fork. Send a pull-request and get it added maybe?

129 changes: 129 additions & 0 deletions lib/cache.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions lib/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions lib/redis.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 109 additions & 0 deletions src/cache.coffee
@@ -0,0 +1,109 @@
# cache.coffee
# init: chris andrejewski 6/8/2013

_ = require 'underscore'
async = require 'async'
## redis = require 'redis'
db = {}

module.exports = (gif) ->
db = gif

class Cache
constructor: (@model, @attributes = {}, options) ->
if (!(this instanceof Cache))
return new Cache(@model, @attributes, @redit);

db.init options

a = _ @attributes
@keys = a.keys
@calc = a.values

this.attach()

attach: ->
operations = ['cache','cast']
for action in operations
@model.method action, this[action]()

# OPERATIONS

# writes the cache, calls the cast by default
cache: ->
cache = this
return (attributes = cache.keys, next) ->
model = this

if _(attributes).isNull() is true
attributes = cache.attributes

if typeof attributes is typeof String
attributes = [attributes]

if attributes.length is 0
return next model, null

# async-ly write cache changes
async.map attributes, cache.write, (err, changes) ->
throw err if err
# finally
if next
model.cast next

# does the actual writing
write: (attribute, done) ->
redit = @redis
model = @model

key = cacheKey model, attribute

next = (value) ->
db.write(model, key, value, done)

# actually calculate the value
@attributes[attribute].call @model, next

# reads values from the cache and updates the model instance
cast: ->
cache = this
return (next) ->
model = this

if cache.keys.length is 0
return next model, null

# async-ly write cache changes
async.map cache.keys, cache.read, (err, changes) ->
throw err if err
# finally
if next
next model, changes

# does the actual reading
read: (attribute, done) ->

cache = this
redit = @redis
model = @model

ckey = cacheKey model, attribute

next = (err, result) ->
model[attribute] = result
done err, result

# get value
db.read key, (err, result) ->
if err is false
cache.write attribute, next
else
next err, result

return Cache


cacheKey = (model, attribute) ->
model.modelName+':'+model._id+':'+attribute


11 changes: 11 additions & 0 deletions src/index.coffee
@@ -0,0 +1,11 @@
# index.coffee
# init: chris andrejewski 6/8/2013

module.exports =
Cache : require './cache'
Redis : ->
require './redis'




34 changes: 34 additions & 0 deletions src/redis.coffee
@@ -0,0 +1,34 @@
# redis.coffee
# init: chris andrejewski 6/8/2013

redis = require redis;

# Interface
module.exports = Interface

Interface.init = (options) ->
this.client = redis.createClient(options)

Interface.write = (model, key, value, next) ->
client = this.client
client.set [key, value], (err, result) ->
(redis.print err, result) if err
next err, result

client.expire key, expireTime model, (err) ->
(redis.print err, result) if err

Interface.read = (key, next) ->
client.get key, (err, result) ->
(redis.print err, result) if err
# assume err == DNE; must cache now
if err
next false
else
next err, result

# Helpers

expireTime = (model) ->
model.get 'expire' || 1000*30; # ms*sec*min*hr*day :: 30secs

0 comments on commit 4266e81

Please sign in to comment.