Blog dedicado a la distribución GNU/Linux que uso habitualmente, Arch Linux, a mis andanzas alrededor del software libre, la programación y a otros temas relacionados con la tecnología y la informática.
Disponer de información para tratar de averiguar la causa de un problema es vital para poder resolverlo rápidamente. Para ello existen los sistemas y librerías de trazas que permiten a la aplicación sacar mensajes a un archivo para su consulta posterior ya sea en Java, Javascript o seguro que otros lenguajes.
En el caso de Grails inyecta en varias entidades como controladores, servicios y clases de dominio el objeto log con el que se pueden emitir las trazas con el nivel que deseemos.
Sin embargo, a veces nos puede resultar interesante también sacar alguna traza en los gsp para conocer que es lo que se ha emitido, especialmente si se trantan de gsp que contienen lógica de negocio con etiquetas g:if especialmente complejas. Tener mucha lógica en las vistas es una práctica no recomendada para evitar el código espagueti en los gsp que en el mantenimiento puede causarnos problemas al posiblemente tener esa lógica duplicada en varias zonas diferentes de la aplicación. Los frameworks que usan un sistema de plantillas donde por un lado están los datos y por otro la plantilla sin una posibilidad propia («built-in») de extraer esa lógica a una entidad externa suele ser habitual. Esto nos puede obligar a precalcular todas esas condiciones y pasarlas a la visa como datos lo que nos exige conocer previamente en el controlador exactamente que datos necesita la vista y mantener el código de dos archivos sincronizados o crear métodos con esa lógica en los objetos que le pasamos a la vista como podrían ser en las entidades persistentes de dominio.
Una forma de emitir trazas desde un gsp es incluir un scriptlet que obtenga un logger y posteriormente hacer log.debug. Pero si hacemos esto en muchos gsp y de forma habitual es mejor hacerlo con un tag ya que el código de los gsp nos quedará más limpio y será un poco más sencillo además de tener centralizado en un único sitio el tratamiento de los logs de todos los gsp. Ese tag necesitará recibir el nivel de la traza y el mensaje de la traza al menos. Podría ser como lo siguiente:
Su uso en un gsp sería:
Y este sería el caso para el framework grails y su parte de visualización con gsp, para otros frameworks que probablemente tengan otro sistema para la parte de la vista nos puede ser útil una solución con similar funcionalidad.
En Java los archivos Properties que se cargan con la clase ResourceBundle utilizados comúnmente para realizar la internacionalización y la localización a varios idiomas en una aplicación Java por defecto usan la codificación de caracteres ISO-8859-1 (salvo que usemos un framework o una librería lo haga de otra forma). Los caracteres que no pertenezcan al ISO-8859-1 deben ser escapados, por ejemplo \u20AC para el símbolo del euro (€). Esto hace que si tenemos una aplicación que trabaja con caracteres en varios idiomas tengamos unos ficheros properties con un montón de caracteres de escape que impide su legibilidad al momento de escribirlos o nos obliga a usar el comando native2ascii lo que nos produce también archivos poco legibles.
La clase ResourceBundle permite cargar archivos properties según un Locale pero como digo los carga con la codificación ISO-8859-1 y esto es un problema para los locales como el chino donde casi todos los caracteres deben ser escapados. Si queremos tener archivos más legibles y sin necesidad de escapar los caracteres debemos extender la clase ResourceBundle.Control y redefinirla un poco para que cargue los properties en UTF-8. La implementación sería la siguiente:
El código anterior es parte de la clase EncodingControl donde está redefinido el método newBundle. La magia está en la siguiente linea, donde al InputStreamReader se le indica la codificación de caracteres:
Su uso para cargar los ResourceBundle sería:
Y el programa completo en el que puede verse una carga de un archivo properties con codificación ISO-8859-1 y otro con codificación UTF-8:
Como la RPi la uso por SSH y sin interfaz gráfica necesitaba buscar un reproductor de música basado en una interfaz de texto, que permaneciese ejecutandose a pesar de terminar la sesión ssh y usable desde la terminal para reproducir música además de que consuma pocos recursos (ridículos comparados con cualquier programa gráfico). Después de una búsqueda en google y en la wiki de Arch Linux encontré cmus (C* Music Player). Y me ha parecido tan sencillo y bueno que se ha convertido en mi reproductor de música incluso en el ordenador personal que uso con interfaz gráfica, no necesito más que lo que ofrece, antes usaba Banshee y anteriormente Rhythmbox.
La dificultad de los programas o comandos basados en la consola es que suelen ser menos intuitivos que los que tienen interfaz gráfica para los usuarios nuevos de esos programas. A continuación explicaré el funcionamiento básico de cmus para que un usuario que esté buscando en este programa una alternativa al que usa ahora le sea más sencillo iniciarse y no abandone cmus por otra opción porque le cueste descubrir como conseguir con él lo que uno quiere. Primeramente deberemos instalar su paquete, en Arch Linux con:
Nada más iniciar cmus nos encontraremos con una lista vacía de nuestra colección de música. Primeramente debemos conocer es que cmus se divide en varias pantallas (o tabuladores) que se acceden con las teclas:
1 para Colección de música
2 para Librería de música
3 para Lista de reproducción
4 para Cola de reproducción
5 para Navegador de archivos
6 para Librería de filtros
7 para Configuración y referencia de teclas
Para añadir nuestra colección en música mp3 u ogg iremos al navegador de archivos, una vez que estamos en el directorio que contiene la música que queremos añadir pulsamos al tecla «a» sobre el directorio, cmus buscará de forma recursiva los archivos de esa carpeta. En la pestaña de la Colección de música veremos que ya contiene los archivos organizados por artista y álbum. Con las flechas «arriba» o «k» y «abajo» o «j» podemos cambiar de artista y con la tecla «espacio» podemos expandir un artista para ver cada unos de sus álbumes, con la tecla «tabulador» cambiamos entre el panel artista/álbum y track. Con la tecla «retorno» podemos reproducir un artista, álbum o pista dependiendo de donde este la selección o foco.
Si queremos crear una lista de reproducción con música de varios artistas, álbumes o pistas estando en la Colección de música pulsaremos la tecla «y», con esta tecla la música se irá añadiendo a la Lista de reproducción. Estando en la Lista de reproducción podemos hacer que se reproduzca de forma aleatoria con la tecla «s» o de forma repetida «r», en la barra de estado en la parte inferior veremos las opciones que están activas (CRS). Con las tecla «z» podemos reproducir el elemento anterior de la lista, con la tecla «x» iniciamos la reproducción desde el inicio de la pista, con «c» pausamos/reiniciamos la reproducción, con «v» se detiene la reproducción y con «b» se reproduce el siguiente elemento de la lista. Todas estas teclas de reproducción están en la fila de abajo y todas seguidas en el teclado con lo que es cómodo usarlas y no muy complicado de recordarlas.
Si queremos limpiar la Lista de reproducción o nuestra Colección de música podemos hacerlo individualmente sobre cada elemento con la tecla «Supr» o «D» (la mayúscula es importante) o con el comando :clear, los comandos de cmus funcionan de forma similar a como se trabaja con vim, primeramente pulsando «:» y luego introduciendo el comando y pulsando la tecla retorno. Para buscar una pista en la Librería de música pulsamos «/» seguido del término de la búsqueda, según vayamos escribiendo la lista se irá posicionando el el primer elemento encontrado, si queremos encontrar la siguiente coincidencia podemos hacerlo con la tecla «n» (al igual que con vim). Con los comandos «:save ~/playlist.pls» podemos guardar la lista de reproducción y con «:load ~/playlist.pls» cargarla.
Aunque pueda parecer que no y en muchos casos no se tenga en cuenta la diferencia que puede haber entre una aplicación web no optimizada y optimizada puede ser significativa en varios aspectos.
¿Por que en algunos casos es importante optimizar la aplicación? Uno de los motivos es conseguir una mejor experiencia de usuario haciendo que la página le cargue más rápido. Una página que tarde en cargar demasiado puede significar pérdida de visitas y si se trata de una página de comercio electrónico de clientes y compras. Si un flujo importante de usuarios de una página procede de las búsquedas la velocidad de carga de la página es importante ya que es una de las variables que tiene muy en cuenta el algoritmo de Google para establecer el ranking de los resultados, reducir el tiempo de carga puede significar aparecer antes en los resultados de la búsqueda y esto significa más clics en nuestro resultado, más visitas y nuevamente más potenciales clientes y compras. Y en caso de que no estemos desarrollando una aplicación accesible desde internet sino una aplicación para una empresa o administración publica hacer que cargue más rápido puede suponer una mayor satisfacción de los usuarios y una aplicación más eficiente (con una menor carga para el servidor, con la posibilidad de soportar más usuarios o con menor necesidad de hardware).
La velocidad de carga de una página se ve afectada por varios elementos entre ellos el número de peticiones que hace la página para recuperar los recursos (imáges, css, fuentes, javascript) y el peso de esos archivos. Cuantas menos peticiones hagamos y menos peso de los archivos notaremos una significativa reducción de tiempo de carga de la aplicación. El tamaño de los archivos no afecta tanto a dispositivos que acceden por líneas de banda ancha de varios megas pero hay muchos dispositivos móviles como los teléfonos inteligentes que hace uso de conexiones de datos móviles, las velocidades son más modestas en estos y muchos usuarios tienen cuotas de megas descargados, es de agradecer tenerlos en cuenta también.
En esta entrada usaré el Ejemplo de la lista de tareas con Marionette, un ejemplo sencillo pero bastante completo que usa RequireJS, Backbone y Marionette, jQuery, Mustache, el plugin i18n de RequireJS para internacionalizar los textos de las plantillas, el plugin text para externalizar del html las plantillas de Mustache. Me centraré en como optimizar toda esa cantidad importante de código javascript que necesita el ejemplo cargar en el navegador del usuario. El estado inicial de la aplicación es de 28 peticiones, un peso de 737 KiB utilizando las versiones no minimizadas de las librerías javascript y un tiempo de carga de 261 ms funcionando en local, si tomásemos medidas de tiempo pero funcionando en internet la latencia haría que el tiempo de carga fuese aún mayor. En la imagen se puden apreciar estos datos en la parte inferior de las herramientas para desarrolladores de Chrome.
Para minimizar los archivos de javascript y reducir su peso y también para agregarlos en unos solo que evite muchas peticiones usaré el optimizador de RequireJS (r.js). Esta utilidad ya la que expliqué en la Optimización de módulos RequireJS y archivos javascript del ejemplo más sencillo de la Introducción a Backbone pero ahora lo aplicaré con este ejemplo de Marionette que es bastante más complejo.
Para usar r.js necesitaremos instalar previamente node.js (pacman -S nodejs en Arch Linux). Después deberemos crear el archivo de configuración para r.js, en el que básicamente indicamos la localización de los archivos a optimizar (baseUrl), el nombre del módulo de la aplicación (main), el archivo de resultado agregado y minimizado (out) y la misma configuración shim que utilizaríamos en la aplicación (shim).
Ejecutando este archivo build.js con r.js a través de node obtendremos el archivo agregado y minimizado main.min.js que será el único archivo de módulo que necesitará cargar nuestra aplicación:
Para usar este nuevo módulo deberemos cambiar el nombre del archivo de main.min.js a main.js o cambiar el nombre del módulo del archivo generado de main a main-min (al final del archivo generado, en el define) si hacemos esto último deberemos especificar este nuevo módulo en el atributo data-main de la etiqueta script que cargar el archivo require.js (en Index.tml). En el archivo parece que a pesar de optimizarse se siguen generando algunos comentarios si queremos optimizarlo completamente podemos eliminarlos. El resultado es que cargando este módulo optimizado ahora la aplicación hará tan solo 14 peticiones, la mitad de peticiones, necesitará descargar 403 KiB y se cargará en 245 ms. La diferencia de tiempo no es significativa por hacer las pruebas en local, como decía la latencia de internet o el ancho de banda de un móvil probablemente haría que el tiempo fuese bastante mayor.
Finalmente si queremos automatizar este comando con Gradle podemos hacerlo añadiendo lo siguiente en nuestro archivo de construcción:
Optimizar el javascript y los módulos de RequireJS solo es una de las cosas que podemos hacer para optimizar nuestra aplicación, hay otras muchas cosas que podemos hacer como se comentan en los siguientes enlaces, además si es el caso también deberíamos tener en cuenta el SEO:
Como comentaba en el ejemplo de una aplicación de una lista de tareas en una aplicación javascript Backbone es una herramienta que nos puede ayudar mucho a evitar que el código se nos convierta en difícil de manejar facilitándonos como estructurarlo con los modelos, colecciones y vistas y permitiéndonos separar el modelo de la vista actualizando estas últimas a través de los eventos que produce el modelo y escuchados en las vistas.
Backbone es una herramienta que nos da bastante libertad en cuanto a como queremos hacer las cosas y de lo que ofrece podemos usar solo lo que queramos. En algunos casos podemos considerar que Backbone ya es de por si una solución suficiente pero pero en otros podemos necesitar algo que nos facilite la tarea un poco más, de hecho hay muchas herramientas de terceros que proporciona multitud de funcionalidades adicionales sobre Backbone.
Usando Backbone por si solo a medida que vayamos haciendo varias aplicaciones nos daremos cuenta que debemos escribir código que empezaremos a considerar repetitivo, tampoco nos ayudará en desasociar los modelos de las vistas cuando eliminemos estas de la aplicación y si no lo hacemos correctamente tendremos fugas de memoria o vistas zombies que siguen escuchando eventos de los modelos cuando esperamos que se hubiesen destruido, también debemos encargarnos de gestionar donde deben visualizarse las vistas. Marionette es una de esas herramientas que nos puede ayudar a facilitarnos las tareas anteriores que en Backbone debemos hacer manualmente, además de ofrecernos algunas otras funcionalidades adicionales, pero también sin obligarnos a usar lo que no queramos siguiendo la misma filosofía de Backbone.
En esta entrada implementaré el mismo ejemplo de la lista de tareas que hice con Backbone pero esta vez implementado con Marionette aunque revisándolo incluiré un par de funcionalidades adicionales como externalizar del html las plantillas de las vistas y como internacionalizar los textos que aparecen en ellas si necesitamos soportar múltiples idiomas. Para ello utilizaré una de las versiones «bundled» que ofrece Marionette y un par de dependencias que necesita (wreq y babysiter).
El modelo y la colección de tareas es muy parecido aunque se han simplificado un poco al moverse el moverse el código que convertía el modelo a json a la vista, donde Marionette ofrece y llama al método serializeData cuando necesita y posteriormente el método render con los datos obtenidos, en el método render en definitiva se usa Mustache para producir el html de la vista. En realidad, el convertir el modelo al json que necesita a la vista está mejor colocado en la vista, ya que la vista es la que conoce los datos que necesita mostrar del modelo. Otra cosa muy útil que nos ofrece Marionette es la sección con los elementos ui de las vistas, esto evitará que tengamos los sectores de jquery repartidos por varios sitios de la vista y hará el código más simple y legible. Las secciones de las vistas modelEvents y collectionEvents sirven para los mismo que hacíamos en el método initialize con .on o .listenTo de Backbone.
Los objetos Marionette.ItemView sirven para visuaslizar un modelo (una tarea) y Marionette.CollectionView sirve para mostrar una colección de elementos (una lista de tareas). El objeto Marionette.Layout se utiliza para componer una vista con varias secciones (tareas y estado) donde podremos colocar las vistas de la aplicación, aparte de estas secciones un Layout es como una vista Marionette.ItemView con su plantilla. Con Marionette.Controller construiremos una clase que podemos utilizar para ofrecer la interfaz de la aplicación al exterior del módulo con únicamente los métodos necesarios (sin initialize, render y los métodos onXxx del mismo ejemplo con Backbone). Y hasta aquí, son los cambios más importantes en cuanto al cambio de Backbone a Marionette.
A continuación el código del módulo tareas que contiene la mayor parte del código javascript de la aplicación y el módulo main que no cambia en nada.
Para externalizar las plantillas del html podemos usar el plugin de RequireJS text que permite cargar el contenido de archivos como dependencias de un módulo tal como sucede como los archivos javascript. Y para internacionalizar las cadenas de las plantillas el plugin i18n. Los literales de cada idioma se definen en un archivo distinto, en el caso de esta aplicación solo he proporcionado uno, para el idioma inglés en la carperta src/main/webapp/js/i18n/nls/en, cada uno de estos archivos contiene una clave/valor, algunos de los cuales son una plantilla de Mustache para sustituir las variables como en «{{completadas}} tareas de {{total}} completadas». A continuación el código de un plantilla de Mustache que se carga de forma externalizada y el archivo con los literales que se utilizarán en la localización por defecto. Estos dos plugins de RequireJS probablemente nos sean necesarios y útiles en un ejemplo real.
El idioma de la aplicación se especifica en el archivo que genera el html de la página.
La aplicación tiene el siguiente aspecto:
Para terminar y como prólogo de la siguiente entrada un detalle que comentaré es la cantidad de archivos que se cargan para este ejemplo y eso que no es excesivamente complejo, en total al cargar el ejemplo se hacen 31 peticiones, unas cuantas son de los estilos, fuentes e imágenes pero la mitad son de archivos js y plantillas mustache. En la siguiente entrada explicaré como optimizarlo para conseguir reducir esas 31 peticiones a 13, con una diferencia importante de tiempo de carga y esto en local (probablemente puesto el ejemplo en internet la latencia y la diferencia sería mayor), también conseguiremos reducir algunos kilobytes de peso a la página. La optimización será similar a lo que explique en la Optimización de módulos RequireJS y archivos javascript del ejemplo más sencillo de la Introducción a Backbone pero aplicado a este ejemplo de Marionette que es bastante más complejo. En el código fuente puede verse también como ejecutar pruebas unitarias usando Grunt, Jasmine, Sinon y como integrarlo con Gradle pero la expicación de esto será tema para otra entrada.
Como en el resto de entradas el código fuente completo lo puedes encontrar en mi repositorio de GitHub. Si quieres probarlo en tu equipo lo puedes hacer de forma muy sencilla con los siguientes comandos y sin instalar nada. Si no dispones de git para clonar mi repositorio de GitHub puedes obtener el código fuente del repositorio en un archivo zip con el anterior enlace.
Un patrón de diseño aplicado adecuadamente para resolver un problema puede ayudar enormemente a simplificar el código y facilitar el mantenimiento. Si tenemos un código que es difícil de mantener y entender, hay código duplicado y no tiene ninguna organización puede que aplicar un patrón de diseño nos resuelva el problema en gran parte.
El patrón de diseño State nos puede ser de mucha utilidad en los casos que por ejemplo una entidad tenga asociado un grafo de estados con transiciones permitidas y no permitidas entre algunos estados. En función del estado, sus datos y la transición la entidad puede comportarse de forma diferente. Por ejemplo, supongamos que tenemos una entidad Compra que a lo largo de su vida en la aplicación pasa por diferentes estados:
creada: la compra se acaba de crear.
en espera: se ha hecho una compra y se está esperando que el pago sea correcto.
verificada: el pago es correcto y se está esperando a enviar el producto.
cancelada: la compra se ha cancelado porque el usuario no quiere ya el producto, no hay existencias u otro motivo.
enviada: el pedido ha sido enviado.
Y tiene diferentes transiciones como:
comprar: la compra pasa de creada a en espera de verificarla.
verificar: la compra pasa de en espera a verificada y esperando a enviarse.
cancelar: la compra se puede cancelar excepto una vez que ya se ha enviado.
enviar: la compra se envía al usuario y ya no puede cancelarse.
Si diseñamos este flujo de estados sin el patrón State probablemente acabemos con una clase con un montón de condiciones y métodos de bastantes líneas sin una organización clara a simple vista. Para evitarlo aplicaremos el patrón State a este pequeño flujo de estados. En cuanto a código este patrón se basa en dos ideas:
Cada estado será representado una clase.
Cada una de estas clases contendrá un método por cada posible transición.
Y estas dos simples ideas son suficientes para guiar la tarea de codificación. Por lo tanto tendremos los siguientes clases que representarán a los estados: CreadaCompraState, EnEsperaCompraState, VerificadaCompraState, CanceladaCompraState y EnvidaCompraState.
Según el diagrama de estados y transiciones no todas las transiciones son posibles, una compra en espera no puede enviarse. Pero en el código estamos haciendo que todos los estados tengan todos los métodos que representan todas las transiciones, la forma de hacer en el código que una transición no sea posible para un determinado estado es lanzando una excepción en su correspondiente método, el método enviar del estado en espera, lanzará una excepción ya que es este estado aún la compra no puede enviarse. La clase abstracta AbstractState implementará la interfaz CompraState y lanzará una excepción en todos los métodos, las clases que extiendan de esta podrán redefinir los métodos que necesiten utilizando la propiedad de la programación orientada a objetos del polimorfismo, esta clase abstracta nos permitirá implementar en cada clase de estado únicamente los métodos con las transiciones válidas. Las clases nos podrían quedar de la siguiente forma:
Para que los métodos puedan acceder y manipular los datos de la compra se les pasa como parámetro en el constructor. La factoría CompraStateFactory encapsula la lógica para construir cada uno de los estados. La implementación de cada uno de los estado podría ser la siguiente.
Finalmente, la clase Compra podría ser de la siguiente forma:
El patrón State puede facilitarnos bastante la vida como programadores pero si el diagrama de estados fuese más complejo, con mucha lógica de negocio y el flujo dependiense de información independiente de la compra quizá deberíamos evaluar su si un motor de procesos (BPMS) como Activiti y un sistema de reglas de negocio (BRMS) como Drools sería más adecuado.
El código fuente completo de ejemplo los puedes obtener de mi repositorio de github con los siguiente comandos:
Una de las dificultades que nos solemos encontrar a la hora de hacer pruebas unitarias es como probar el código que accede a la base de datos. El problema es que ese código necesita de una base de datos para ejecutarse, si se usa una base de datos como MySQL o PosgreSQL más que una prueba unitaria puede considerarse una prueba de integración y las pruebas pasan a depender de ese sistema externo con lo que las pruebas no son autónomas.
La base de datos H2 es una base de datos que puede ser embebida en una aplicación. Esta característica hace que pueda servir como base de datos contra la que lanzar los teses sin necesidad de una base de datos externa, además puede ejecutarse en memoria y sin comunicación de red lo que hace que sea muy rápida y los teses también. Tampoco es una solución perfecta ya que H2 puede tener un comportamiento diferente de la base de datos real pero en la mayoría de los casos nos servirá perfectamente.
El método beforeClass inicializa la persistencia y crea el DAO. El método before borra los datos que haya creado un test anterior y crear los datos de prueba que una prueba puede esperar que existan, en este caso se trata de un único producto. Las pruebas consisten en probar el método findAll y search del DAO (el método removeAll puede considerarse probado en el before aunque podría crearse un caso de prueba específico):
En las pruebas unitarias al código que use el DAO puede proporcionársele un «stub» de este haciendo que devuelva los datos que necesitemos y sin necesidad de una base de datos como H2 pero para probar el DAO hay que disponer de una base de datos. Como es código que también puede contener fallos es bueno que también esté cubierto con algunas pruebas. Con Hibernate todo será más sencillo ya que con este además de abstraernos de la base de datos específica puede crear el esquema con las tablas y campos a partir del modelo de forma que antes de pasar los teses no necesitemos lanzar un script con sentencias SQL para crear las tablas.
Usar H2 como la base de datos contra que lanzar las pruebas unitarias es una solución imperfecta ya que idealmente la base de datos debería ser igual a la base de datos que vaya a usar la aplicación real (probablemente MySQL o PosgreSQL). H2 tiene la ventaja de que los teses se ejecutarán más rápido que con un sistema real externo y que las pruebas son más autónomas ya que no depende de ese sistema externo, aún así si hacemos uso de elementos nativos de la base de datos y queremos que queden cubiertos con pruebas no nos quedará más remedio que disponer de una base de datos «más real» que H2 para ellas aunque como decía estas pasarán a ser más de integración que unitarias.
Como el resto de ejemplos que escribo en el blog el código fuente lo puedes encontrar en mi repositorio de GitHub. Para probar este código en tu equipo basta con ejecutar: