jueves, 8 de junio de 2017

50 sombras de Play

Hoy vamos a hablar sobre el Play, el famoso framework Java para el frontend que se ha hecho muy popular hace pocos años y que ha prometido de hacernos maravillas por los fácil y eficaz que era. Por que se hecho tan famoso? Porque combinaba la facilidad de Ruby on Rails con la eficacidad de Java ( por fin podemos desarrollar en Java sin tener que compilar/re-deployar/linkear/etc cada vez que cambiamos una letra en el código!).

Originalmente escrito en Scala, hoyendía Play tiene también las bibliotecas Java y utiliza tambien el concepto MVC (utilizado en otros frameworks, como p.e. Struts).

Ventajas:

Los ventajas de Play:
  •          No Redeploy! Cambios en el código no hace falta re-deployar
  •          Ligero: no hace falta ni DAO, ni XML
  •          Fácil para empezar, tutoriales son de maravilla
  •          Proyectos tienen una estructura muy lógica
  •          Routing es muy simple: no hace falta ni registrar ni configurar nada (controller/method)


Otros ventajas: no se necesita sesión Java, la sesión está en los cookies (+cash) y la escalabilidad no tiene límites, soporto jpa/hibernate/db, la base datos h2 "in memory", los modelos groovy, generación PDF y Excel, plugin, varios mulos.
Desde fuera parece todo fantástico, me lo compro.
- Estructura fantástica:

- Lanzamiento maravilloso:

- Resultado inmediato:


Mas fácil no puede ser. No hacen falta ni los XMLs enormes,  Cambiamos “welcome” a index.html, obtenemos el resultado inmediatamente (ni redeploy) con F5 (refresh browser)
Añadimos una nueva acción (“hello”)

No hace falta registrar ni mapear esta nueva acción, simplemente vamos a localhost:9000/Application/hello . Hasta los fallos están indicados de maravilla:

Aquí podemos crear nuestro hello.html para q el fallo se vaya y - voila:

Sin re-deploy sin nada.

O pasar un parametro a una pagina - muy facil.
En index.html enviamos el parámetro a hello.html:

En hello.html con groovy:


Ahora pensamos: cómo es técnicamente posible de cambiar los parámetros en Java sin re-deploy? Normalmente el código binario Java (byte code) no memoriza ningún nombre de los métodos o variables en ninguna parte. Como encuentra Java el nuevo nombre del método, si lo cambiamos en nuestro HTML sin redeploy?
Resulta que Play compila solo su código. Ademas es recomendable apagar el compilador estándar para que no haga interferencia con el compilador play. Play compila con un compilador que se parece a lo de Eclipse utilizando los marcadores especiales que meten en el byte code los nombres de los métodos y variables del código Java original. En esta manera, cambiando el nombre del método en nuestro codigo, Play entiende de que método original se refiere y lo encontrará fácilmente a traverso de estos marcadores. Maravilla!
También las listas serán posibles de ser pasadas de una pagina a otra:

En la destinación hacemos una iteración normal:
Como siempre sin restart/redeploy:

Muy cómodo no?
Las pruebas (tests) también pueden ser ejecutadas con play test:

Los tests están bien clasificados (funcional, selenium, unit).

Critica:

Pruebas unitarias casi imposibles:

Hay que pensar críticamente (especialmente cuando alguien te quiere vender algo). Que problema estamos resolviendo? Que otro problema puede ocurrir si haremos eso? 

Vamos a mirar el código autogenerado para los tests y intentamos lanzarlo:




(atencion! estamos haciendo los tests para la pagina que solo imprime un texto, na ma)
que se ve aquí? una conexión a la base de datos en la línea 3? wtf? eso es la prueba unitaria? a mi me parece mas que esta probando todo el programa, no solo un método!
Para hacer los verdaderos tests unitarios hemos creado un modulo:
https://github.com/codeborne/play-tests

se lanzan con
>play tests
o
>play unit-.tests
>play ui-tests

Routing raro:

Que problema resuelve el routing /{controller}/{action}? Bueno, evitamos de meter cada vez la linea de mapping en nuestro file de configuración para mapear la pagina con su acción. Pero al mismo tiempo, si por un error declaramos un método public static , este método sera accesible desde fuera y nuestro server puede ser hackeado muy fácilmente:

Si el mapping para este método estuviera declarado en el web.xml de la aplicación (a la manera "antigua"), un acceso fácil no hubiera sido posible).
Es muy significante que todos los métodos de los controladores son estáticos ("public static"), i.e. la herencia nos han hecho imposible (por una razón o otra). Para hacer la "herencia" de  estos métodos estáticos lo que ellos hacen es llamar la función de la clase que quieres modificar con el método invokeChildOrStatic() (nunca visto una cosa así).
Redirección se hace llamando simplemente el metodo en otra pagina. No hace falta escribir "redirect", como siempre. 

Pero es que eso realmente hace nuestra vida mas simple? Es que ganando una cosa, eso no nos hace perder algo en la visibilidad de nuestro codigo: 
- vete a saber que pagina implementa "details())"
- vete a saber si "details()" es un redirect y no estamos simplemente llamando un método de la misma página?

PropertyEnhancer hace cosas innecesarias:


Nos imaginamos una clase que representa una persona que tiene un nombre.

public class Person
{
   public String name;
}

Play nos va  générer también el setter y el getter. Es que eso es realmente tan necesario? Que pasa, si ya tenemos setName() o getName() en nuestra clase? No lo va llamar, va llamar a lo suyo setName().
Esta magia esta bien a nivel básico, cuando uno cree la pagina web simple y que no tiene que adaptar/modificar el código. Pero en el mundo real, cuando estas en un proyecto, tambien si empeza como una pagina simple, quien te puede garantizar que de aquí a 1 ano el cliente no viene con una nueva idea fantástica que va convertir tu pagina simple en una cosa mas sofisticada. Meter tus manos en la lógica de Play te espera un fracaso.


Errores raros:


- ClassNotFound BeanInfo (a veces cuando haces refresh de la pagina)

Causa:
1) Play carga internamente todas las clases a traverso de la clase  ApplicationClassLoader (metodo loadApplicationClass()) que se encuentra en su biblioteca.
2) Dentro aquel método (loadApplicationClass())Play utiliza java.beans.Introspector que busca el nombre de nuestra clase para saber si este nombre esta registrado en su propriedad Beaninfo:

3) y luego lo mete en una colección suya (con un marcador si esta clase esta compilada o no): 

4) Cuando intentamos a compilar nuestra clase, Play mira si se ha compilado o no (si no se ha compilado - retira nuestra clase de su colección):

5) Si entre p3. y p4 otro hilo de ejecución intenta a acceder la clase BeanInfo - esta clase no sera accesible -> nuestra excepcion. 

6) Este error no se puede corregir

-Play.detectChanges() - el método que determina se había modificaciones en el código (+plugins) antes de cada deploy:



Quien ha tenido la idea de meter start() dentro una excepción? Una excepción es siempre una anomalía y se uno hace start() cada vez que entramos en la excepción, hay un riesgo que error se va reproduir de nuevo y entraremos en un circulo indefinido.

Logging raro:

En una biblioteca "normal" de logging (como log4j) normalmente se escribe el texto y luego la excepción:

logger.error("mi mensaje", ex.getMessage());

En el logger de Play se escribe al revés:

logger.error(ex.getMessage(), "mi mensaje");

Esta bien, pero como no esta documentado a ningun lado, uno automáticamente intenta a hacer a la manera habitual. Y Play no te va generar ninguna excepción StackTrace que no haz metido bien los argumentos! Simplemente tu error no aparecerá! Y vas a perder mucho tiempo para saber porque.

Recarga HotsSwap:

La magia de redeploy....detrás de la magia de Play esta un simple hotswap - un mecanismo que ya esta integrado en las bibliotecas estándares de Java (yo me siento decepcionado . :)


Despues de cada cambio el cache sera vaciado y la aplicación se carga de nuevo. En caso de una aplicación grande cada cambio pequeño va causar la recarga de la aplicación entera. Eso puede impactar el rendimiento de la aplicación.

Otras barbaridades:

- Las excepciones no son ni claras ni visibles (aquí un ejemplo del código Play que en vez de simplemente escribir el error, lo mete en uno de sus registros donde nadie lo puede encontrar). También el mensaje del error no esta para nado claro ("validation.invalid", da igual que error):


- Agrupamiento innecesario de los parámetros: si tenemos una base datos no estándar pero que tiene el driver JDBC que empezar por "h2:mem", vamos a siempre tener un error de la conexión y no vamos a saber nunca porque.


- Cuidado con los logs: no te olvides de crear o log4j.xml o log4j.properties desde el inicio de tu proyecto, Sino no vas encontrar nunca, donde Play escribe las cosas.

- Javadoc del código Play es "fantástico":

- las excepciones muy claras:



Resumen:

Desde que empecé a trabajar con Play, he aprendido de siempre tener mas cuidado con los frameworks que yo quiero utilizar para mi trabajo. Sobre todo presto atención a las cosas siguientes:
- Facilidad para pruebas (sobre todo pruebas unitarias)
- Facilidad de customizar (si sera necesario)
- Si no tiene demasiadas funciones innecesarias (redirect, etc)
- Lo mas importante: pruebalo con tu proyecto (con muchas clases, en un entorno verdadero, con base de datos, etc)

A pesar de mi critica yo agradezco a Play para 2 cosas:
- una revolución en Java Web (de verdad)
- Open Source: el código de Play esta escrito a la manera mas facil de cualquier framework que he visto antes (p.e. Hibernate).


No hay comentarios:

Publicar un comentario