Skip to content

Commit

Permalink
Allow more complex caching designs
Browse files Browse the repository at this point in the history
Caches are now selected based on whether they report an ability to cache the given
document's selection conditions. This should allow more flexible usage of caches
in real world applications (where you won't necessarily be using _id the whole time)
  • Loading branch information
notheotherben committed Jan 10, 2014
1 parent dcba1ac commit dc1ca73
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 16 deletions.
37 changes: 37 additions & 0 deletions README.md
Expand Up @@ -20,6 +20,8 @@ Iridium hopes to solve these issues by providing a bare bones ORM targeted at po
Everyone who has written code using Node.js knows about Express, to help make your life easier we've included support right out of the box for Express.
- **Powerful Models**
Iridium's models are designed to exist as individual files or modules within your application, this helps simplify management of your models and separates database design code from your application code. In addition to this, Iridium supports virtual properties, extension methods, transforms, client side property renaming and validations in an easy to understand and implement package.
- **Caching Support**
High performance web applications depend on accessing your data as quickly as possible, Iridium provides support for automated inline caching through any key-value store, allowing you to ensure that you can build the fastest application possible.
- **Plugin Framework**
Iridium allows the creation and use of plugins which can extend models and reduce duplicated code across models for common behavioural use cases. Plugins can provide custom validation, manipulate models at creation time and have the opportunity to extend instances when they are created.

Expand Down Expand Up @@ -249,6 +251,41 @@ Instance.remove();
Instance.remove(function(err));
```

## Caching Framework
Our caching framework allows basic queries to be served against a high performance cache, offloading requests from your database server and allowing you to more easily develop high performance applications that scale well to user demand.

Your cache will **only** be tried for `Model.get` and `Model.findOne` requests for which the cache's `valid()` function returns true, allowing you to implement any basic cache structure you wish - including compound caches should you wish.

By default Iridium doesn't cache anything, implementing a no-op cache, but you can easily configure your own caching plugin on a per-model basis by following this example.

```javascript
function MemoryCache() {
this.cache = {};
}

// Tells Iridium whether it can use the cache for objects that match these conditions
MemoryCache.prototype.valid = function(conditions) {
return conditions && conditions._id;
};

MemoryCache.prototype.store = function(document, callback) {
var id = JSON.stringify(document._id);
this.cache[id] = document;
callback();
};

MemoryCache.prototype.fetch = function(id, callback) {
var id = JSON.stringify(conditions._id);
callback(this.cache[id]);
};

MemoryCache.prototype.drop = function(conditions, callback) {
var id = JSON.stringify(conditions._id);
if(this.cache[id]) delete this.cache[id];
callback();
};
```
## Preprocessing Framework
The preprocessing framework allows Iridium to convert values from a form better suited to your database into a form more suitable for your application in an entirely transparent manner. This is acomplished through the use of a number of preprocessors which run when retrieving an object from the database, their order is reversed when pushing an object to the database.
Expand Down
2 changes: 1 addition & 1 deletion lib/Instance.js
Expand Up @@ -160,7 +160,7 @@ Instance.prototype.remove = Instance.prototype.delete = function(callback) {
if(this.__state.isNew) return (callback || function() { })(null, 0);

var conditions = this.__state.model.uniqueConditions(this.__state.modified);
this.__state.model.cache.drop(conditions._id, function() {
this.__state.model.cache.drop(conditions, function() {
this.__state.model.collection.remove(conditions, { w: callback ? 1 : 0 }, callback);
});
};
Expand Down
16 changes: 9 additions & 7 deletions lib/Model.js
Expand Up @@ -416,10 +416,7 @@ Model.prototype.findOne = Model.prototype.get = function (conditions, options, c
cache: true
});


var isID = !_.isPlainObject(conditions);

if (isID) conditions = this.downstreamID(conditions);
if (!_.isPlainObject(conditions)) conditions = this.downstreamID(conditions);
this.toSource(conditions);

var fromDB = (function() {
Expand All @@ -431,8 +428,8 @@ Model.prototype.findOne = Model.prototype.get = function (conditions, options, c
}).bind(this));
}).bind(this);

if(isID && this.cache && options.cache)
this.cache.fetch(conditions._id, (function(err, doc) {
if(options.cache && this.cache && this.cache.valid(conditions))
this.cache.fetch(conditions, (function(err, doc) {
if(!err && doc)
return this.onRetrieved(doc, callback, null, { wrap: options.wrap, cache: false });
else
Expand Down Expand Up @@ -595,7 +592,12 @@ Model.prototype.remove = function (conditions, callback) {

this.toSource(conditions);

this.collection.remove(conditions, { w: callback ? 1 : 0 }, callback);
if(this.cache && this.cache.valid(conditions))
this.cache.drop(conditions, function() {
this.collection.remove(conditions, { w: callback ? 1 : 0 }, callback);
});
else
this.collection.remove(conditions, { w: callback ? 1 : 0 }, callback);
};

Model.prototype.aggregate = function () {
Expand Down
14 changes: 10 additions & 4 deletions lib/caches/NoOpCache.js
Expand Up @@ -5,6 +5,12 @@ function NoOpCache(options) {
/// <param name="options" type="Object">Options dictating the configuration of this cache</param>
}

NoOpCache.prototype.valid = function(conditions) {
/// <summary>Determines whether or not an object with the given conditions can be retrieved from this cache</summary>
/// <param name="conditions" type="Object">The conditions for which the document was retrieved</param>
/// <return type="Boolean"/>
};

NoOpCache.prototype.store = function(document, callback) {
/// <summary>Stores a document in the cache for future access</summary>
/// <param name="document" type="Object">The database object to store in the cache</param>
Expand All @@ -13,17 +19,17 @@ NoOpCache.prototype.store = function(document, callback) {
return callback();
};

NoOpCache.prototype.fetch = function(id, callback) {
NoOpCache.prototype.fetch = function(conditions, callback) {
/// <summary>Fetches the document with the matching id from the cache</summary>
/// <param name="id" type="Mixed">The _id field of the document to retrieve from the cache</param>
/// <param name="conditions" type="Object">The conditions used to select the object to be returned from the cache</param>
/// <param name="callback" type="Function">A function to call with the retrieved value</param>

return callback(null);
};

NoOpCache.prototype.drop = function(id, callback) {
NoOpCache.prototype.drop = function(conditions, callback) {
/// <summary>Removes the document with the matching id from the cache</summary>
/// <param name="id" type="Mixed">The _id field of the document to remove from the cache</param>
/// <param name="conditions" type="Object">The conditions used to select the object to be removed from the cache</param>
/// <param name="callback" type="Function">A function to call once the document has been removed from the cache</param>

return callback(null);
Expand Down
11 changes: 7 additions & 4 deletions test/cache.js
Expand Up @@ -16,19 +16,22 @@ function EventEmitterCache() {
}

EventEmitterCache.prototype.__proto__ = EventEmitter.prototype;
EventEmitterCache.prototype.valid = function(conditions) {
return conditions && conditions._id;
};
EventEmitterCache.prototype.store = function(document, callback) {
this.emit('store');
var id = JSON.stringify(document._id);
this.cache[id] = document;
callback();
};
EventEmitterCache.prototype.fetch = function(id, callback) {
id = JSON.stringify(id);
EventEmitterCache.prototype.fetch = function(document, callback) {
var id = JSON.stringify(document._id);
if(this.cache[id]) this.emit('fetched');
callback(this.cache[id]);
};
EventEmitterCache.prototype.drop = function(id, callback) {
id = JSON.stringify(id);
EventEmitterCache.prototype.drop = function(document, callback) {
var id = JSON.stringify(document._id);
if(this.cache[id]) {
delete this.cache[id];
this.emit('dropped');
Expand Down

0 comments on commit dc1ca73

Please sign in to comment.