• facebook
  • flickr
  • google
  • instagram
  • lastfm
  • linkedin
  • spotify
  • twitter
  • Goodreads
  • GitHub
  • juanignaciosl.github.io
  • Medium
Cómo se carga una página en mi uso de AngularJS, Play, Slick… (I – cliente)

Cómo se carga una página en mi uso de AngularJS, Play, Slick… (I – cliente)

Una de las preguntas elementales que debo saber responder en mi stack tecnológico y diseño de arquitectura es “¿cómo funciona desde que el usuario solicita la URL hasta que ve la página cargada?”. En la actualidad estoy desarrollando para Nemesis un sistema web de monitorización de sensores, orientado a eficiencia energética, que proporciona al usuario un panel de control web para el seguimiento y análisis de sus datos de consumo eléctrico, gas, etc. Al ver los descriptores de la aplicación (build.sbt en servidor, bower.json en cliente) veo que la lista de librerías no hace sino crecer, y de vez en cuando me gusta echar la vista atrás para verificar que mantengo todo bajo control.

La captura corresponde con la pantalla inicial. Muestra los sensores presentes, con el último dato conocido y la evolución en las últimas horas. Como potencialmente el número de sensores puede ser muy alto, existe un filtro que permite ocultar o mostrar los que necesites. ¿Cómo se genera? En esta entrada describiré con cierto detalle el comienzo, y el lado cliente, dejando para más adelante el lado servidor (con Scala, Play y Slick principalmente).

Carga inicial: Netty, Play Framework

1.- El usuario introduce la URL. Dicha petición le llega a nuestro servidor, Netty. Es el servidor por defecto de Play, que no requiere un servidor de aplicaciones estilo JBoss AS o Glassfish (yikes!). Se arranca tras hacer git push al repositorio de producción, que ejecuta un comando en el hook post_deploy. El comando es un script que genera Play cuando, con el comando stage, preparas el despliegue. La simplicidad tanto del despliegue en si, con git, como de la preparación del desplegable son fantásticas. En un futuro podemos poner un servidor HTTP delante, para balanceo de carga, uptime 100% entre despliegues, envío de estáticos, etc, pero por ahora no ha sido necesario. Typesafe (la empresa detrás de Scala) recientemente ha comprado Spray.io, el cual se ha incorporado como capa de transporte en Akka. En un futuro próximo Spray.io será el servidor detrás de Play, pero eso debería ser transparente para mi.

2.- La ruta es servida por Play en función de la configuración en el fichero routes. En Play todo es compilado a Scala para garantizar un tipado fuerte por todo el proyecto, y este fichero no es una excepción. La ruta se hace corresponder con una invocación a un método de un controlador, o con un recurso estático.

 3.- En nuestro caso, la carga de la página inicial, le devolverá el fichero estático index.html. Play tiene un interesante sistema de plantillas también fuertemente tipado con el que experimenté para la página de ofertas de viaje personalizadas. Sin embargo, al usar AngularJS combinar ambas tecnologías no parece natural. ¿Para qué hacer que el servidor renderice la página, cuando estamos delegando ese trabajo en el cliente? En esta aplicación opté por una arquitectura cliente-servidor “más pura”, y el servidor actúa sólo como proveedor de recursos estáticos y API REST para la consulta de datos. En este sentido, me siento muy identificado con el interesante artículo Why we abandoned server generated web pages, ideas que ya comenté en el Postmortem de “Diseñando una arquitectura”.

Cliente: AngularJS, Foundation, Sass, Javascript, D3…

4.- Cuando el index.html llega al cliente se disparan hasta 54 peticiones de ficheros más, entre recursos estáticos (fuentes, pngs, csss, jss…) y peticiones de datos. Esto es claramente subóptimo -siendo generosos-, pero, entre la caché (casi todo se resuelve con 302 Not Modified) y la naturaleza de la aplicación (una vez cargado inicial se mantendrá muchas horas pidiendo sólo datos) probablemente nunca vaya a resultar un problema. En caso de necesitar optimizar podremos trabajar en un servidor http en frente, comprimir y agrupar ficheros, etc.

Los ficheros están organizados por funcionalidad, no por aspectos técnicos como “capas”. Podéis leer al respecto en estas recomendaciones para proyectos AngularJS grandes. Aunque generas muchos ficheros esto lo hace más mantenible, y la propia estructura del proyecto te da una imagen de lo que hace.

Play integra cierta gestión de assets. Por ejemplo, compila el Javascript con Closure, que de vez en cuando caza algún error. También podría integrar RequireJS o la compilación de Sass (por defecto viene con Less), pero por ahora no es algo que me aporte demasiado y opto por hacerlo manualmente , pero por ahora con integrar Play con Sass y Compass con Foundation me vale.

5.- De la apariencia se encarga Foundation con CSS propio de la aplicación (Sass, por supuesto), y algunas fuentes sacadas de Google Web Fonts. Foundation es un framework muy interesante, del estilo a Bootstrap, basado en Sass y diseñado mobile-first, con componentes, grid, etc. Una de las cosas que más me gusta es que permite hacer marcado semántico mediante mixins incluso para el grid (como Neat) y así no tener que llenar el html de clases de apariencia, por ejemplo indicando cuántas columnas ocupa algo:

6.- De la estructura y comportamiento de la página se encarga AngularJS. La única crítica que le veo a esta librería es que es muy biased, imponiéndote los patrones que ellos consideran adecuados, pero me gusta muchísimo y cosas como el data binding y las directivas permiten desarrollar el UI con un estilo totalmente declarativo muy claro y mantenible. Utilizo la librería base con los módulos angular-route para el enrutado (oficial) y angular-translate (no oficial) para la traducción. El motor para las gráficas es d3, pero a través de NVD3 y angular-nvd3-directives. Esto se debe a que cuando manipulas el DOM en AngularJS debes usar directivas. Pensé que tendría que prepararme mis propias directivas, pero encontrarme estos proyectos, junto con NVD3 para gráficas reutilizables, me permitió tanto prototipar y ofrecer valor al usuario muy pronto. Aunque puede parecer un número elevado de librerías, su uso es terriblemente sencillo y me impone pocas restricciones, pudiéndome centrar en el producto, que es lo importante.

7.- No sólo he usado directivas de terceros, sino que cada componente de estructura intento extraerlo a su propia directiva.

8.- AngularJS es un MVC Javascript, por lo que tras fragmentos de página nos encontramos con controladores, encargados de poco más que meter en $scope datos obtenidos desde servicios.

9.- Como he dicho, los controladores invocan a servicios para obtener los datos. Los servicios en AngularJS son esencialmente singletones que puedes inyectar en los controladores para diferentes tareas, como invocar APIs externos (mi uso principal) o compartir estado entre controladores.

En los controladores y, sobre todo, servicios, se concentra la chicha. A destacar:

  • Sólo tengo que soportar navegadores modernos, así que puedo utilizar métodos de Array como map, filter, forEach… ^_^
  • Recibo JSON, pero trabajo con objetos JS generados pasando estos datos a la función constructor. Esta es la decisión de diseño con la que más dudas tengo. Por un lado, wrappear cada dato JSON en un objeto que tiene poco comportamiento (interpretar rangos, datos acumulados y poco más) me huele a overkill y sobreingeniería. Sin embargo, pienso que si no hiciese el lado servidor no lo dudaría, y me permite abstraer el interfaz de la estructura JSON recibida.
  • Todos los métodos públicos de los servicios devuelven promesas, que AngularJS incorpora por defecto a través de $q, una simplificación de Q.js. Esto hace sencillísimo trabajar con asincronía, y me permite además mantener un estilo consistente en cliente y servidor. Esto es importante tenerlo en mente desde el primer momento: al prototipar conviene que los mocks de los servicios de AngularJS también funcionen asíncronamente, para que cuando los sustituyas por el comportamiento real, contra $http, sea transparente para los controladores:
Conclusiones

Espero haber dado un vistazo más o menos claro sobre cómo trabaja la aplicación en el lado cliente, y el por qué de ciertas decisiones tomadas. Para este tipo de aplicaciones, AngularJS me parece, sencillamente, perfecto. Tengo mis dudas acerca de si lo usaría en la típica aplicación de gestión con chopocientos campos en cada formulario, porque el trasiego al servidor vía REST creo que sería más engorroso de programar y con más acoplamiento con el servidor que con un, digamos, JSF. Tampoco creo que aporte tanto para una aplicación centrada en contenido estático (“un WordPress”, para entendernos). Sin embargo, para aplicaciones en las que la interacción en cliente vaya a exigir Javascript, y aquellas que encajen bien modelando la interacción con el servidor vía API, es ideal. Como corolario, encaja muy bien para proyectos que requieran aplicación móvil y web, porque te permite reutilizar el API. Y ojo, que también se puede embeber para no programar en nativo, con Cordova y similares. Señores, a remangarse y aprender a usar Javascript correctamente.

Si no te gusta la idea de separar el cliente en controladores, servicios, filtros y directivas, AngularJS no es para ti, pero creo que el diseño resultante es claro y mantenible. Te ata al framework, está claro, pero es un tradeoff más que asumible. No he usado otras como Enver, así que no puedo comparar, pero si estás acostumbrado a jQuery a pelo, sencillamente no hay color. Sobre todo, insisto, por el estilo declarativo resultante. Además, como habrás visto, ya hay librerías con directivas para casi cualquier necesidad. La documentación es fantástica, por cierto, aunque adolece de que ofrece demasiadas alternativas para ciertas cosas (como la declaración de controladores y sus dependencias) y al principio es un poco lío seguir los ejemplos. Y si buscas fuera (allá tú), ojo con la versión de Angular para la que es, que en la 1.2 cambiaron cosas.

El uso de promesas para simular y soportar la asincronía es un puntazo. En el último proyecto móvil que programé prototipé el interfaz con llamadas síncronas a datos de ejemplo. ¡Error! Eso me permitió presentar un interfaz a validar muy pronto, pero después tuve que rehacer casi todo al llamar al servidor real. Aquí tuve eso en cuenta desde el principio. $q lo deja muy, muy fácil. Lo siguiente con lo que experimentaré será utilizarlo no sólo para la comunicación con el servidor, sino para otras cosas como la interacción de usuario.

En el siguiente episodio, el lado servidor ;-)

Némesis Energy 0.16

3 Comentarios

  1. Cómo se carga una página en mi uso de AngularJS, Play, Slick… (II – servidor) | juanignaciosl · abril 18, 2014

    […] la anterior entrada expliqué, grosso modo, lo que ocurre en el navegador del usuario en una aplicación de monitorización de sensores en tiemp…. En esta segunda edición toca revisar el papel del […]

  2. patoroco · abril 19, 2014

    Muy interesante y completo el post. De hecho, muchas de mis preguntas de ayer sobre Angular y demás estaban ya contestadas aquí.
    Voy a por la segunda parte… :)

  3. 10º aniversario | juanignaciosl · septiembre 21, 2014

    […] comenzado a escribir en inglés (éste es una excepción), hecho proyectos de ofertas de viaje, monitorización de señales y consumos eléctricos, aplicaciones iOS e incluso planificadores de mesas de boda ataría cabos y vería que mis planes […]

¿Me dejas una respuesta?