quinta-feira, 3 de abril de 2014

MEAN Stack: Hora de ser mau!

(Hector Gomez - OpenClippart.org)

Chegou a hora de sermos maus! Vamos usar o MEAN stack para cortar as cabeças dos amantes do Java EE! Vamos ver como usar um novo toolset de ferramentas para criar aplicações web, com desempenho alucinante, e disciplinadas, como todo ninja deve ser. Criei um micro tutorial para você aprender os conceitos e as ferramentas que compõem o MEAN stack, de forma simples e prática.




(logo da linnovate.net, dona do mean.io)

Segundo o Free Dictionary, o adjetivo "mean" significa: "Cruel, spiteful, or malicious", ou seja, ser mau. Não é à toa que o logo do "mean.io" é uma ninja, com espada na mão. 

Por que mau? Por que, usando o MEAN, você detonará, sem piedade, todos os amantes do Java EE, jogando seus últimos argumentos no chão, junto com suas cabeças! Brincadeiras à parte, é mais ou menos isso mesmo... 


Frameworks são demoníacos!

Eu ainda vou criar o site: "frameworksareevil.com"...

Já vejo até a galera me criticando: "Ué, mas você não era contra os frameworks para aplicações corporativas? Como é que passou a defender o MEAN?". Realmente, minha posição pode parecer meio dúbia, mas, se me der a chance de explicar, verá que tudo faz sentido. 

Existem dois tipos básicos de frameworks: os AUXILIARES e os ESTRUTURADORES. Frameworks auxiliares servem para funções específicas, e nos ajudam muito. Frameworks estruturadores são os malignos! Eles estruturam nossa aplicação, passando a nos dizer QUANDO e ONDE devemos "enfiar" nosso código (com todo respeito). Java EE é um framework estruturador, assim como todos os seus "capangas", dos quais destaco: "JSF", "JPA", "EJB", "JAX-WS" etc. 

Quando o meu amigo Gunisalvo, me falou sobre os frameworks Javascript, como o Angular.js, e o Express.js, e disse que eu deveria escrever sobre eles, mandei um tremendo "allegro, ma non troppo" de volta. Eu estou travando uma guerra termonuclear contra os frameworks corporativos, especialmente contra o ecossistema Java EE, logo, não faz sentido eu defender outro framework estruturador. 

Depois, bebendo uma "Tropper", com a cara do Eddie no rótulo, pensei melhor... É impressionante como "Alcohol fueled development" tem suas vantagens! Eu já havia usado e gostado muito do Angular.js e do Express.js, embora continuasse com algumas ressalvas, parecia ser legal usar isso. 

Então, veio a minha motivação! Estava conversando com alguns J-Panacas, que estavam criticando a linha que estou seguindo, e tentando me convencer que o Java EE ainda seria a melhor alternativa para aplicações corporativas, afinal de contas, eles organizavam a aplicação, evitando a "zona" que o Javascript proporciona. Tentaram convencer a mim! Eu, que escrevi livros sobre Java EE e que sou arquiteto certificado Java! Isso foi a gota d'água!

Então, resolvi avaliar o stack MEAN e escrever um turbo tutorial, só para calar a boca da galera. No final, eu concluí que o MEAN, apesar de ser estruturador, não é tão ruim assim, e que de fato é uma excelente alternativa às coisas que mais detesto (JAX-WS, XML, JPA e JSF). 

Por que os frameworks são ruins?

As minhas principais ressalvas quanto ao uso de frameworks, e isso inclui até o MEAN, são:
  • Restringem o desenvolvimento a um modelo pré-concebido;
  • Encapsulam as tecnologias de base e guiam seu uso;
  • Complicam a atualização de novas versões, forçando refactorings desnecessários;
  • Tornam-se obsoletos, mas as aplicações desenvolvidas com eles, continuam a ser usadas e atualizadas;
A ressalva mais grave é a última. Quem não se lembra do finado "Struts"? Quando ele surgiu, lá pelos idos de 2000, parecia ser uma boa ideia, logo, ganhou popularidade e várias aplicações foram desenvolvidas com ele. Hoje em dia, poucas equipes ainda desenvolvem novas aplicações com Struts, mas existe muito código legado desenvolvido com ele. A manutenção desse código é um verdadeiro pesadelo para os desenvolvedores, formando uma imensa dívida técnica, cujo pagamento é impossível, e os juros são muito altos. 

E o uso intenso do MEAN não fugirá a esse problema! Assim como o JSF e outros frameworks.

That being said...


Ou, se preferir: Tendo dito isso, vamos começar a ver o MEAN! Para começar, o nome significa as letras dos principais componentes do stack:
  • M: MongoDB - Um poderoso banco de dados "no-SQL", que trabalha diretamente com objetos BSON / JSON, cuja performance com grande volume de dados o torna candidato ideal a uso em aplicações de "Big data"; 
  • E: Express.js - Um framework para criação de aplicações cliente-servidor, baseadas em Javascript e Node.js, que usem REST como API. Ele se define como um "web framework" para node.js, e faz um pouco do que o JSF e o JAX-WS fazem, no Java EE;
  • A: Angular.js - É um framework MVC para uso em páginas HTML, muito interessante. Feito pela Google, ele permite criar páginas HTML 5 dinâmicas, e ajuda no conceito de "single page web site";
  • N: Node.js - É uma aplicação que usa o engine Javascript V8, do Chrome, para criar aplicações servidoras, baseadas em "no blocking I/O", que respondem muito bem ao alto tráfego e grande volume de dados. Toda a programação da sua aplicação servidora é feita em Javascript;
Vamos comentar um pouco sobre cada componente, antes de mostrarmos um micro tutorial.



Já falamos aqui sobre o MongoDB, logo, não vou encher a sua paciência com mais bla-bla-bla. Ele é Open Source, rápido, fácil de usar e usa objetos como estrutura de dados. E ele escala muito bem, graças a sua tecnologia de Sharding, que permite armazenar conjuntos de dados através de várias máquinas separadas. 

O Express.js é um framework estruturante para desenvolvimento de aplicações Web com o Node.js. Devido a essa característica, eu "torci a cara" inicialmente para ele, mas tive que me render à sua funcionalidade e facilidade de uso. Ele organiza as várias "rotas" REST, direcionando-as para as "views", que podem ser criadas com os frameworks de template Jade ou EJS

Nós começamos o tutorial criando uma aplicação "express", logo, toda a nossa estrutura de pastas e código já fica pronta, facilitando o desenvolvimento posterior. No exemplo anterior, eu usei o Restify.js com o Node.js, com tudo dentro de um único script. 

O Express cria uma estrutura para nossas aplicações com as seguintes pastas:


/projeto
......../node_modules
......../public
.............../images
.............../javascripts
.............../stylesheets
......./routes
......./views
.......app.js


Na pasta "node_modules", o "npm" (gerenciador de pacotes node) carrega os módulos que nós vamos instalar para essa aplicação.

Na pasta "public" vai todo conteúdo que deve ser enviado ao navegador do usuário, como resposta aos requests, por exemplo: código javascript para execução no browser, imagens e stylesheets.

Na pasta "routes" fica o código javascript servidor, que o Node.js executará em  resposta a um request do Cliente. Essas "rotas" são associadas dentro do arquivo "app.js", também criado automaticamente pelo Express.js, sendo o bootstrap da nossa aplicação.

Funciona assim:

  1. O "app.js" cria uma rota REST, baseada em um método HTTP e uma URL, associando-a com um script js, que fica dentro da pasta "routes";
  2. Ao chegar um request, o Express.js o desvia para a rota correta, invocando o Javascript que designamos para tratá-lo;
  3. O script trata o request e renderiza a resposta em JSON ou usa um dos templates Jade, armazenados na pasta "views", para gerar uma resposta ao Cliente. Se ele requisitar algum recurso da pasta "public", este será disponibilizado automaticamente;
O Angular.js é um framework MV* (MVC, MVW etc) para uso com HTML. Ele permite manipularmos o HTML, sem nos envolvermos diretamente com o DOM, e funciona bem com qualquer tipo de HTML, incluindo o HTML 5. Sua característica MVC fica evidenciada com o uso de "Controllers" e com o two-way data binding, que permite sincronizar a view (componentes HTML) com o model (variáveis de escopo) automaticamente. Assim, podemos alterar a página HTML automaticamente, com base em queries JSON, sem ter que alterar o DOM manualmente, como fazemos com o jQuery. 

Para usar o Angular, temos que instalar o seu pacote em nosso projeto, incluindo seu Javascript, que deve ser enviado ao cliente. Depois, criamos diretivas dentro dos tags, identificando-os como partes de uma aplicação MVW Angular (são atributos "ng-"). E criamos "Controllers", que são funções Javascript para coordenar os eventos da view e as alterações do model.

Uau, o que mais podemos falar sobre o Node.js, além de tudo o que já falamos aqui? Muita coisa! Na verdade, o Node.js é assunto para muitas horas de conversa, preferencialmente regadas a um bom chope, com bolinhos de bacalhau para acompanhar (acho que estou com fome). 

O Node.js funciona com um único thread, e usa um loop de eventos para processar suas funções assíncronas. Por isso é extremamente rápido e eficiente para aplicações de muito thoughput, como RESTful web services. O ponto fraco é justamente o esquema de thread único, que pode travar se for utilizado para cálculos intensivos ou acesso síncrono a recursos. 

A escalabilidade de aplicações Node.js se baseia em subir várias VMs, cada uma rodando uma instância do Node.js, e balanceando a carga entre elas. 

O Node.js vem com uma grande biblioteca para criação de servidores, com quase todas as suas funções baseadas em "callback", ou seja, invocamos uma função e passamos outra, como argumento. Quando o loop de eventos detectar o término, ele invoca a função "callback". 

Preparando o terreno

Antes de começar a desenvolver o tutorial, é preciso instalar algumas coisas. Vamos ver um passo a passo rápido.

A opção mais rápida é instalar um stack MEAN customizado, como o da MEAN.io ou da BitNami, que já vem com tudo pronto para você instalar e criar aplicações. Porém, se preferir, é possível instalar tudo manualmente, o que lhe dará maior controle sobre o processo. É essa a abordagem que vamos usar nesse demo.

1) Instale o python 2.7

Windows ou Mac:
- Baixe de: https://www.python.org/download/releases/2.7/

Linux:
- Baixe do mesmo link ou então use o repositório "deadsnakes"

sudo add-apt-repository ppa:fkrull/deadsnakes
sudo apt-get update
sudo apt-get install python2.7

2) Instale o node.js e o npm:

Windows ou Mac:
- Baixe e rode: http://nodejs.org/download/

Linux: 
- Baixe do mesmo link ou use o repositório abaixo:

sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install python-software-properties python g++ make nodejs

3) Instale o MongoDB:

Windows:
- Visite o link: http://docs.mongodb.org/manual/tutorial/install-mongodb-on-windows/

Mac: 
- Visite o link: http://docs.mongodb.org/manual/tutorial/install-mongodb-on-os-x/

Linux: 
- Visite o link: http://docs.mongodb.org/manual/administration/install-on-linux/

4) Instale o Express.js:

Independente do sistema operacional, abra um Terminal (ou command prompt):
- npm install express -g

5) Instale o Bower:

- npm install bower -g

O Bower é um gerenciador de pacotes para o front-end de uma aplicação web, ou seja, pacotes clientes. Ele funciona com o Git e pode baixar dependências também. Ao invés de baixar manualmente os arquivos ".js" e "css" necessários, use o Bower, que já baixa todas as dependências de cada pacote automaticamente.

6) Só falta o "A", que deve ser instalado dentro da nossa aplicação de exemplo:

Vamos instalar o Angular.js na parte cliente da nossa aplicação, usando o Bower. Antes disso, é preciso fazer algumas configurações.

O Bower vai tentar usar as urls originais, que podem usar o protocolo "git://". Se você tem um maldito firewall, então não vai conseguir... A solução é configurar o git para usar "https://:

git config --global url."https://".insteadOf git://

a) Crie uma pasta "vendor" dentro de "public/javascripts";

b) Crie um arquivo ".bowerrc" dentro da pasta raiz do projeto, com o conteúdo:
{ "directory" : "public/javascripts/vendor" }

c) Instale o angular.js:
- bower install angular

Criando a aplicação "linkmania":

Vamos criar uma aplicação muito simples, que exibe uma lista de links, cadastrada em um banco MongoDB. Para esse micro tutorial, nem vamos implementar um CRUD completo, pois acho que isso complica muito as coisas nesse momento. Eu recomendo que você entenda esse tutorial em primeiro lugar, e depois que tiver se acostumado com o MEAN, tente um tutorial mais completo, como os do Code Barbarian:
O código fonte completo dessa aplicação pode ser baixado do GitHub. Você pode clonar o repositório ou pode baixar um ZIP.

1) Na pasta onde quer criar o projeto, digite: 

- express linkmania
- cd linkmania
- npm install

Agora, execute a aplicação:

- node app

Abra um navegador e digite

- http://localhost:3000

Pronto, sua aplicação express está pronta.

Acertando o banco de dados

Vamos usar o MongoDB, para isso, precisamos do "driver" para Mongo do Node.js, que é o pacote "mongodb", e, além disso, vamos usar um mapeador para facilitar o uso do MongoDB, o "mongoose", que faz validação, casting e lógica de persistência para o MongoDB.

Vamos deixar o npm instalar esses pacotes, então temos que configurar essas dependências no arquivo "package.json" de nosso projeto:

{
  "name": "application-name",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.5.0",
    "jade": "*",
    "mongodb": "1.3.23",
    "mongoose" : "3.8.8"    
  }
}

O arquivo "package.json" serve para o "npm" saber o que ele tem que baixar para a pasta "node_modules". Depois de acrescentar as duas linhas ("mongodb" e "mongoose") é necessário rodar "npm install" novamente.

Agora, vamos criar um arquivo de esquema para o nosso banco de dados. O MongoDB é um banco no SQL e no Schema, logo, fica mais fácil lidar com ele se tivermos algum modelo para podermos trabalhar. É exatamente o que o "Mongoose" faz. Primeiramente, vamos criar uma instäncia de Mongoose.schema para o nosso documento. Na pasta do projeto, crie uma pasta "models" e, dentro dela, um arquivo Link.js (aqui está ele):


var Mongoose = require('mongoose');

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

Este arquivo "Link.js" cria uma instância de Mongoose.schema, e exporta como "LinksSchema", permitindo que o usemos fora do script. Ele serve para tudo, desde definir como é a coleção de documentos até as funções mais comuns, como "find()".

Agora, vamos alterar o arquivo principal da aplicação para criar uma conexão com o MongoDB e instanciar o esquema. Abra o arquivo "app.js", que está na raiz do projeto, e acrescente as seguintes linhas antes do comentário "// development only":


var Mongoose = require('mongoose');
var db = Mongoose.createConnection('mongodb://localhost:27017/mylinks');
var LinksSchema = require('./models/Link.js').LinksSchema;
var Link = db.model('Link', LinksSchema, 'links');

Criamos uma conexão com o MongoDB, apontando para o banco "mylinks". Também criamos uma variável global "LinksSchema", que aponta para a propriedade "LinksSchema", criada pelo arquivo "models/Link.js", que criamos anteriormente. Agora, podemos nos referenciar ao esquema do banco usando essa variável.

Finalmente, criamos uma variável do tipo "Mongoose.Model" chamada "Link", que vai representar o modelo do banco. O método "model" cria o modelo e os parâmetros são: Nome do modelo, Variável de esquema e nome da coleção dentro do MongoDB. Agora, sempre que fizermos "Link.find()" ele vai retornar a coleção de documentos "links", que estiver no banco "mylinks".

Criando uma página inicial

Precisamos criar uma página inicial, onde nossa "mágica" vai acontecer. Essa será uma "single-page" application, logo, ela será modificada de acordo com o estado do modelo, o que será feito pelo Angular.js. Mas isso acontecerá no Cliente.

O Express.js trabalha com templates de HTML, e, como não especificamos nada, ele está esperando páginas feitas em Jade. Na verdade, o Express.js já criou duas páginas Jade para nós, que ficam na pasta "views". Dessas só vamos precisar da "layout.jade" e da "index.jade". o Jade tem o mecanismo de herança, que é muito útil. Vamos criar o arquivo principal, que vai ser o "pai" de todas as nossas páginas (layout.jade):


doctype html
html(ng-app='mylinks')
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(type='text/javascript', src='/javascripts/vendor/angular/angular.js')
    block head
  body
    block content

Com o Jade, é muito mais fácil escrever templates HTML, e há uma farta documentação disponível. A identação é muito importante, pois "aninha" os tags gerados. Nesta página estamos definindo o que será geral para a aplicação, como a inclusão do Javascript do Angular.js, por exemplo. Note que definimos um atributo "ng-app" para o tag "html", indicando que esta página faz parte de uma aplicação Angular.js.

Também definimos dois blocos "head" e "content", que serão sobrescritos na página filha, "index.jade":


extends layout
 
block head
  script(type='text/javascript', src='/javascripts/controllers/mylinks.js')

block content
   div.container(ng-controller="LinksController", ng-init="getAll( #{JSON.stringify(myLinks)} )")
       h1 Minha lista de links
       ul
         li(ng-repeat="link in colecaoLinks")
            a(href='{{link.url}}') {{link.description}}
       <input ng-click="update()" type="button" value="Atualizar" />

Aqui, começamos a usar o Angular.js! No início do bloco "content", definimos uma DIV, que usa um Controller Angular.js. No momento da inicialização, ou seja, a primeira vez que este tag for renderizado, ele vai invocar o método "getAll" do Controller, passando a variável Javascript "myLinks", que foi recebida junto com a página.

Note que fizemos um loop de "<li>", para cada elemento da lista de links, mostramos um link com a descrição, apontando para a sua URL.

Ao final, temos um botão que invoca o método "update()", do nosso Controller Angular.js. Eu fiz questão de deixar esse botão em HTML, para mostrar que podemos misturar Jade com HTML sem problema algum.

Temos duas ações que são executadas. No momento da carga da página, vem um HTML e vem uma lista de links, e temos uma ação de refresh dos dados, que só atualiza a lista, mas não a página. Como a página vai se redesenhar? Quando uma variável do modelo é alterada, a view é alterada para refletir seu estado. Este é o two-way data binding, do Angular.js. Se a variável "colecaoLinks", criada pelo nosso Controller, for alterada, o código será re-executado para atualizar a view.

O código do Controller é muito simples. Veja o arquivo "public/javascript/controllers/mylinks.js, que é o nosso Controller Angular.js:

angular.module('mylinks', [])
 .controller('LinksController', ['$scope', '$http',function($scope, $http) {
   $scope.colecaoLinks = [];

   $scope.getAll = function (mlinks) {
     $scope.colecaoLinks = mlinks;
   };
   

   $scope.update = function() {
     $http.get('/update').success(function(lista) {
       $scope.colecaoLinks = lista.myLinks;
     });
   };   
   
 }]);

Criamos um módulo Angular.js, do tipo "Controller", e retornamos uma função, que cria a variável "colecaoLinks" dentro do escopo atual (o tag DIV). O método "getAll" simplesmente copia a variável recebida para ela, mas o método "update()" é bem interessante! Ele faz um request GET para a nossa API REST, com a URI "/update") e recebe um objeto JSON, que é associado ao modelo (variável "colecaoLinks").

Se esse método for invocado, o módulo "$http" do Angular.js vai fazer um request Ajax para nosso código node.js, e receber um JSON de resposta, atualizando o modelo. O Angular.js se encarregará de atualizar a view novamente. É um framework MVW - Model-View para Web.

Sem o Angular.js, teríamos que usar o jQuery e alterar o DOM manualmente, para refletir o estado do modelo.

Criando a parte servidora

A parte servidora será atendida pelo Express.js. Ao receber um request, nós temos que criar "rotas" para eles, baseados no método HTTP e na URI. Isto é feito usando o próprio Express, dentro do script "app.js", que fica na raiz do projeto. Crie as rotas entre o "if" do "development-only" e o "createServer":


// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/update', routes.update(Link));
app.get('/', routes.index(Link));


http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

Se recebermos um request GET, para a URI "/update", invocaremos a rota "update", passando o nosso Modelo Mongoose. Se recebermos um request GET, para a rota default "/", invocaremos a rota "index", passando o nosso modelo Mongoose.

As duas rotas ficam dentro da pasta "routes", dentro do arquivo "index.js":

/*
 * GET home page.
 */

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
      });
    });
  };
};

/*
 * GET new data.
 */

exports.update = function(Link) {
   return function(req, res) {
      var resposta = "";
      console.log("*** Refresh dos dados ***");
      Link.find({}, function(error, data) {
       if(error) {
          console.log(">>> ERROR: " + error);
          resposta = {'erro' : error};
       }
       else {
          console.log("*** OK: " + JSON.stringify(data));
          resposta = data;
       }      
       res.header("Content-Type", "application/json; charset=utf-8");
       res.json({ 'myLinks' : resposta });
      });
   };
};

São dois objetos FUNCTION do Javascript, um na propriedade "index" e outro na propriedade "update". No Node.js, sempre trabalhamos com scripts de callback, como nesse caso.

A primeira rota, simplesmente retorna uma resposta HTML. Eu mando renderizar a view "index", criada pelo arquivo "views/index.jade", e passo um objeto JSON para isso. Ele indica o nome da view, e as duas variáveis Javascript que devem ser passadas ao template Jade (lembra?): title e myLinks.

E de onde eu obtive a lista de links? Do Banco de dados, através do nosso Mongoose Model, que é a variável Link, passada como parâmetro para essa rota.

A rota "update" retorna uma resposta JSON, contendo apenas a lista de links. Note que eu uso o método "json", do objeto "res", ao invés do método "render". Ele não vai usar nenhum template Jade para isso.

O uso de um engine de template me permite customizar a geração do HTML que será enviado ao Cliente, evitando que meu script servidor tenha que "escrever" HTML manualmente.

Pronto

Minha aplicação MEAN está pronta! Eu tive que escrever os seguintes arquivos:

  • Esquema do banco de dados (models/Link.js);
  • Abrir conexão e instanciar o esquema (app.js);
  • Criar templates Jade (views/layout.jade e views/index.jade);
  • Criar um controller Angular.js (public/javascript/controllers/mylinks.js);
  • Configurar as rotas do servidor REST (app.js);
  • Criar duas rotas (routes/index.js);
É claro que se trata de uma aplicação muito simples, mas o objetivo desse "micro tutorial" e dar uma ideia do que é o MEAN stack e COMO se desenvolve com ele. 

Todo o código fonte da aplicação está disponível.

Antes de finalizar, deixe-me mostrar alguns screenshots:



Aqui é a janela Command Prompt, na qual instancio a aplicação com o comando "node app.js". Ela também mostra as mensagens que mandei mostrar na console.


Aqui é a tela do programa cliente Mongo, que usei para inserir registros no banco de dados.


Esta é a janela do browser, com a aplicação funcionando. Se inserirmos um novo registro no Banco, e pressionarmos "Atualizar", ele será exibido, sem necessidade de renderizar a página toda.

Conclusão

Eu achei muito fácil e divertido criar essa mini aplicação. Estou até preparando uma demonstração melhor, e estou considerando migrar um jogo meu para MEAN, e hospedar no Heroku. Foi muito fácil e, apesar de parecer com o Java EE (Express.js é estruturante e Angular.js parece com JSF), é bem mais simples e desconectado, permitindo substituir os elementos sem grande esforço, ou seja: uma verdadeira arquitetura diluída. 

Existe plugin para eclipse, o que eu mostrei no meu post anterior, e existe debug e tudo mais. Acho que os desenvolvedores acostumados com Java EE se sentirão muito a vontade com o MEAN. 

Considerando o que eu mostrei sobre o Mercado, você deveria pelo menos conhecer o MEAN stack.