terça-feira, 13 de maio de 2014

Brinquedos legais para usar com Node.js

O Node.js tem um rico ecossistema, baseado no gerenciador de pacotes NPM. Existem vários pacotes interessantes e muitos surgem a cada momento. Como me pediram uma referência, vou listar aqui alguns dos mais importantes que já usei.




NPM e a gestão de pacotes

Quando você criar um projeto Node.js, seja com o Express ou não, sempre crie um arquivo "package.json" contendo a identificação da sua aplicação e as suas dependências. É algo como o "pom.xml", do Maven, e pode ser criado até mesmo manualmente:

{
  "name": "promises-demo",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.5.0",
    "jade": "*",
    "promise" : "*",
    "request" : "*"
  }
}

É muito fácil e intuitivo. Identificamos a aplicação, sua versão e o seu comando de inicialização, depois, identificamos cada dependência, especificando a versão ou colocando um "*", para pegar qualquer versão.

Após especificar suas dependências, é só ir para a pasta que contém o arquivo "package.json" e digitar o comando: "npm install", para que o Node Package Modules Manager baixe e instale todos os módulos dentro da pasta "node_modules".

Você pode ler mais sobre a sintaxe do "package.json" AQUI.

Depois de instalado o pacote, você pode usar o "require" para criar uma variável apontando para os elementos do módulo:

var Mongoose = require('mongoose');

Se você rodar o comando "npm install", o NPM lerá seu arquivo "package.json" e vai instalar todas as dependências dele dentro da pasta "node_modules" do projeto.

Você pode gerar ou alterar o "package.json" com o comando "npm install <module> --save", fazendo com que o módulo seja instalado localmente no projeto e uma entrada seja adicionada às dependências do "package.json".

Banco de dados

Se você quiser instalar o cliente de banco de dados MongoDB, por exemplo, pode instalar o módulo: "mongodb" que é um driver para acessar bancos de dados MongoDB usando uma interface nativa Node.js. Por exemplo:

var MongoClient = require('mongodb').MongoClient
    , format = require('util').format;

  MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
    if(err) throw err;

    var collection = db.collection('test_insert');
    collection.insert({a:2}, function(err, docs) {

      collection.count(function(err, count) {
        console.log(format("count = %s", count));
      });

      // Locate all the entries using find
      collection.find().toArray(function(err, results) {
        console.dir(results);
        // Let's close the db
        db.close();
      });
    });
  })

Mas usar dessa forma não é muito documentacional... Se você gosta de JPA e Hibernate, vai preferir algum tipo de mapeamento de esquema, e o Mongoose é a solução!

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');

var Cat = mongoose.model('Cat', { name: String });

var kitty = new Cat({ name: 'Zildjian' });
kitty.save(function (err) {
  if (err) // ...
  console.log('meow');
});

Nós usamos o Mongoose no exemplo "index.js", do artigo "MEAN Stack é hora de ser mau", e é muito simples de usar.

exports.index = function(Link) {
  return function(req, res) {
    console.log("*** INDEX COM MongoDB ***");
    Link.find({}, function(error, data) {
       if(error) {
          console.log(">>> ERROR: " + error);
       }
       else {
          console.log("*** OK: " + JSON.stringify(data));
       }
      res.render('index', {
        title: 'MyLinks',
        "myLinks" : data
      });
    });
  };
};

Eis o esquema da coleção links (arquivo "/model/Links.js"):

var Mongoose = require('mongoose');

exports.LinksSchema = new Mongoose.Schema({
  description : { type : String, required : true },
  url : { type : String, required : true }
});

Agora, se você precisa de um banco NoSQL rápido, seja para Cache, Sessão HTTP ou para guardar algum dado temporário, o Redis é a melhor solução! O pacote "redis". Veja o que fizemos no arquivo "/routes/index.js", do artigo "Escalabilidade com Node.js e Redis":

var Worker = require('webworker-threads').Worker;
var redis = require("redis");
var uuid = require('node-uuid');

exports.index = function(req, res){
  res.render('index', { title: 'Operação assíncrona', botao: 'Iniciar' });
};

exports.iniciar = function(req, res) {
     //var client = redis.createClient();
     var identificador = uuid.v1();
     
     // Worker Thread:
     var worker = new Worker(function() {
          onmessage = function (event) {
            var fibonacci = function (n) {
              if (n < 2)
                return 1;
              else
                return fibonacci(n - 2) + fibonacci(n - 1);
            };
            var obj = event.data; 
console.log('worker vai iniciar: ' + JSON.stringify(obj));            
            obj.numero = fibonacci(obj.numero);         
            postMessage(obj);
          }
        });
        
     // Quando o Worker Thread concluir o cálculo:
     worker.onmessage = function (event) {
           var objeto = event.data;
console.log('worker terminou: ' + JSON.stringify(objeto));        
           var client = redis.createClient();
           client.on("error", function (err) {
               console.log('Erro 1: ' + err);
               res.send(500, 'Erro!');
           });  
           client.on("connect", function() {
              objeto.calculando = false;
              client.set(objeto.chave, JSON.stringify(objeto), function(err, res) {
                 if (err != null) {
                    console.log('Erro 2 ao gravar no redis a resposta! ' + err);
                 }
                 else {
                    client.expire(objeto.identificador, 120);
                 }
              });
           });          
      };
      
     var objeto = {numero : 45, chave : identificador, calculando : true};
     
     // Coloca o pedido no REDIS:   
     var client = redis.createClient();
     client.on("connect", function() {
        client.set(identificador, JSON.stringify(objeto), function(err, resposta) {
           if (err == null) {
              // Posta a mensagem para o Worker Thread:
              client.expire(identificador, 120); // Expira em 2 minutos
              worker.postMessage(objeto);
              res.header("Content-Type", "application/json; charset=utf-8");
              res.json({ 'chave' : identificador });
           }
           else {
              console.log('Erro 4: ' + err);
              res.send(500, 'Erro!');
           }
        });
     }); 
     
     client.quit; 
};

Threads

Trabalhar com processamento intensivo em Node.js requer um certo malabarismo, pois corremos o risco de travar o loop de eventos. Para isso, abrir um Worker Thread é uma solução simples. Existe o módulo "webworker-threads" que ajuda muito. Veja só no mesmo exemplo anterior, o código no qual iniciamos um webworker thread:

     var worker = new Worker(function() {
          onmessage = function (event) {
            var fibonacci = function (n) {
              if (n < 2)
                return 1;
              else
                return fibonacci(n - 2) + fibonacci(n - 1);
            };
            var obj = event.data; 
console.log('worker vai iniciar: ' + JSON.stringify(obj));            
            obj.numero = fibonacci(obj.numero);         
            postMessage(obj);
          }
        });

O nosso Worker vai calcular a sequência de fibonacci para o termo indicado. Quando terminar, ele vai postar uma mensagem com o resultado obtido. Eis o código que recebe a mensagem quando o Worker termina o trabalho: 

     worker.onmessage = function (event) {
           var objeto = event.data;
console.log('worker terminou: ' + JSON.stringify(objeto));        
           var client = redis.createClient();
           client.on("error", function (err) {
               console.log('Erro 1: ' + err);
               res.send(500, 'Erro!');
           });  
           client.on("connect", function() {
              objeto.calculando = false;
              client.set(objeto.chave, JSON.stringify(objeto), function(err, res) {
                 if (err != null) {
                    console.log('Erro 2 ao gravar no redis a resposta! ' + err);
                 }
                 else {
                    client.expire(objeto.identificador, 120);
                 }
              });
           });          
      };
      
E, para disparar o cálculo, basta enviar uma mensagem para o Worker:

worker.postMessage(objeto);

UUID

Podemos gerar um UUID de forma muito simples. O módulo "node-uuid" nos facilita muito a vida: 

var uuid = require('node-uuid');
...
var identificador = uuid.v1();

Processamento de requests REST

O Express é a solução mais utilizada, e é parte do MEAN stack. Para isto, basta instalar o módulo Express: "npm install express -g". Eu recomendo instalar como global, pois ele permite gerar novos projetos:

express <nome do projeto>

E ele vai gerar uma aplicação exemplo que usa Jade e já funciona para você.

Leia o artigo "MEAN stack: é hora de ser mau" para entender melhor os detalhes.

Já o Restify é bem mais simples do que o Express e, na verdade, eu o utilizo muito para aplicações mais simples.

Veja o exemplo "Microblog" do artigo "Limando o Java EE":

var restify = require('restify')
...
server.use(restify.bodyParser({ mapParams: false }));
 
// Home page:
server.get('/mb',home)
 
// Logon call: /mb/server/session/<username>/<password>
server.get('/mb/server/session/:username/:password', logon);
 
// Userdata: POST /mb/server/userdata <session>
server.post('/mb/server/userdata',userdata);
 
// Messages: POST /mb/server/messages <session>
server.post('/mb/server/messages',messages);
 
// Start server
server.listen(8080, function() {
console.log('%s listening at %s', server.name, server.url);
});