diff --git a/README.md b/README.md index 062adf3..29550e7 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. diff --git a/lib/Instance.js b/lib/Instance.js index a3293ec..1acc4ec 100644 --- a/lib/Instance.js +++ b/lib/Instance.js @@ -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); }); }; diff --git a/lib/Model.js b/lib/Model.js index 7dd5d02..2b819d0 100644 --- a/lib/Model.js +++ b/lib/Model.js @@ -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() { @@ -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 @@ -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 () { diff --git a/lib/caches/NoOpCache.js b/lib/caches/NoOpCache.js index aafe7f3..45633a1 100644 --- a/lib/caches/NoOpCache.js +++ b/lib/caches/NoOpCache.js @@ -5,6 +5,12 @@ function NoOpCache(options) { /// Options dictating the configuration of this cache } +NoOpCache.prototype.valid = function(conditions) { + /// Determines whether or not an object with the given conditions can be retrieved from this cache + /// The conditions for which the document was retrieved + /// +}; + NoOpCache.prototype.store = function(document, callback) { /// Stores a document in the cache for future access /// The database object to store in the cache @@ -13,17 +19,17 @@ NoOpCache.prototype.store = function(document, callback) { return callback(); }; -NoOpCache.prototype.fetch = function(id, callback) { +NoOpCache.prototype.fetch = function(conditions, callback) { /// Fetches the document with the matching id from the cache - /// The _id field of the document to retrieve from the cache + /// The conditions used to select the object to be returned from the cache /// A function to call with the retrieved value return callback(null); }; -NoOpCache.prototype.drop = function(id, callback) { +NoOpCache.prototype.drop = function(conditions, callback) { /// Removes the document with the matching id from the cache - /// The _id field of the document to remove from the cache + /// The conditions used to select the object to be removed from the cache /// A function to call once the document has been removed from the cache return callback(null); diff --git a/test/cache.js b/test/cache.js index 48dfd1e..311ec59 100644 --- a/test/cache.js +++ b/test/cache.js @@ -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');