Tag: play! framework

Uma avaliação do play! framework

Há alguns meses, alguém levantou a possibilidade de melhorar o gerenciamento das visitas a clientes na empresa em que trabalho, utilizando um sistema específico.

Consultaram-me sobre a possibilidade de utilizar um sistema opensource, entretanto não encontrei nada nessa linha. Como estava estudando algumas novidades e queria desenvolver algum sistema com elas, no caso o play! framework, vi que a oportunidade era perfeita, pois tratava-se de um sistema de uso interno e de baixíssimo risco, então respondi que em pouco tempo poderia criar um protótipo.

Utilizei o Twitter Bootstrap (que também estava aprendendo) na camada de apresentação e o Hibernate para persistência (depois de desistir do Ebeans, padrão do framework).

O desenvolvimento do protótipo totalmente funcional ocorreu em uma semana. Trata-se de um sistema pequeno, porém como estava usando o play! e o bootstrap pela primeira vez, acredito que foi bem rápido.

Depois das rotineiras mudanças de requisitos e solicitações de melhorias após a apresentação para os usuários, mais dois ou três dias e estava tudo pronto.

O que tem de mais esse tal de play!?

  • Instantâneo: Todas as alterações em telas ou no código java são recompiladas e executadas sem reiniciar o servidor, só é necessário um “refresh”;
  • Depuração: é possível conectar o debugger do Eclipse com a aplicação executando em modo de desenvolvimento, mas nem é tão necessário, pois quando há algum erro no Java ou na tela, a mensagem do framework é tão amigável que mostra, além da pilha, o próprio código fonte e qual o comando ou erro de sintaxe que causou a falha;
  • Sessão: não existe uma session, os únicos valores que são armazenados entre as requisições ficam em cookies criptografados. Isso faz com que a aplicação seja altamente escalável com múltiplas instâncias, embora seja confuso para quem está acostumado com as tecnologias JEE padrão;
  • Threads: não há criação de threads por requisição como nos Servlets, os métodos dos controladores são static e processamentos “demorados” devem ser assíncronos;
  • Totalmente REST: todas as entradas de url são configuradas no arquivo “routes”, como no rails;
GET     /sobre                      controllers.App.sobre()
  • CoC (Convensão sobre Configuração / Convention over Configuration): a ideia é não reinventar a roda, basta executar um comando “play new [nome sistema]” e sair programando, obviamente se você seguir o padrão adotado pelo framework. Já se quiser fazer diferente aí vai ter tanto ou mais trabalho do que num framework tradicional;
  • Builders para as views: as telas, por exemplo, possuem parâmetros e você “invoca” uma tela a partir do código chamando um método de uma classe gerada dinamicamente que recebe os devidos parâmetros, então se você mudar uma tela para receber algo novo, não tem como esquecer de atualizar o código, já que isso vai gerar erros de compilação;
  • Servidor de aplicação: esqueça Tomcat, Glassfish, Weblogic ou Websphere, ele já vem com um servidor leve embutido, o comando “run” inicia a aplicação em modo desenvolvimento e o comando “start” em modo de produção;
  • Ciclo de vida: o framework gerencia o ciclo de vida através de uma ferramenta de build chamada SBT (uma espécie de ANT), gerencia as dependências através do IVY e também é compatível com Maven;
  • Integração com Eclipse: o comando “eclipsify” gera os arquivos necessários para importar o projeto no Eclipse, quando incluir uma nova dependências, basta executar o comando novamente para atualizar o classpath e demais configurações;
  • Banco de dados: integração nativa com JPA, inclusive gerenciando as evoluções da estrutura do banco e gerando a DDL necessária a partir das classes
  • Telas: templates feitos numa mistura de HTML com a liguagem Scala. É simples e rápido para implementar, porém lento demais para compilar.
<select id="cliente" name="cliente">
  @for(opcao <- options(Cliente.options)) {
    <option value="@opcao._1"
      @if(opcao._1 == filtro.cliente) { selected }>@opcao._2</option>
  }
</select>

Pontos negativos

No Eclipse, várias vezes as classes geradas pelos builders não são reconhecidas mesmo dando refresh no projeto e erros em views, por exemplo, são listados mesmo que já estejam corrigidos. Às vezes, só fechando e reabrindo o projeto (usando “Close Project”). Isso não afeta a execução mas fica “feio” ver o projeto com erros que não existem.

A versão 2.x mudou muito em relação à primeira e a maioria dos tópicos encontrados fala da versão 1. Além diso, ela não é uma simples evolução da primeira, mas uma reformulação total, tornando a migração muito difícil. Por exemplo, alguns recursos, tal como a geração de WAR para deploy num container, foram removidos e atualmente só é possível executar a aplicação no servidor embutido.

Apesar do framework suportar Java ou Scala como linguagem de programação, os templates da versão 2 são feitos em Scala por padrão, portanto é necessário aprender um pouco da sintaxe e a curva de aprendizagem aumenta. A linguagem Scala executa na JVM e é eficiente, mas a compilação pode ser bem mais demorada que uma classe comum.

O gerenciamento automático da evolução da DDL pode ser confuso à primeira vista e causar alguns efeitos colaterais se mal utilizada.

Update: Como comentei num post mais recente, uma biblioteca foi removida do repositório, tornando impossível compilar uma versão do sistema com o play 2.0 e obrigando os desenvolvedores a migrar para a versão 2.1. 

Resumo

No geral as implementações dos diversos aspectos do play! framework usam outras ferramentas já bem conhecidos, tal como o Hibernate, a linguagem Scala, o gerenciador de dependências IVY, etc. O uso dessas e outras tecnologias já consagradas é uma ótima base para se desenvolver, aumentando a confiança nos resultados.

Desde que seguidos os padrões do framework e depois que se pega o jeito dele, fica muito fácil sair implementando novos requisitos. O maior problema no começo é se despender do conceito de JSP/Sevlet que está tão arraigado no mundo Java.

Enfim, o play! framework é muito bom para projetos pequenos e rápidos. Para algo maior, com uma vida útil mais longa, maior risco ou com uma equipe que não conheça a tecnologia, não recomendaria no momento, por dois motivos: a documentação não é muito completa e os desenvolvedores não tem um compromisso em manter compatibilidade, corrigir bugs críticos ou implementar funcionalidades que eles não considerem importantes.

Por outro lado, com o estudo de frameworks como este, é possível extrair algumas boas práticas e conceitos importantes, aplicando de outras maneiras mesmo em tecnologias mais tradicionais.

Estudo de caso: experiência com arquitetura RESTful

Trabalhei recentemente num projeto de desenvolvimento de um sistema interno para a empresa, no qual os analistas de suporte, desenvolvimento ou negócios deverão registrar com antecedência as visitas aos clientes e incluir anexos como documentos e e-mails relacionados às visitas.

Tratando-se de um projeto de baixo risco, encontrei uma ótima oportunidade de realizar experimentos arquiteturais e conferir na prática algumas tecnologias que nunca foram usadas por aqui, principalmente relacionadas à arquitetura REST.

O sistema de Agenda de Visitas

O sistema contém uma tela inicial que lista as entradas da agenda, incluindo filtro e ordenação. Os filtros são persistidos em cookies que expiram após alguns dias, então o usuário vê a tela inicial do sistema da mesma forma que havia deixado na última visita.

Para incluir ou alterar uma entrada o usuário deve autenticar-se. Ele somente poderá editar entradas em seu próprio nome a não ser que seja um líder de equipe. Aqui estão requisitos de autenticação e autorização.

Ao incluir ou alterar uma entrada, é possível incluir ou remover anexos, além de alterar sua descrição a qualquer momento.

Um relatório (PDF ou XLS) com as entradas de um período pode ser emitido por qualquer usuário.

O sistema possui log para todas as ações e é possível emitir um relatório de “quem fez o que e aonde” num período de sistema.

sistema-bootstrap

Iniciando com o play! framework

Na primeira iteração do projeto, após a reunião inicial de levantamento de requisitos, adotei o play! framework para criar o primeiro protótipo, o qual fornece uma pilha de tecnologias pré-definidas e promete um desenvolvimento ágil e agradável.

Em geral, gostei bastante do play!. Embora eu acredite ele carece de amadurecimento em muitos aspectos, a codificação e os resultados obtidos são bastante satisfatórios. Depois que se "pega o jeito", novas funcionalidades são agregadas muito rapidamente ao sistema. O framework segue a linha do Rails e utiliza adota fortemente o conceito de CoC (Convention over Configuration – Convenção antes de Configuração), então as aplicações funcionam sem necessidade de criar XML’s ou qualquer configuração complicada. O ponto fraco desse conceito é que se você quiser “fugir” do padrão poderá ter grandes dores de cabeça. A documentação é simples e bem-feita, mas não completa. Precisei andar bastante nos fóruns.

Para as classes de domínio e a persistência das mesmas adotei o padrão DAO com JPA (Hibernate). No mais, os controladores foram feitos no estilo play! e as views em templates Scala (na verdade, um misto de HTML com expressões em linguagem Scala), que são o padrão da versão 2.0 do framework.

A camada de apresentação, renderizada no navegador do usuário, foi implementada em HTML 5 com toques de jQuery. Usei a “estilosa” biblioteca Bootstrap, que fornece componentes visuais muito bons e altamente portáveis entre os diversos navegadores. Nunca me arrependi de tê-la usado.

O desenvolvimento e depuração com o play! merecem destaque, pois o mesmo exibe mensagens muito amigáveis sempre que há problemas em código Java ou nos templates Scala. O resultado nem sempre é perfeito, houve um momento ou outro em que foi difícil achar a causa, mas em geral foi muito mais fácil depurar do que outros frameworks conhecidos.

Os problemas

Após o primeiro ciclo de implementação dos requisitos iniciais, o sistema precisava ser entregue para homologação. Aqui as restrições do play! começaram a surgir. Como toda empresa que trabalha com JEE, usamos como padrão servidores de aplicação como Tomcat, Weblogic e Websphere. Porém, o play! 2.0 não suportava deploy para o formato WAR, embora fosse possível na versão 1.x. As aplicações do play! são distribuídas em pacotes standalone que rodam uma versão do Jetty e escutam por padrão a porta 9000. Para contornar essa limitação criei um manual de instalação exclusivo para essa tecnologia, mas ainda assim houve dificuldade por parte dos analistas de suporte que estão acostumados com servidores de aplicação tradicionais. Além disso, depois de desfeita a confusão, tive que ajudar a configurar a inicialização automática da aplicação num servidor Windows utilizando parâmetros específicos do play! de acesso a banco de dados, porta, etc. Tudo isso poderia ser evitado com a opção de deploy em formato WAR, mas os desenvolvedores optaram por mudar o framework de tal forma que a versão 2 não é compatível com a anterior de forma alguma, nem há um processo natural de migração (upgrade ou downgrade), então descartei essa possibilidade logo de início.

Outra dificuldade foi a falta de uma configuração do path base da aplicação, ou seja, ela só funciona na “raiz” do servidor. O play! gera sempre URLs absolutas. O problema é que a aplicação seria disponibilizada para os diferentes setores da empresa através de um proxy reverso, logo todos os links da aplicação precisariam ser reescritos como http://servidor/aplicacao/. Resolvi a questão usando uma branch do core do framework que permitia alterar a raiz da aplicação através de uma nova configuração e assim os links seriam gerados corretamente. Feito isso, tudo funcionou perfeitamente… pelo menos por um tempo.

A última gota foi quando recebi o retorno da homologação solicitando alterações no sistema (alguns requisitos novos e outros modificados). Parti para a segunda iteração, fiz algumas modificações, mas o play! simplesmente não conseguia mais compilar as classes e views. Depois de algumas horas tentando entender o motivo, descobri que algumas bibliotecas da versão 2.0 foram removidas do repositório Ivy pela própria equipe responsável pelo framework com a justificativa de estarem com problemas. A solução foi migrar para a versão 2.1, porém os problemas com o path base da URL voltaram, pois esta funcionalidade não havia sido incluída na HEAD e não havia uma branch da versão 2.1 com a nova configuração que eu necessitava. O único caminho seria aplicar manualmente as modificações necessárias na versão 2.1 e recompilar o framework… ou mudar de tecnologia.

Brincando com JAX-RS

Em paralelo a essas experiências, também vinha estudando o JAX-RS (Java API for RESTful Web Services) e realizado alguns testes com o framework Jersey. Além de ser uma API bastante fácil de usar, achei promissora em vários sentidos. De certa forma a codificação de controladores em JAX-RS é semelhante ao play! (que também usa REST), o código fica conciso e a assinatura dos métodos é ainda mais flexível.

Decidi apostar numa mudança e, embora ainda enfrentasse o início da curva de aprendizado da API, a migração para JAX-RS com Jersey foi concluída em menos de uma semana. É claro que nem tudo foi um mar de rosas, houveram várias dificuldades. A documentação oficial do Jersey deixa muitos pontos em aberto e a internet está cheia de exemplos defasados pelos fóruns.

Um ponto complicado foram os uploads, pois como já comentei, havia uma funcionalidade de envio de múltiplos arquivos usando o plugin jquery-file-upload e foi difícil encontrar o parâmetro necessário (que é FormDataMultiPart) que funcionasse na versão mais recente do Jersey.

Além disso, tive que estender a classe de tratamento de cookies, mas o culpado (e quase sempre é ele) é o Internet Explorer por não respeitar o cabeçalho MAX-AGE. Outro problema que envolveu o IE e uploads foi relacionado aos nomes dos arquivos enviados para o servidor. O IE envia o caminho absoluto dos arquivos de upload sem “escapar” as barras de diretórios. O resultado obtido pela API do Jersey é algo como c:\diretorio\arquivo.txt. Tanto no caso do cookie quanto do upload, os desenvolvedores do Jersey se negaram a contornar essas situações (leia-se: fazer gambiarras específicas para o IE), logo cabe ao desenvolvedor da aplicação contornar manualmente esses problemas. Pelo menos existem pontos de extensão suficientes para isso, embora carentes de documentação.

No geral fiquei muito satisfeito com o Jersey, principalmente com a possibilidade de estender as classes que permitem transformar o corpo da requisição em objetos Java e vice-versa (usando as interfaces MessageBodyWriter e MessageBodyReader), assim o mapeamento do request para objeto e de objeto para o response fica completamente transparente.

Abandonando JPSs

Outra escolha marcante da segunda iteração foi o uso da biblioteca FreeMarker para escrever a saída em HTML. Encontrei nela uma linguagem de marcação de templates simples, poderosa e extremamente eficiente.

Havia feito algumas pesquisas sobre benchmarks de template engines porque, após conviver bastante tempo com JSP e JSF eu já estava cansado da limitação, da lentidão e das dificuldades encontradas nessas APIs para criar lógicas simples de apresentação. O FreeMarker se mostrou uma das melhores opções, não possuindo dependências, sendo extensível e compacto.

O fato é que o JSF, principalmente nas versões mais novas, até pode já trazer componentes prontos e fáceis de usar, mas na maioria das vezes isso inclui alto grau de acoplamento com a implementação escolhida e com o visual da aplicação. O desenvolvedor que gosta de liberdade e flexibilidade, sem abrir mão da performance, irá preferir uma biblioteca como o FreeMarker ou Grails, que não agregam uma pilha enorme às tecnologias do projeto.

Substituir as marcações Scala pela sintaxe do FreeMarker não foi muito dispendioso depois de ter feito a configuração básica dos templates. A performance é excelente e a maior parte da sintaxe é simples e agradável.

Conclusões

Primeiramente, achei o play! framework promissor, mas em ambientes já dominados por tecnologias JEE ou com determinadas restrições, dificuldades oriundas da imaturidade do projeto podem surgir e acabar com a “mágica” de soluções inovadoras como esta.

A arquitetura REST é excelente para se trabalhar, pois facilita ao desenvolvedor pensar no sistema em termos de serviços. Este é uma das razões que tornaram a migração do play! para o Jersey fácil, pois não foi preciso alterar nenhum URL.

A API JAX-RS, na minha opinião, é uma escolha certeira para a web. Porém, não estou me referindo apenas a enviar e receber dados em JSON ou XML, mas também HTML sobre HTTP.

Não acoplar as diferentes camadas da arquitetura permitiu a troca de tecnologia de forma razoavelmente rápida e com introdução de poucos bugs. Mais um ponto positivo para bibliotecas como o Bootstrap e mais um motivo pelo qual eu não recomendo tecnologias que “amarrem” diferentes as camadas. O planejamento da arquitetura deve levar em consideração o acoplamento entre os componentes utilizados. Se tivesse adotado inicialmente o JSF ou Vaadin, por exemplo, e tentasse trocar de tecnologia, teria que reescrever praticamente a aplicação inteira.

Há frameworks que prometem uma solução completa, uma pilha tecnológica que vai da persistência à visão. Embora sejam bons para desenvolvedores menos experientes ou que simplesmente não querem lidar com decisões arquiteturais, eles não trarão a longo prazo todos os benefícios de uma arquitetura bem pensada e, pelo contrário, poderão se tornar barreiras à evolução do software. A tendência é que a tecnologia estacione no tempo, como é o caso do framework Demoiselle, iniciativa da SERPRO que pretendia criar um modelo completo para o desenvolvimento de aplicações para o governo.

Por outro lado, usar componentes arquiteturais independentes, conectando-os para formar a arquitetura do sistema, apesar de inicialmente ser mais trabalhoso, compensa ao longo do tempo. Uma vez definida uma boa arquitetura básica (por exemplo: persistência, modelo, controle, visão e apresentação) é possível atingir um alto grau de flexibilidade e baixo acoplamento, além de extrair o melhor de cada camada usando a tecnologia adequada e não aquela que vem de “brinde” com o framework. Aliás, essa é a tendência de muitos frameworks modernos. Os desenvolvedores os dividem em módulos e permitem o uso das componentes independentemente uns dos outros. Outra tendência, como no caso do Jersey, que faz mapeamento nativo de e para JSON, XML e JAXB, é o aumento da interoperabilidade e extensibilidade dos frameworks através de APIs e configurações específicas.

Finalmente já é de senso comum que, por melhor que seja um framework, ele não vai atender todos os diferentes requisitos arquiteturais do mercado.

Creative Commons O blog State of the Art de Luiz Ricardo é licenciado sob uma Licença Creative Commons. Copie, compartihe e modifique, apenas cite a fonte.