Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Chris Andrejewski
committed
Jun 9, 2013
0 parents
commit 4266e81
Showing
7 changed files
with
383 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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? | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# index.coffee | ||
# init: chris andrejewski 6/8/2013 | ||
|
||
module.exports = | ||
Cache : require './cache' | ||
Redis : -> | ||
require './redis' | ||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|