All posts by Kico (Henrique Lobo Weissmann)

Minha expedição ao mundo do Node.js

Este final de semana resolvi mergulhar no Node.js: apesar de ser uma plataforma que acompanho desde o lançamento nunca lhe dei a devida atenção que esta merece, então resolvi corrigir esta desfeita.

Este mergulho foi uma experiência incrível, intensa e que me fez refletir sobre diversos assuntos. Aprendi horrores e então pra finalizar (dar o primeiro grande passo), nada melhor que compartilhar com vocês minhas conclusões iniciais.

Eu e o Node.js

Talvez a melhor expressão para descrever meu relacionamento com Node.js até agora seja “curiosidade distante”. Meu primeiro contato com a plataforma foi quando surgiu: escrevi alguns mocks de webservices e APIs REST lá pelos idos de 2009/2010.

Se não me falha a memória estes mocks foram escritos usando o módulo HTTP mesmo. Na época fiquei muito impressionado pois com pouquíssimo código eu conseguia implementar aqueles servidores. Parecia fantástico (e era).

Mas na época (e ainda hoje) a JVM dominava minha vida (estou nela desde o Java 1.1 1996/97). Pra piorar, desde então vi muitas histórias de terror envolvendo o mau uso: essencialmente a má compreensão do modelo assíncrono, apresentações muito ruins sobre o assunto (muito ruins mesmo), hype excessivo, fanboys… Tudo isto gerou uma péssima impressão em mim, o que acabou me distanciando da plataforma.

(eu sei que devia focar minhas impressões em aspectos objetivos, mas é inegável (e perigoso) o poder do subjetivo)

De lá pra cá meu uso do Node.js sempre foi indireto: ou tendo como base ferramentas como o Apache Cordova, Vue.js, até mesmo a escrita de pequenos scripts internos para resolver coisas pequenas do meu dia a dia (usava o comando Node como calculadora). Nada de avançado. Nunca fiz uma aplicação web real.

Além disto sou da geração de programadores que não via o JavaScript com bons olhos. Me surpreende a popularidade da linguagem que, todos sabemos, não foi construída sobre a melhor das bases. E esta primeira impressão ainda exerce influencia sobre mim. Curiosamente mesmo assim JavaScript sempre foi uma das linguagens que mais dominei. Um sentimento do tipo: “sei que você tem muitos problemas, mas os ignoro e gosto de vocẽ”.

Começa a expedição ao redor do meu quarto

Um bom livro: recomendo!

Navegando pelo Udemy topei com uma promoção envolvendo este curso: The Complete Node.js Developer Course (2nd Edition). Custava R$ 20,00, os livros em português que havia lido a respeito detestei (idem os cursos), vi a ementa, li muitos reviews positivos, tinha um fim de semana livre, o negócio era barato e me bateu aquela vontade de aprender Node.js. Comprei. (a propósito, o curso é maravilhoso, recomendo)

De onde bateu esta vontade de aprender Node.js? Honesta e pura curiosidade e vontade de tirar a má impressão que tinha da coisa. E dado que já estou há mais da metade da minha vida na JVM, que se tornou uma espécie de “ilha de Lost” pra mim, por que não tentar sair um pouquinho deste mundo e minimizar meu determinismo linguístico? De quebra eu ainda aprenderia um pouco mais sobre o ES6 e algumas ferramentas novas. Começava a expedição.

(spoiler: ainda considero a JVM como a melhor plataforma de todos os tempos)

Já acessou hoje? Devia!

Mas este meu mergulho deveria ter um objetivo final: eu saberia se Node.js valeria à pena se construísse uma prova de conceito que o validasse enquanto tecnologia. Qual prova de conceito? Simples: reescrever parcialmente o /dev/All em Node.js e descobrir se o negócio escala mesmo, assim como se o ferramental me fornece produtividade similar à que tenho com Grails.

(o resultado foi muito interessante, aguarde e verá)

/dev/All – Node.js ou Grails?

O /dev/All tem dois componentes: o “Feed Hunter”, que é o responsável por obter os links que aparecem no site (escrito em Java usando Spring, Apache Camel e outras cositas sobre as quais escreverei em breve por que vêm surpresa aí) e o “Front-end”, feito inteiramente em Grails (3.1.9) e Vue.js.

Um dos nossos objetivos na evolução do Front-end é transformá-lo em um SPA, desacoplando-o completamente do código Grails (sim, eventualmente haverá um app, no qual já estou trabalhando). Já demos alguns passos nesta separação, o que se manifesta na adoção do Vue.js: o código Grails seria então apenas uma API REST dali pra frente.

Apesar de todos os nossos esforços, ainda acho este componente pesado: ocupa no mínimo algo em torno de 300 a 400 Mb de RAM no servidor. A vida inteira escuto que Java devora memória. Sendo assim decidi que minha prova de conceito seria a implementação de uma API que já existe no /dev/All: aquela responsável por obter os posts apresentados na página inicial. Este endpoint aqui.

Um pouco mais sobre o modelo de desenvolvimento atual e o da prova de conceito

O modelo de desenvolvimento atual usa como base aquilo que o Grails nos provê por padrão: usamos o GORM como ORM e a própria estrutura de controladores do framework para implementar estas APIs. É notório portanto que há aqui um custo adicional de memória/desempenho relativo ao ORM, entretanto no que diz respeito à produtividade, comparando o custo do desenvolvedor e do servidor, produtividade ganha e portanto o ORM fica.

O SGBD adotado é o MySQL: e aí entra a primeira dificuldade em relação ao material existente sobre o Node.js: 99% do que existe hoje usa o MongoDB como base de dados. Eu teria de aprender portanto como usar o MySQL com Node.js. Usei o módulo mysql na versão 2.5.4 (e o aprendizado foi super rápido).

No caso do Node.js não encontrei um módulo de ORM com bases relacionais e, sinceramente, desta vez quis evitar. Um dos meus objetivos foi também fugir um pouco do desenvolvimento estritamente orientado a objetos e partir para uma abordagem mais funcional (quem acompanha este blog sabe que tenho lá meus problemas com OO).

As impressões

JavaScript  – ES6

Foi uma excelente oportunidade para aprender de vez o ES6 e aqui aquela minha antiga impressão a respeito da linguagem foi embora. Querendo ou não eu acabava escrevendo código JavaScript tal como havia o conhecido lá no início da minha carreira no final dos anos 90: de repente veio um upgrade gigantesco e muitas das coisas que não conseguia entender se tornaram claras.

Já faz algum tempo que estava me dedicando ao estudo da linguagem, mas ainda não havia me debruçado sobre o ES6. Foi sem sombra de dúvidas uma verdadeira reciclagem neste aspecto. Muitas coisas que não entendia agora fazem sentido:funções arrow, a modularidade, e muitos aspectos envolvendo melhorias da própria sintaxe.

Ferramental do Node.js e tempo de execução

Uma surpresa maravilhosa: tal como no Grails, tudo o que preciso para trabalhar é da interface de linha de comando e um editor de textos. Mas mais do que isto, as ferramentas em si são bastante produtivas: nodemon para carregamento automático das mudanças que realizo no código fonte,  as ferramentas de depuração nativas do Node, o próprio npm (que já conhecia e inclusive devemos lançar um guia esta semana)… Fantástico pra dizer o mínimo.

O carregamento do código e o tempo de execução foram surpreendentes: muito mais rápido que o que eu esperava. Sobre isto vou inclusive falar mais à frente.

Escrita de testes com Mocha, Expect e Supertest

Sempre que vou aprender algo novo os testes viram meu laboratório. Até então escrevia meus testes no navegador usando o Jasmine. É uma solução legal, mas nesta expedição acabei conhecendo o Mocha, que é inclusive muito parecido. Não houve grandes mudanças para mim neste ponto portanto.

O interessante veio com o Expect: ele tem uma funcionalidade muito interessante chamada “spy”. Essencialmente é um “AOP para testes”, que te permite verificar se um método foi ou não chamado.

O Supertest também achei muito bacana: é usado para escrever testes em cima de requisições HTTP geradas pelo Express, ou seja, me permite escrever os testes funcionais de uma forma bastante simples.

O bacana é que com o nodemon podemos ter os testes executando a cada alteração que fazemos no código, isto se mostrou uma mão na roda no meu laboratório interno.

O ExpressJS

Do lado JVM já temos alternativas que seguem a direção do ExpressJS, como o Ratpack (não conhece? devia!) e o Vert.x (literalmente o Node na JVM). É o modelo de desenvolvimento que considero ideal quando estamos escrevendo APIs: fácil, direto, focado no que vamos fazer (a implementação dos endpoints).

Confesso que apenas amei o ExpressJS. A documentação não é tão boa quanto a do Grails, mas te fornece o essencial para que você possa fazer praticamente tudo com ele, e de uma forma muito simples.

Nem tudo é claro: o uso de sessões, por exemplo, não é tão óbvio (mais a frente conto o por quê das sessões), idem no que diz respeito à implementação de coisas como o CORS. Entretanto, quando você conhece o conceito de middlewares (o equivalente aos filtros da API Servlet) a coisa deslancha.

Sobre os templates, sim: há a renderização de páginas tal como o JSP do Java EE ou o GSP do Grails. Para tal experimentei o Mustache, Pug e EJS. Comparado ao que temos do lado Java são soluções muito primitivas: o GSP sem sombra de dúvidas está anos luz na frente. Mas é natural isto: a pegada do Node.js sempre foi muito mais no desenvolvimento de aplicações que seguem o padrão SPA, o que joga este tipo de solução para o segundo plano. Acabei optando pelo hbs (Handlebars) na minha prova de conceito.

No frigir dos ovos é um framework extremamente produtivo. No meu caso, que só conhecia (e muito pouco) o módulo http, foi uma bela surpresa.

O poder e a ilusão de poder

Quase tudo que vi no Node achei muito produtivo: e é mesmo, mas apenas se você sabe o que está fazendo. Parece óbvio, né? Mas não é: JavaScript ainda é aquela linguagem que a maior parte das pessoas diz conhecer mas nunca estudou a respeito.

Lembra as histórias de terror que mencionei no início deste post? Pelo que pude ver sempre surgiram das mesmas causas:

  • Falta de conhecimento acerca do modelo de desenvolvimento assíncrono que o Node adota (o não conhecimento do loop de eventos é fatal).
  • Tem de conhecer o paradigma funcional.
  • A falta de conhecimento sobre desenvolvimento backend – Node foi feito para ser executado no servidor. Vi muita gente sem conhecimento algum desta área, mas muito de JavaScript cometendo erros absurdos aqui.
  • Desconhecimento das nuances do JavaScript (coisas como == e ===, por exemplo, o próprio escopo de variáveis, etc)

A ferramenta é realmente muito poderosa, é  muito fácil de usar e você de fato tem as coisas rodando num tempo muito menor. Mas quando ignora estes pontos acima a coisa fica feia, muito feia.

A armadilha surge no fato do Node ter uma única thread (é possível ter um servidor com mais de um processo, basta usar o módulo cluster, mas não é o padrão). Qualquer operação de I/O que agarre, prende todas as requisições que chegam no seu servidor: sendo assim você não “tem de pensar assincronamente”, você é obrigado.

Mais do que pensar assincronamente, você precisa pensar funcionalmente. Se o desenvolvedor não tiver bem fixados os conceitos do paradigma funcional é quase certo que vai dar merda. E quer saber de uma coisa? Acho isto fantástico, por que sair um pouco do OO foi uma experiência quase terapêutica para mim (já mencionei que não curto tanto OO?).

Se não souber estas coisas, você não tem poder: tem a ilusão de poder e a garantia de estar criando mais histórias tristes que se propagarão por aí. Rapadura é doce, mas não é mole não.

E a sua prova de conceito, como ficou?

Bom, vamos aos resultados então. Comecei pela implementação de um único endpoint: o responsável por realizar a busca por posts no /dev/All (este aqui). Foi algo fácil de fazer: este endpoint me retorna a lista de posts, e cada elemento no post tem uma estrutura similar à seguinte:


{
 id:"id do post",
 titulo:"titulo do post",
 resumo:"o resumo do post",
 dataPublicacao:"a data em que o post foi publicado no post",
 dataInclusao:"a data em que o /dev/All encontrou o post e o incluiu no banco de dados",
 cliques:"quantos cliques recebeu o post",
 site:{
  id:"identificador do blog que contém o post",
  nome:"o nome do blog",
  url:"a URL do blog",
  autor:{
  id:"o identificador do autor do blog",
  nome:"O nome do autor do blog"
 }
 }
}

Há portanto três tabelas no banco de dados unidas por join: post, site e autor. Lembre: não estou usando MongoDB aqui, mas sim o MySQL. Será que a coisa escala? Comecei a realizar então alguns testes de desempenho e o resultado foi “apenas” assustador como mostrarei na sequência.

A chamada padrão a este endpoint retorna os últimos 20 posts cadastrados no /dev/All. Em média o tamanho da resposta é 20kb.

Teste de desempenho e escalabilidade

Para realizar o teste usei uma ferramenta chamada “siege“, que me permite realizar testes de carga usando o protocolo HTTP. Caso esteja usando Linux, você pode instalá-la usando o comando apt-get install siege.

Inicialmente peguei a mesma implementação feita em Grails e a instalei em um servidor Tomcat local (exatamente como se encontra em produção). Na sequência, executei os testes usando o Siege levando em consideração o tempo de um minuto e 255 usuários simultâneos. Vamos aos valores aproximados para a versão escrita em Grails:
Transactions:       21046 hits
Availability:      100.00 %
Elapsed time:       59.95 secs
Data transferred:      245.47 MB
Response time:        0.47 secs
Transaction rate:      351.06 trans/sec

Agora, vamos aos resultados na mesma API, implementada em Node.js usando as mesmas configurações:
Transactions:       32596 hits
Availability:      100.00 %
Elapsed time:       59.06 secs
Data transferred:      131.28 MB
Response time:        0.21 secs
Transaction rate:      551.91 trans/sec

Na média a mesma API escrita em Node.js consegue um throughput maior: 40 a 50% a mais de transações por segundo.

E sobre o consumo de memória? Na média enquanto o Tomcat consome 1 Gb durante o teste, a instância do Node consome 170Mb. 80% a menos.

Mas este benchmark não é preciso, então não comemore ainda

Não comemore ainda: pra começar este é um benchmark muito vagabundo. Você deve levar em consideração os seguintes pontos:

  • O código escrito em Grails usa o GORM e contém uma série de funcionalidades carregadas junto com a aplicação que não existem ainda no código escrito em Node.js.
  • O código escrito em Node.js usa apenas SQL nativo para obter os dados, o que dá um ganho de desempenho em relação à adoção de qualquer ORM.

Não cheguei a implementar uma versão usando apenas SQL do mesmo endpoint na aplicação, entretanto creio que o resultado seria muito parecido mesmo assim, pois ainda há uma enorme pilha por trás. Além disto, é fato conhecido que sim, Java sempre consome uma quantidade significativamente maior de memória.

Os testes foram além: depois executei verificações com 500, 1000 usuários simultâneos. A disponibilidade e escalabilidade do Node.js ganhou nestes casos (note: estou testando apenas um endpoint).

Resumindo: obtive resultados melhores do ponto de vista empírico com Node: mas minha metodologia de teste é muito furada e não deve ser levada como palavra final.

E depois de ter implementado a API?

Bom: aí eu empolguei e implementei quase todo o comonente Frontend do /dev/All em Node.js. Há uma versão muito tosca online caso queira conferir: ela tem apenas a página inicial, mas dá pra pelo menos você experimentar. Basta acessar http://devall.com.br:3000 (não sei até quando este link estará disponível, pois é apenas para testes).

Escrevi a página inicial usando o hbs, ou seja, não é uma aplicação SPA: meu objetivo era apenas aprender e testar a tecnologia, sendo assim leve isto em consideração quando a estiver accessando, ok?

Minhas considerações finais

Node.js com certeza faz parte agora do meu cinto de utilidades, e saibam que em pouquíssimo tempo teremos um novo front-end do /dev/All 100% implementado nesta tecnologia pelas seguintes razões:

  • Nosso front-end é muito pequeno, então é viável de ser reescrito (todo o trabalho pesado é feito pelo Feed Hunter).
  • O consumo de memória é realmente muito menor, o que nos permite aproveitar melhor nossos servidores e reduzir o custo de operação.
  • A escalabilidade se mostrou bastante superior.
  • E nesta minha empolgação já estou com 80% disto implementado e, no processo, sem os vícios das versões anteriores do código fonte. :)

É vital no entanto lembrar do que escrevi acima a respeito da ilusão de poder. É assustadoramente fácil escrever código lento e que não escalará em Node.js. Ficou óbvio pra mim a origem das histórias tristes que havia mencionado antes. Se você não souber programação funcional, entender o modelo assíncrono e de eventos do Node.js, é quase certo que vai dar errado.

(é importante lembrar que JavaScript ainda é aquela linguagem que a maioria julga saber mas nunca estudou de verdade, e isto é a origem de inúmeros problemas)

Sobre a produtividade em relação ao Grails

Do ponto de vista de produtividade, comparado ao Grails, sinceramente não posso dizer que seja mais produtivo que este. Os plug-ins do Grails, além do próprio GSP o tornam matador quando há renderização do lado servidor. Isto sem mencionar que a linguagem Groovy também é melhor que JavaScript (ao menos é construída sobre bases bem mais sólidas).

Ainda sobre produtividade comparado ao Grails: aqui nós pensamos de forma síncrona, que é muito mais natural para a esmagadora maioria das pessoas. No Node.js pensamos essencialmente em callbacks e promises e código que escrevemos para ser executado no futuro, e não no agora. E sim: se adequar a esta outra realidade leva tempo e, portanto, boa parte da sua produtividade também.

Entretanto no que diz respeito ao carregamento do código fonte e sua modificação durante a execução, Node.js chuta a bunda do Grails diversas vezes. É muito mais rápido, e isto é fundamental quando vamos executar uma grande bateria de testes.

Sobre modularidade e grandes bases de código

A questão da modularidade também é importante mencionar: já trabalhei em projetos gigantescos com Grails (e Java em geral). No caso do Node.js, ainda não peguei um projeto com uma grande base de código. Entretanto, para escrever micro-serviços, Node.js se mostrou uma ferramenta extremamente interessante e com certeza está no centro do meu radar para estes casos.

Sobre o consumo de recursos e escalabilidade

Não há muito o que dizer: consome uma quantidade muito menor de memória e quando bem projetado escala maravilhosamente bem. É portanto um forte candidato em situações nas quais temos servidores limitados (que é justamente uma área na qual venho pesquisando bastante nos últimos anos).

O consumo da CPU também se mostrou muito inferior. No caso dos testes que realizei, código na JVM chegava a consumir 350% da CPU fácil, enquanto o Node ficava na faixa dos 120%.

Resumindo: se você souber o que está fazendo o resultado é lindo.

Renderização do lado servidor

Não é algo lindo: tal como mencionei as opções que encontrei ainda são muito primitivas quando comparadas ao que temos em Java, Groovy ou PHP. Natural, não é o foco deste público. Para aplicações que requeiram a criação de vários CRUDs algo como Grails ainda é uma solução bem mais interessante.

Mas aqui leve em consideração minha pouca experiência no assunto. Pode ser que existam soluções que eu ainda não conheça.

E finalmente

Este foi um final de semana maravilhoso e estas foram as minhas conclusões iniciais sobre aquilo que estudei (por isto o post longo).

Neste primeiro momento recomendo o Node para pequenos projetos, especialmente se for projetos nos quais você implementará apenas uma API. É uma tecnologia muito bacana e que vale à pena estudar.

Conforme progrido no estudo conto mais pra vocês aqui.

Novo guia da Itexto: SDKMan

Infelizmente o Jenv (http://jenv.io) ficou fora do ar por um bom tempo (pelo menos os últimos cinco dias). Então, junto com Daniel Altarmiro, escrevemos um novo guia, desta vez sobre o SDKMan, tendo como base o nosso antigo guia do Jenv.

Entrei em contato com a equipe responsável pelo desenvolvimento e manutenção do projeto pelo Gist, e por lá pude ver que a situação já dura um bom tempo infelizmente.

Vocês podem baixar o guia clicando aqui.

Iniciando a Semana JavaScript

Mais um avanço no JavaScript Brasil: lançamos esta semana a newsletter, chamada “A Semana JavaScript”. O objetivo é compartilhar eventos, treinamentos, vagas de emprego, material e tudo o mais relativo ao JavaScript para que possamos manter a comunidade bem informada semanalmente.

Naturalmente este é um esforço da comunidade, sendo assim, qualquer um poderá compartilhar links para que sejam publicados na newsletter. Para tal vamos incluir um formulário no site do JavaScript Brasil para que todos possam compartilhar conteúdo (inclusive anonimamente se preferirem): o importante é divulgar coisas importantes para a comunidade.

Caso deseje se inscrever, basta preencher os dados presentes neste formulário.

E acessar o JavaScript Brasil para acompanhar as novidades!

Uma instância do Tomcat atendendo dois domínios? Configurando Virtual Hosts

Imagine a seguinte situação: você tem um único servidor e duas aplicações distintas que serão hospedadas na mesma máquina. Cada uma destas aplicações tem, por sua vez, seu próprio domínio (www.aplicacaoquente.com.br (aplicação A) e www.aplicacaofervente.com.br (aplicação B), por exemplo). Como você resolve esta questão usando apenas o Tomcat?

Se você tiver o servidor Apache instalado, é possível usá-lo como front-end do seu servidor e, configurando o recurso do proxy reverso, simplesmente fazer o direcionamento das requisições vindas dos dois domínios acima para as aplicações A ou B. Mas e quando você tem apenas o Tomcat? Razoavelmente simples: você usa o recurso dos hosts virtuais (virtual hosts daqui pra frente).

(A documentação oficial do Tomcat sobre este tema é muito ruim, e isto me motivou a escrever este post.)

O que é um host virtual?

Um host virtual representa o direcionamento que o Tomcat fará quando receber solicitações para um dado domínio. No exemplo deste post, temos os dois domínios, um para cada uma das aplicações. Vamos ver aqui agora como realizar esta configuração na versão 8.0 e 8.5 do Tomcat.

Como configurar os virtual hosts

Antes de iniciar, uma simples convenção. CATALINA_HOME representa o diretório no qual o Apache Tomcat encontra-se instalado em seu servidor.

O primeiro passo consiste em editar o arquivo CATALINA_HOME/conf/server.xml. Busque pela tag <Engine> cujo atributo name seja igual a “Catalina”, tal como no exemplo a seguir:


<Engine name="Catalina" defaultHost="localhost">

<!--For clustering, please take a look at documentation at:

É no interior desta tag que iremos inserir as configurações de nossos virtual hosts. A configuração é bastante simples. Em nosso exemplo, basta incluir duas tags <Host>, tal como no exemplo a seguir:


<Host name="aplicacaofervente.com.br" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Alias>www.aplicacaofervente.com.br</Alias>

<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"   prefix="aplicacaofervente_access_log" suffix=".txt"
pattern="%h %l %u %t %r %s %b" />
</Host>

<Host name="aplicacaoquente.com.br" appBase="webapps2" unpackWARs="true" autoDeploy="true">
<Alias>www.aplicacaoquente.com.br</Alias>

<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs" prefix="aplicacaoquente_access_log" suffix=".txt"
pattern="%h %l %u %t %r %s %b" />
</Host>

Muita atenção a alguns atributos muito importantes presentes na tag <Host>:

  • appBase – define o diretório “webapps” daquele host. No caso de virtual hosts, é uma boa ideia você ter um diretório “webapps” por host. Em nosso exemplo, criei dois diretórios: CATALINA_HOME/webapps e CATALINA_HOME/webapps2
  • name – identifica o domínio para o qual serão direcionadas as requisições. Observe que defini o domínio sem o “www” na frente. Isto por que no interior da tag <Host> podemos incluir aliases, observe a tag <Alias>: ela inclui o prefixo no nome do domńio, o “www”.

A tag <Valve> neste caso é usada para configurar uma válvula do Tomcat (um recurso poderosíssimo e muito pouco conhecido pelos desenvolvedores) responsável por registrar as requisições que chegam ao servidor em um arquivo de log. Observe que criei um arquivo de log por domínio.

Muita atenção em relação à pasta appBase

É possível ter dois virtual hosts apontando para a mesma pasta appBase. Entretanto, em diversas situações você observará que as aplicações, durante sua inicialização, entram em “loop de boot”, ou seja, são iniciadas e reiniciadas infinitas vezes.

Não sei ao certo o que causa este problema, mas sei como solucioná-lo: uma pasta appBase por host. Simples assim.

Alguns links úteis

É interessante que você conheça os atributos que podem ser inseridos nas tags <Engine> e <Host>. Sendo assim, recomendo que leia sobre a primeira tag aqui e sobre a segunda aqui.

Definindo o host padrão

Com isto nosso servidor está praticamente pronto. O mesmo endereço IP irá atender aos dois domínios. Mas e se alguém tentar acessar o servidor diretamente pelo seu IP? Neste caso definimos qual o host padrão na tag <Engine>, tal como exposto no exemplo a seguir:


<Engine name="Catalina" defaultHost="localhost">

Neste exemplo criei uma tag <Host> cujo nome é “localhost” (é inclusive o host padrão da configuração limpa do Tomcat).

Sobre o contexto das aplicações em um domínio

Se você já usou o Tomcat sabe que é possível ter mais de uma aplicação instalada na pasta CATALINA_HOME/webapps. Voltando ao nosso exemplo, imagine que para o domínio aplicacaofervente.com.br tivéssemos os seguintes arquivos WAR na sua pasta webapps:

  • ROOT.war
  • administrativo.war

A aplicação padrão do domínio seria o ROOT.war. Já se você quiser aplicar o administrativo, teria de usar o endereço http://www.aplicacaofervente.com.br/administrativo

Melhorias no consumo de memória

Talvez as aplicações que você hospeda em seus virtual hosts tenham bibliotecas em comum. Apenas se você tiver certeza absoluta de que compartilham exatamente as mesmas versões destas bibliotecas, considere a configuração de uma pasta compartilhada de libs. Recomendo a leitura desta página na documentação oficial do Tomcat sobre class loading.

Concluindo

Reinicie o seu Tomcat e faça testes enviando requisições para os dois domínios que apontam para o mesmo IP e veja o resultado. Na sequência, acesse o servidor diretamente por seu IP e verá ser retornada a aplicação padrão definida na tag <Engine>.

O Tomcat é cheio de recursos muito interessantes que os desenvolvedores desconhecem. Em um futuro próximo talvez escreva mais dicas sobre estes assuntos (dica: leia sobre as válvulas).

Experimentando o Apache Tapestry

Recentemente começamos um projeto de evolução de uma aplicação web escrita em Apache Tapestry. Já fazia um tempo que não ouvia falar a seu respeito (uns seis anos pelo menos), então esta foi a oportunidade perfeita (e necessária) para que finalmente eu estudasse um pouco a seu respeito, e o que estou aprendendo tenho achado muito interessante.

Nota importante: este é um post escrito por um iniciante no assunto (eu). Sendo assim toda correção é bem vinda!

O que é o Apache Tapestry e minhas primeiras impressões

Uma definição bem vagabunda: “é framework para desenvolvimento de aplicações web, baseado em componentes, voltado para a plataforma Java”. Isto não incita ninguém a correr atrás da ferramenta, especialmente se levarmos em consideração que não vemos muitos artigos escritos a seu respeito nos últimos anos. Sendo assim neste post vou escrever uma aplicação de exemplo que ilustra seu funcionamento.

Meu primeiro contato com Tapestry foi através de artigos na Internet e revistas em 2004/2005: anos mais tarde, lá por 2011, trabalhei em uma empresa que tinha alguns projetos baseados neste framework, mas eu mesmo jamais tive qualquer contato com estes projetos. Como nunca fui fã de frameworks que fossem baseados em componentes, torci o nariz para o projeto, até este mês (agosto/2017), quando precisei estudá-lo.

O que gostei no Tapestry

  • Altíssima produtividade – o projeto implementa um mecanismo de carregamento automático de classes que nos possibilita modificar o código fonte (Java) e ver as mudanças imediatamente na aplicação em execução, sem qualquer necessidade de reiniciar a aplicação.
  • O modelo de desenvolvimento – uma aplicação escrita em Tapestry essencialmente é um conjunto de páginas (este não é um framework voltado para a escrita de APIs REST). Uma página é basicamente uma classe Java e um arquivo de Template que reflete o estado dos objetos instanciados desta classe.
  • POJO puro (ou quase) – você não precisa implementar interfaces neste framework. Classes Java simples (e portanto fáceis de serem testadas) fazem todo o trabalho, e ainda há um container de IoC embutido que cuida de todo o ciclo de vida destes objetos.
  • É muito rápido – o desempenho é inacreditável. O carregamento dinâmico de classes que mencionei acima, por exemplo, é praticamente instantâneo.
  • Gestão interessante de sessões – o modo como Tapestry lida com sessões do usuário é muito bacana. Você não tem um objeto “session” ou algo similar: ao invés disto, os atributos das páginas que mencionei acima são tratados como tal de uma forma completamente transparente para o desenvolvedor.
  • É extensível – pelo pouco que pude ver na documentação oficial do projeto, é possível integrá-lo com o Spring, o que abre um universo de possibilidades. Este link mostra algumas extensões publicadas além da documentação instruindo como aplicar o Spring e Hibernate no projeto.
  • Baseado em Maven (e Gradle) – isto é ótimo, pois me possibilita abrir o código fonte em praticamente todas as IDEs Java do mercado hoje.
  • A documentação oficial – é boa, apesar dos problemas que vou citar a seguir (talvez este ponto seja uma contradição). Ela quebra o galho, mas não é ideal.
  • Bom suporte no Eclipse – há bons plugins para o projeto (estou usando o Tapestry Tools). Neste link há algumas opções, mas você as encontra fácil no Eclipse Marketplace.
  • Convenções sobre configuração – você quase não precisa editar arquivos de configuração no Tapestry, basta seguir as convenções que o framework oferece, mas sem te prender a uma forma pré-fixada: é possível mudar, até onde pude ver, qualquer comportamento do framework.
  • Desenvolvimento do projeto continua ativo – apesar de não ouvir falar do Tapestry já faz um bom tempo, fiquei muito feliz ao ver na página de release notes que sai pelo menos um (no máximo 2) release importante por ano do framework.
    (talvez seja um release por ano pelo fato do projeto já estar maduro, o que é um ponto bastante positivo)

O que não gostei

  • Alguns problemas na documentação – No momento da escrita deste post a documentação do framework (especialmente a sessão “Tutorial“) estava quebrada, e os trechos que envolvem o código fonte não estava muito legível, o que dificulta a vida de quem está dando os primeiros passos.
    O “Getting Started”, por exemplo, está desatualizado (durante a escrita deste post), e você não conseguirá criar o projeto se seguir o passo a passo.
  • Ausência de uma comunidade brasileira – no site oficial estão listadas algumas mailing lists, mas apenas em inglês. Infelizmente não encontrei muitos artigos escritos em português, o que, por mais que muitos não creiam, dificulta a adoção da ferramenta em território nacional.
    (sobre a falta de artigos em português, comecei a fazer minha parte. Conforme vou aprendendo mais sobre o Tapestry posto por aqui).
  • Comunidade mundial pouco ativa – ao menos esta foi minha impressão. No Facebook, por exemplo, só encontrei um grupo contendo seis membros, e na mailing list de usuários há poucos posts novos por mês também.
  • Poucas issues no Jira do projeto e ausência de um roadmap para as próximas versões – este é um ponto de atenção: não vi muitas issues abertas no Jira da comunidade que digam respeito a novas funcionalidades, especialmente no que diz respeito à próxima versão, que será a 5.5.0.
    Também não encontrei um roadmap para as próximas versões: o máximo que encontrei foi um antigo documento (marcado inclusive como desatualizado) para a versão 5.0 do projeto.
    Estes pontos levantam dúvidas a respeito da evolução do projeto e se ele estará acompanhando as próximas tendências do mercado, o que conta como ponto negativo na sua adoção para novas aplicações.
  • Não é bom para se escrever uma API REST – o objetivo do Tapestry claramente é a criação de aplicações no sentido “páginas interativas”, não para a escrita de APIs. Na documentação oficial, por exemplo, você não vê qualquer menção ao desenvolvimento deste tipo de projeto (ao menos esta foi a impressão, enquanto iniciante, que tive do projeto).

Notas sobre a popularidade do framework e se você deve adotá-lo ou não com base nisto.

Tapestry, assim como Grails, Wicket, Play e outros não é o que podemos chamar de uma opção “mainstream”, tal como o Spring (aliás, de todos os frameworks fora do Java EE, talvez o único mainstream seja o Spring) ou Java EE (puro,o que inclui o JSF). Sendo assim, quaisquer comparações em relação a estas opções é descabida de fundamentação.

No caso do Java EE, ainda mais, especialmente se levar em consideração o fato de que todos os demais frameworks dependem deste para funcionar (o que consequentemente o tornará sempre o mais popular).

O que levo em consideração na adoção destes frameworks (que são verdadeiras armas secretas) é a atividade da comunidade que possuem e a quantidade de releases anuais. Normalmente quando não ocorrem releases por mais de um ano levanto o sinal amarelo.

A propósito, já escrevi sobre este tópico neste link alguns anos atrás, mantenho minha opinião e já está na hora de escrever outro post a respeito.

(e ei, se você curte uma tecnologia e não escreve sobre ela por não ser tão popular, só tenho uma coisa a te dizer: SHAME ON YOU!)

“Olá Mundo” com Tapestry

O código fonte pode ser baixado neste repositório do GitHub.

O que você vai precisar

  • Maven 3 – http://maven.apache.org 
  • Java (JDK) 1.8 (para a versão 5.3.8 pra frente do framework. As anteriores tem de usar o Java 7 ainda)
  • Qualquer IDE que ofereça suporte a Maven (o projeto vai abrir em qualquer uma, no meu caso estou usando o Eclipse junto com o plug-in Tapestry Tools.

É uma aplicação bem simples, só pra mostrar os conceitos de páginas, componentes e o modus operandi do framework, sendo assim não esperem muita coisa daqui.

Criando o projeto com Maven

No link “Getting Started” do projeto você lerá que deve executar o comando a seguir:

mvn archetype:generate -DarchetypeCatalog=http://tapestry.apache.org

Não funcionará com o Maven 3: você topará com a seguinte mensagem de erro:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-archetype-plugin:3.0.1:generate (default-cli) on project standalone-pom: archetypeCatalog 'http://tapestry.apache.org' is not supported anymore. Please read the plugin documentation for details. -> [Help 1]

Isto é fácil de resolver: basta usar uma versão anterior do plug-in archetype do Maven, tal como a 2.4 no exemplo a seguir:

mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeCatalog=https://repository.apache.org/content/repositories/staging -Dfilter=org.apache.tapestry:quickstart

Após metade da Internet ser baixada para seu computador, serão aprsentadas 10 opções de arquétipos a serem escolhidos. Para o nosso exemplo adotei a opção “20” que equivale à última versão disponível do Tapestry (5.4.3).

Na sequência você precisará responder algumas perguntas:

  • O nome do grupo (groupId) – no exemplo: br.com.itexto
  • O nome do artefato (artifactId) – no exemplo: inicio-tapestry
  • Sua versão
  • O pacote no qual o código fonte se encontra (isto é importante e veremos mais a respeito mais à frente) – no exemplo: br.com.itexto

Respondidas as perguntas, será criado um diretório cujo nome equivale ao artifactId do projeto. Para testar o projeto, mude seu diretório corrente para o criado pelo projeto e execute o comando abaixo:

mvn jetty:run

Será iniciado o servidor Jetty, embarcado no projeto. Sim, seu ambiente de desenvolvimento está pronto. Acessando a URL http://localhost:8080, exposta na saída do prompt, você se deparará com a seguinte página:

clique sobre o link: ele aponta para uma URL similar ao artifactId que você forneceu ao criar o projeto. Agora você verá a página de boas vindas do Tapestry.

Dica: dá pra criar o projeto de uma forma muito mais simples usando o Eclipse, tal como descrito neste link.

A estrutura do projeto

A estrutura de diretórios criada pelo arquétipo do Maven

Importe o projeto Maven para a IDE de sua preferência, pois iremos alterar levemente este código fonte para expor o funcionamento do Tapestry. O arquétipo do Maven cria alguns sub-pacotes que são importantes: neste post falarei apenas de dois:

  • br.com.itexto.pages – contém as páginas do projeto
  • br.com.itexto.components – contém os componentes

E na pasta “src/main/resources” também são criados alguns diretórios interessantes:

  • src/main/resources/br/com/itexto/pages – contém os templates das páginas (falaremos mais sobre isto mais a frente)
  • src/main/resources/br/com/itexto/pages/components

Os mesmos nomes dos pacotes se repetem nas pastas criadas em “src/main”. Como disse no início deste post, uma aplicação Tapestry é composta essencialmente por páginas. Uma página, por sua vez, tem duas faces:

  • O código fonte Java que representa o “controlador” do projeto.
  • Um arquivo de template, que deve ter exatamente o mesmo nome da classe que a representa, na pasta “src/main/resources”. Estes templates tem a terminação “.tml”

Com base no exemplo acima, alterei o código fonte do arquivo “Index.java” para que fique tal como o exposto a seguir:


package br.com.itexto.pages;package br.com.itexto.pages;

public class Index{

public String getRandomico() {
return java.util.UUID.randomUUID().toString();
}
}

Note que é uma classe Java simples, sem a necessidade de realizar qualquer implementação de interfaces ou uso de anotações. Alterei também levemente o arquivo de template relacionado (src/main/resources/br/com/itexto/pages/Index.tml) para que fique tal como no exemplo a seguir:


<html t:type="layout" title="inicio-tapestry Index"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"
>
<p>Randomico como ${randomico}</p>
</html>

(Quando acessamos a URL do projeto, é a página Index que será renderizada para o visitante. Se você quiser experimentar, crie páginas com o nome “Teste” (contendo o código Java e o de template) e acesse as URLs diretamente para brincar.)

O template é a outra face da página: é o conteúdo HTML que será renderizado. No caso do Tapestry, é XHTML que usamos na realidade (HTML formatado como XML). Em seu interior, incluímos uma expressão: ${randomico}, que irá renderizar o valor retornado pela função getRandomico(), definido na classe Index.java. Tapestry usa a especificação JavaBeans do Java para definir as expressões que serão usadas na renderização das páginas.

Coloque a aplicação para executar em sua IDE usando o comando “mvn jetty:run” para ver o resultado, que será similar ao exposto na imagem a seguir:

Recarregamento dinâmico de classes

Agora, com a aplicação ainda em execução, faça o seguinte experimento: modifique a função getRandomico() da classe Index para que retorne diferentes valores, sem parar a aplicação. Carregue novamente a página: a mudança é instantânea.

Vá além: crie outros “getters” para novas propriedades e vá incrementando o arquivo de template. Note como mesmo novos métodos são detectados instantaneamente na aplicação sem precisar realizar qualquer tipo de reinicialização. Isto dá muita produtividade no desenvolvimento. A propósito, observou a velocidade com que a aplicação é iniciada?

E os componentes?

Bom, vendo a última imagem você deve ter notado que é renderizada uma barra de navegação, mas não a digitamos no código fonte do template. Notou? Então vou repetir o código aqui:


<html t:type="layout" title="inicio-tapestry Index"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"
>

<p>Randomico como ${randomico}</p>
</html>

O template define que ele é de um dado tipo: “layout”. Você notou que existe uma pasta chamada “components” em “src/main/resources” e também um pacote chamado “components”? Se você abrir o código fonte do arquivo “src/main/resources/br/com/itexto/components/Layout.tml” verá que a barra de navegação, assim como o rodapé e toda a identidade visual está aí. E também vai achar um trecho similar ao exposto a seguir:

<div class="container">
<t:body />
<hr />
<footer>
<p>&copy; Your Company 2015</p>
</footer>
</div> <!-- /container -->

Sabe esta tag <t:body/>? Ela inclui o conteúdo da página Index no interior do Template. É um mecanismo muito similar ao Sitemesh do Grails! Na realidade é um pouco mais que isto, mas para este post inicial, creio que esta definição será o suficiente.

Código fonte do exemplo

O código fonte deste projeto pode ser encontrado neste link. Talvez eu o evolua no futuro conforme novos posts surjam no blog.

O que o Tapestry não tem e não é

É importante salientar que este não é um framework full stack tal como o Grails ou Spring Boot (dependendo da configuração). Quando criamos um novo projeto ele não vem com a camada de persistência pré-configurada. É necessário realizar esta configuração manualmente, tal como exposto na própria documentação.

O projeto já vêm com algumas configurações prontas, tal como o mecanismo de logging e algum CSS, baseado no bootstrap, mas não muito mais do que isto.

Também é importante observar o seu foco: são aplicações web que contenham páginas interativas, não APIs REST. É possível configurar no mesmo projeto um segundo framework para prover estas funcionalidades, tal como o JAX-RS ou mesmo o Spring, mas não recomendo isto.

Sei que não mencionei neste post nada relativo à gestão de sessões, mas este é outro aspecto interessantíssimo do Tapestry: não é um destes frameworks no qual você tem a sessão explícita. Lidamos com ela de uma forma indireta através de anotações que incluímos em nossas classes (@Persistent). Sendo assim, para projetos que precisem lidar diretamente com sessões (cada vez menos o caso), Tapestry também não se aplica.

Conclusões finais sobre este contato inicial

Neste post mostrei apenas o básico do básico do básico do Tapestry (quase nada pra ser sincero). Muita coisa ficou de fora, tal como a forma como é realizada a navegação entre as páginas, gestão de sessão, formulários, inversão de controle, etc.

Meu objetivo foi apenas gerar algum interesse pelo framework e, talvez, criar o post inicial que me permita ir detalhando aos poucos como o Tapestry funciona conforme vou avançando nossos estudos a seu respeito.

Estou achando uma solução muito interessante: mais interessante que o JSF na minha opinião, pois é mais fácil de aprender e oferece uma produtividade muito superior. Isto sem mencionar no desempenho, que foi um dos fatores que mais me surpreendeu.

(tal como mencionado no início deste POST, para implementar APIs REST não é uma solução interessante. Aliás, nem sei se faria sentido, dado ser focado em componentes, e não em ações, tal como ocorre no Grails e Spring MVC)

Dado o estado atual da comunidade (ao menos minha percepção inicial), não sei se o recomendaria para iniciar novos projetos, mas em relação a este aspecto apenas o tempo me dará uma resposta satisfatória. Entretanto, se o tempo mostrar que ainda há gás nesta comunidade, sem sombra de dúvidas que o incluirei em nosso radar.

Visto que tão pouco se escreve a respeito do Tapestry ultimamente, creio que no mínimo estes meus posts servirão para atrair interessados a seu respeito. Mesmo que você jamais o use, as ideias que ele trás já valem à pena conhecer.

Mais fontes sobre Tapestry

Talvez você tenha se interessado pelo framework. A documentação oficial apesar de boa não é suficiente. Sendo assim, seguem alguns links que estão me ajudando no aprendizado:

Tapestry Jumpstart – quase um cookbook de Tapestry

Wiki do projeto – tem algumas informações que não constam na documentação oficial

Este tutorial da Tutorials Point – no formato PDF, é um complemento bem interessante para o tutorial oficial

Tutorial oficial – apesar de estar quebrado o HTML, me ajudou bastante