Страницы

воскресенье, 20 июля 2014 г.

Node.js Singleton Sample App

Пример приложения на Node.js + Express + MongoDB Native Driver в продолжение темы предыдущего поста, которое можно рассматривать в качестве "заготовки" модели. Приложение использует общий конструктор моделей доступа к данным, в котором для примера реализован единственный метод для извлечения одного документа коллекции, кэш созданных объектов, а также кэш запросов к базе данных.

Исходный код приложения:

app.js:
var express = require('express');
var app = express();

var test = require('./model')({prop: 'test'});
var messages = require('./model')({prop: 'messages'});

app.get('/test', function (req, res) {  
  test.findOne({}, {_id: 0}, function(err, item) {
    if (err) return res.json({err: err.message});
    res.json(item);
  });  
});

app.get('/message', function (req, res) {  
  messages.findOne({}, function(err, item) {
    if (err) return res.json({err: err.message});
    res.json(item);
  });
});

app.listen(1337, function() {
  console.log('Magic happens at http://127.0.0.1:1337/');
});

model.js:
var i = 0; // счетчик создания объекта (экземпляра класса) Model
var model = {}; // кэш объектов Model
var cache = {}; // кэш запросов к базе данных
var cacheInt = 10000; // интервал обновления кэша запросов к базе данных
var db; // подключение к базе данных
require('./db')(function(connection) {
  db = connection;
});

module.exports = Model;

function Model(config) { // конструктор
  if (model[config.prop]) return model[config.prop];
  if (!(this instanceof Model)) return model[config.prop] = new Model(config);    
  this.prop = config.prop;
  this.counter = ++i;
  this.dbCounter = 0; // счетчик получения документа коллекции из базы данных
  this.cacheCounter = 0; // счетчик получения документа коллекции из кэша
  cache[this.prop] = {};
};

Model.prototype.findOne = findOne;

function findOne(query, proj) {
  var cb = arguments[arguments.length - 1], 
      qs = JSON.stringify(query), 
      self = this;
  if (typeof proj !== 'object') proj = {};
  if (cache[this.prop][qs]) {
    console.log('Getting item of %s collection from cache #%d', this.prop, ++self.cacheCounter);
    return cb(null, cache[this.prop][qs]);
  }
  db.collection(this.prop).findOne(query, proj, function(err, item) {
    if (err) return cb(err);    
    cache[self.prop][qs] = item;
    console.log('Getting item of %s collection from db #%d', self.prop, ++self.dbCounter);
    cb(null, item);
  });  
}

(function clear() { //обновляем кэш запросов
  setTimeout(function() {
    for (var prop in cache) {
      cache[prop] = {};
      console.log('Cache of %s collection has been cleared', prop);
    }    
    clear();    
  }, cacheInt);
}());

db.js:
var Db     = require('mongodb').Db;
var Server = require('mongodb').Server;

module.exports = function(cb) {  
  var db = new Db('test', new Server('127.0.0.1', 27017, { auto_reconnect: true }), {w: 'majority', safe: true});
  db.open(function(err, db) {
    if (err) throw err;    
    console.log('Opening new connection');
    cb(db);
  });
};

Результат после серии обращений по адресам http://localhost:1337/test и http://localhost:1337/message на стороне сервера выглядит примерно так:

Интервал обновления кэша запросов такой огромный - целых 10 секунд - разумеется в демонстрационных целях, думаю в реальном приложении могло бы хватить 50-100 мс. Прочие методы в прототип конструктора добавляем самостоятельно :).

p.s.: На ECMAScript 6 все могло бы выглядеть гораздо интереснее, но об этом как-нибудь в другой раз...