Quantcast
Channel: Adictos al trabajo
Viewing all 989 articles
Browse latest View live

Autentia lanza su primer producto propio, Teams

$
0
0

Os presentamos con mucha ilusión el PRIMER PRODUCTO PROPIO de Autentia, Teams. La próxima revolución en el marketing orientado a redes sociales esconde un año y medio de un duro trabajo por parte de los Autentios, que han participado en el proyecto desde diferentes roles, ya sea como desarrolladores, testers, responsables de Producto, Tecnología o incluso el propio CEO de la empresa de software matriz, Autentia.

Queremos agradecer a todos los amigos de Autentia sus indicaciones a lo largo de este proceso. Su consejo y apoyo nos han ayudado en esta nuestra primera andadura como spin-off de la gran Autentia. El producto es fantástico y sus consejos han ayudado a desarrollar un MVP de lo más envidiable. Una vez conseguido lo más difícil, ¡ahora toca presumir del retoño!

¿Quieres conocer Teams? Sus creadores te lo explican en este vídeo.

Si quieres saber toda la información, haz click AQUÍ.


Primeros pasos con CodeIgniter

$
0
0

Siguiendo este tutorial podrás crear de manera muy sencilla una aplicación de estructura MVC con CodeIgniter.

Índice de contenidos


1. Introducción

CodeIgniter es un framework de código abierto para crear aplicaciones web utilizando arquitectura MVC. Permite a los desarrolladores realizar proyectos de manera muy rápida, con una interfaz simple y una lógica muy sencilla.

Trabajar con una estructura MVC facilita la organización del código y poder establecer una división entre el acceso a datos, la lógica de negocio y la capa de presentación de nuestra aplicación.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 17’’ (2.66 GHz Intel i7, 8GB 1067 Mhz DDR3, 500GB Flash Storage).
  • Sistema Operativo: Mac OS X El Capitan 10.11.4
  • Entorno de desarrollo: Eclipse Mars for PHP Developers
  • Apache/2.4.18
  • PHP 5.5.31
  • MySQL Community Server 5.7.12
  • CodeIgniter 3.0.6

3. Instalación de CodeIgniter

La instalación de CodeIgniter es muy sencilla. Partiendo de que ya tienes instalado en tu equipo tanto PHP como Apache, solo debes seguir los siguientes pasos:

  • Lo primero que debes hacer es crear un proyecto PHP.
  • A continuación, descarga la última versión de CodeIgniter de la página oficial.
  • Descomprime el zip que has descargado y copia o arrastra su contenido dentro del proyecto de PHP que has creado previamente.

Ya has instalado CodeIgniter y está listo para empezar a trabajar con él. La estructura de tu proyecto debe haber quedado así:

Estructura ficheros CodeIgniter

3.1. Configuración

Aunque nuestro entorno ya está preparado para empezar a escribir código, es recomendable modificar los ficheros de configuración para facilitar algunas tareas.

  • Modificando el archivo application>config>routes.php, puedes cambiar el controlador por defecto, de manera que al escribir la URL Base de tu proyecto en el navegador, se ejecute la acción que tú quieras. Puedes cambiarlo en la siguiente línea:
    $route['default_controller'] = 'welcome';
  • Es importante tener clara cuál es la URL que llamará a nuestra aplicación. Para poder acceder a ella de manera sencilla desde tu código sin necesidad de escribirla constantemente, CodeIgniter pone a tu disposición el helper URL. Si quieres utilizarlo, deberás modificar dos ficheros.

    • En primer lugar, debes definir tu URL Base en application>config>config.php.
      $config['base_url'] = 'http://127.0.0.1/miproyecto';
      En ese mismo fichero puedes definir muchas otras cosas, como el charset, el proxy o la configuración de cookies y variables de sesión.
    • Seguidamente, en application>config>autoload.php tienes que cargar el helper URL para evitar hacer un include cada vez que quieras utilizarlo.
      $autoload['helper'] = array('url');

A la hora de llamar a nuestra aplicación desde el navegador, CodeIgniter lo hace de la siguiente manera:

base url

Para prescindir del elemento index.php y que apache reescriba las urls, puedes incluir en la raíz de tu proyecto un fichero .htaccess con la siguiente configuración:

RewriteEngine On
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteCond %{REQUEST_FILENAME} !-d
	RewriteRule ^(.*)$ index.php/$1 [L]

4. Operaciones básicas

Tras instalar CodeIgniter, puedes mostrar tu aplicación en el navegador simplemente introduciendo la URL Base, y aparecerá la vista que carga el controlador por defecto, definido en el fichero routes.php, como indicaba anteriormente. Si la muestras en este punto, se cargará la página de bienvenida del controlador Welcome.php.

Index CodeIgniter
Para poder visualizar la salida de la aplicación es importante que tu proyecto esté desplegado en el Document Root del servidor.

4.1. Modelo

El modelo es el encargado del acceso a datos. Para este ejemplo he creado una base de datos en MySQL, con una sola tabla llamada “Usuarios”, de manera que pueda acceder a ella y mostrar su contenido en la vista.


4.1.1. Configuración del acceso a BBDD

La configuración de la base de datos se encuentra en el fichero application>config>database.php, donde debes modificar algunos parámetros, como el nombre de la base de datos, el usuario y la contraseña.

$db['default'] = array(
	'dsn'	=> '',
	'hostname' => '127.0.0.1',
	'username' => 'root',
	'password' => '',
	'database' => 'miproyecto',
	'dbdriver' => 'mysqli',

4.1.2. Acceso a datos

A continuación, crea una clase model que extienda de CI_Model en application>models. Para este ejemplo solo necesitarás una función que seleccione todo el contenido de la tabla “Usuarios”.

modelo CodeIgniter ejemplo

Como ves, CodeIgniter establece la conexión con la base de datos por ti, y solamente tienes que indicarle a la variable “db” el método que necesites y el nombre de la tabla a la que quieras acceder.

Una vez obtengas el resultado de tu query, utiliza la función result_array() para transformarlo en un array y hacerlo más manejable. Tu función debe devolver dicho array para que pueda ser recogida por el controlador.


4.2. Controlador

Crea un archivo Usuarios.php en application>controllers que contenga una clase del mismo nombre y que extienda CI_Controller. Escribe la siguiente función:

controller CodeIgniter ejemplo

La primera línea carga el modelo que hemos creado previamente. El segundo argumento es un alias para agilizar la escritura de código, y el tercero indica que quieres que efectúe la conexión con la base de datos automáticamente.

La segunda línea llama a la función getAll() sobre el alias del modelo, y almacena el resultado en la variable $datos[‘Usuarios’].

Por último, carga la vista (que aún no has definido), que mostrará los datos y, para ello, le pasamos éstos como argumento.


4.3. Vista

Crea un último archivo usuarioSaludo.php en application>views. En el paso anterior has pasado a esta vista la variable $datos, de modo que lo único que tienes que hacer es acceder a ella a través de las claves de dicho array (en este caso la clave es ‘Usuarios’).

Como la consulta del modelo ha devuelto todas las filas de la tabla, selecciona el elemento 0 para que solo muestre el primer usuario. Si quisieras mostrarlos todos, podrías recorrerlos con un bucle. Indica también el nombre del campo de la tabla que necesites.

vista CodeIgniter ejemplo

5. Despliegue

Tu aplicación ya está lista para ver el resultado. Para ello solo debes abrir tu navegador y escribir la URL Base, seguida del nombre de tu controlador y el nombre de la función que quieras llamar.

despliegue

6. Conclusiones

CodeIgniter es un framework ligero, sencillo e intuitivo que permite crear aplicaciones PHP rápidamente. Al trabajar con él, se parte de una estructura predefinida que agiliza la tarea del programador, haciendo muy fácil el acceso a datos y el manejo de éstos.


7. Referencias

Administración de Elasticsearch

$
0
0

Aunque el marco apropiado para profundizar en Elasticsearch sería un curso completo, en este tutorial se intentará entrar lo máximo posible en las cuestiones más importantes del caso de uso planteado en el primer tutorial de esta serie y que consiste en recoger, almacenar y explotar logs de aplicaciones.
Este tutorial ocupa la tercera posición; aquí está el segundo.

Índice de contenidos


1. Introducción

Elasticsearch es una base de datos distribuida totalmente enfocada a búsquedas sobre documentos estructurados.
Su capacidad de distribución la hace altamente escalable y tolerante a fallos, siendo además muy sencillo gestionar un cluster de servidores.
La potencia de las búsquedas está apoyada por un lado por un completísimo API REST y por otro por el motor Apache Lucene.

En este tutorial veremos cómo instalar, configurar y administrar un cluster de Elasticsearch.
Al mismo tiempo iremos repasando los conceptos más importantes de esta plataforma.


2. Entorno

Este tutorial utiliza el mismo entorno que la introducción a ELK.


3. Instalación y configuración


3.1. Con Ansible

Los ejemplos mostrados aquí utilizarán el entorno virtual montado con Vagrant y Ansible para el primer tutorial de la serie.

Según esto, la configuración de Elasticsearch se basará en los valores de un diccionario llamado elasticsearch definido en las variables del grupo de servidores stats, que es el grupo donde se instala Elasticsearch.
Actualmente el contenido de ese fichero es el siguiente:

ansible/environments/tutorial/group_vars/stats (fragmento)
# ElasticSearch

  elasticsearch:
    version: 2.2.0
    cluster_name: "tutorial-stats"
    index_number_of_shards: 1
    index_number_of_replicas: 1
    minimum_master_nodes: 1
    http_host: "0.0.0.0"
    network_host: "0.0.0.0"
    http_publish_host: "10.0.2.2"
    http_publish_port: "{{ es_http_port }}"
    transport_publish_host: "10.0.2.2"
    transport_publish_port: "{{ es_transport_port }}"
    unicast_nodes: "{{ es_unicast_nodes }}"
    plugins:
      - { name: "kopf", path: "lmenezes/elasticsearch-kopf/2.0" }

Estos valores sobreescriben los valores por defecto asignados dentro del rol de Ansible elasticsearch, que están definidos en el fichero siguiente:

ansible/roles/elasticsearch/defaults/main.yml
---

  # Define custom settings in a dictionary called "elasticsearch".

  _es_default:
    version: 2.2.0
    user: elasticsearch
    group: elasticsearch
    nofile: 32000
    cluster_name: ""
    node_name: "{{ ansible_fqdn }}"
    is_node_master: yes
    is_node_data: yes
    index_number_of_shards: 5
    index_number_of_replicas: 1
    bootstrap_mlockall: yes
    http_host: _local_
    network_host: _local_
    http_publish_host: ""
    http_publish_port: 0
    transport_publish_host: ""
    transport_publish_port: 0

    unicast_nodes: []
    minimum_master_nodes: 1

    java_opts: ""
    # Taken from rack-roles elasticsearch:
    #   Use 40% of memory for heap, ES will also use large amount of
    #   direct memory allocation, hopefully ending with a total around 50%
    #   of the whole available system memory.
    java_heap_size: "{{ (ansible_memtotal_mb * 0.4) | round | int }}m"

    path_conf: /etc/elasticsearch
    path_data: /var/lib/elasticsearch
    path_home: /usr/share/elasticsearch
    path_logs: /var/log/elasticsearch
    path_plugins: /usr/share/elasticsearch/plugins
    plugins:
      - { name: head, path: mobz/elasticsearch-head}

  # Combines default and custom settings.
  _es_combined: "{{ _es_default | combine(elasticsearch) }}"

Cuando se cambie algún valor se debe forzar el provisionamiento de la máquina virtual donde se ejecuta Elasticsearch:

Terminal
$ vagrant provision statsserver

3.2. Instalación manual

La instalación manual de Elasticsearch es muy sencilla gracias a los repositorios para Debian y RedHat disponibles en la web oficial.
Se requiere una versión moderna de Java 7 o Java 8 hasta el punto de que la aplicación no se ejecutará si detecta una versión con problemas conocidos.

Por otro lado, la configuración del servidor está repartida en los ficheros siguientes:

  • /etc/default/elasticsearch contiene las opciones de arranque del servicio en Debian y derivados.
  • /etc/sysconfig/elasticsearch se utilizará para configurar el arranque del demonio en RedHat y derivados.
  • /etc/elasticsearch/elasticsearch.yml configura el comportamiento del servicio, principalmente cuestiones de clustering y comunicaciones. Es necesario ser root para entrar a este directorio.
  • /etc/elasticsearch/logging.yml sirve para personalizar el sistema de logs de Elasticsearch. Hay que ser root incluso para poder verlo.

3.3. Ajustes de configuración

Elasticsearch es tan flexible que dispone de innumerables opciones de configuración repartidas a lo largo de los módulos listados en la documentación de referencia.


3.3.1. Cluster

El cluster es la unidad de servicio en Elasticsearch, es decir, es una base de datos completa a la que se conectan los clientes para almacenar documentos y realizar búsquedas.

Lo mínimo recomendable es asignar un nombre al cluster y a cada nodo para poder identificarlos fácilmente durante las tareas de administración.
Obviamente, todos los nodos del cluster deben usar el mismo nombre de cluster y diferente nombre de nodo.

También es muy recomendable fijar otros parámetros con un gran impacto sobre los recursos del nodo, pero para saber cómo hacerlo a continuación explicaré algunos conceptos fundamentales.

Un cluster está estructurado a lo largo de dos ejes que podemos llamar “lógico” y “físico”.


3.3.2. Índices

El eje lógico está representado por los índices, que no son más que particiones lógicas de los datos basadas en los criterios de las aplicaciones que almacenan la información.
Un mismo dato (registro, documento, objeto o como nos apetezca llamarlo) no puede existir en dos índices en el sentido de identidad; por supuesto sí que pueden existir dos documentos iguales en contenido.

En esta serie de tutoriales, Logstash reparte los mensajes de log por meses, es decir, existe un índice por mes.
Criterios válidos para saber cómo repartir los índices son: no mezclar en un índice datos de dominios dispares y no tener índices muy pequeños ni excesivamente grandes (los detalles más abajo).


3.3.3. Nodos

El eje físico son los nodos del cluster, donde cada nodo es una máquina virtual Java ejecutando una instancia del servicio.

Por motivos de rendimiento de la JVM, el heap no debe superar el 40% de la RAM del servidor con un límite “hard” de 32 GB que en la práctica queda reducido a unas 24 GB.
Es por esto que en servidores con más de 32 GB de memoria se ejecutan dos o más instancias de Elasticsearch teniendo en cuenta que bajo ningún concepto deben compartir los directorios de configuración, datos y logs.

Elasticsearch distingue varios tipos de nodos:

  • master, tienen como responsabilidad gestionar el cluster y asegurar su integridad.
    Para que un cluster pueda funcionar debe tener al menos minimum_master_nodes nodos de este tipo (por defecto 1).
  • master-eligible son nodos candidatos a ser maestro en caso de que sea necesario, por ejemplo si se cae algún maestro activo o el cluster está inicializándose y aún no se han seleccionado qué nodos van a actuar como maestros.
  • data, son los nodos normales que contienen los datos y ejecutan las búsquedas.
  • client es un nodo que ni es maestro ni contiene datos, así que su única función es enrutar peticiones dentro del cluster y como mucho agregar datos de consultas distribuidas.
  • tribe, cumplen una función de fachada, agregando varios clusters de manera transparente.
Los tipos de nodo master-eligible, data y client se consiguen combinando los parámetros de configuración node.master (is_node_master en Ansible) y node.data (is_node_data en Ansible).
Los nodos tribe requieren configuración especial.

En el capítulo 5 vamos a añadir un segundo nodo al cluster básico del ejemplo de los tutoriales.


3.3.4. Shards

Las shards es donde realmente se realizan las operaciones de indexación y búsqueda en Elasticsearch y es la unidad de distribución de trabajo en el cluster.
Internamente es una instancia de Apache Lucene con sus datos, metadatos e índices (no confundir con el concepto de índice de Elasticesarch).
Cuando se busca, Elasticsearch lanza la consulta en todas las instancias de Lucene implicadas y después combina los resultados y puntuaciones.

Cada índice tiene un número fijo y predeterminado de shards primarias, que son las fuentes que contienen la información almacenada e indexada en Elasticsearch.
La única manera de añadir o quitar shards primarias es recrear el índice (reindexar), que puede ser bastante costoso.

Otra cosa son las shards de respaldo: copias que se distribuyen por los nodos del cluster para conseguir mayor rendimiento, alta disponibilidad y backup y que sí se pueden añadir y quitar en cualquier momento.

Entonces ¿cuántas shards primarias por nodo se deben tener?
Esto depende mucho de cómo responda el servidor ante la carga de trabajo que se le va a dar, así que la recomendación es hacer pruebas de carga y rendimiento con un conjunto de datos representativo (hablamos de gigas) en un índice con una shard.
Esto nos dirá cuántas shards primarias puede soportar el nodo y el tamaño de una shard (número de documentos indexados) antes de que el servidor muera o el rendimiento caiga por debajo de lo que se considere aceptable.

A falta de pruebas que aporten datos realistas, hay que tener en cuenta que la configuración óptima es una shard primaria por nodo e índice. Sin embargo esto depende mucho de:

  • El crecimiento esperado de los datos y concretamente cuántos índices van a existir.
  • La potencia de los equipos en los que se ejecutan los nodos, en concreto cuánta memoria y cuántos núcleos tienen.
  • Y el número de usuarios que van a explotar el sistema de manera concurrente y con qué tipo de consultas.
Así que si esto tampoco está muy claro, la recomendación es simplemente dos shards primarias por nodo.
Esto permitirá en el futuro duplicar el tamaño del cluster sin necesidad de reindexar.

4. Mappings

Elasticsearch con Lucene por debajo estará encantado de indexar cualquier tipo de documento con la configuración por defecto, que básicamente consiste en tokenizar los textos e intentar detectar otros valores primitivos como los números enteros.

En los casos más simples esto será suficiente, pero lo normal y más conveniente es especificar la estructura de los registros que se van a almacenar y cómo queremos que Elasticsearch indexe los valores.
Por ejemplo, en el caso de uso de este tutorial, Logstash hace dos cosas (la primera automática y la segunda manual):

  • Por cada campo del registro, genera un subcampo con el sufijo raw que Lucene no debe analizar y sobre el que sólo se pueden buscar cadenas exactas.
  • El valor del campo @timestamp es una fecha por el que se pueden hacer ordenaciones y filtrados por intervalos.

4.1. Mapping como tipo de documento

Cada documento de Elasticsearch tiene asociado un tipo, una idea equivalente a la de tabla en bases de datos relacionales.
El tipo se define a través de su mapping, el cual determina la estructura del documento (campos), el tipo de valores de los campos y el modo en que deben indexarse esos valores de cara a posteriores búsquedas (o sea, el analizador que controla la tokenización, stemming y otros procesamientos del texto).

La mayoría de APIs REST de Elasticsearch pueden seleccionar los documentos de un tipo determinado sin más que indicarlo en la segunda posición del path de la URL (la primera posición es para el índice).
Por ejemplo, en la base de datos de estos tutoriales, el tipo de los documentos es java, así que para obtener los mensajes de log Java de Abril de 2016 podríamos hacer la siguiente llamada REST GET (recordar que los índices siguen el patrón de nombrado logstash-YYYY.MM):
elk-img20

Por otro lado, se puede utilizar el plugin Kopf para ver los mappings de un índice haciendo clic sobre él y seleccionando la opción show mappings. Por ejemplo, este es el mapping de los mensajes de log de tipo java creados por Logstash:
elk-img21


4.2. Definición de mappings

Es importante definir los mappings aceptados por un índice antes de almacenar en ellos el primer documento.
Si el cliente no lo hace explícitamente, el propio Elasticsearch creará un mapping dinámico con el comportamiento por defecto que se describió al principio de este capítulo.

Además, una vez se asocia un mapping a un tipo (ya sea de forma manual o dinámica), ya no es posible cambiarlo aunque sí ampliarlo.
La razón es que los cambios seguramente afectarán al modo en que se indexan los datos, requiriendo por tanto recrear los índices de las shards.
Entonces ¿cómo se lleva a cabo un cambio de mapping?
La solución es reindexar los documentos moviéndolos a otro índice donde esté definido el nuevo mapping.

Dada la importancia de asociar mappings a un índice antes de guardar nada en él, Elasticsearch permite definir plantillas de mappings.
La plantilla, llamada template, consta de un patrón de nombre de índice y uno o más mappings asociados a tipos de documentos.
Con esto, cuando algún cliente crea un índice que cumple el patrón, Elasticsearch asocia los mappings al índice de manera automática.

El plugin Kopf incluye una opción para gestionar las templates del cluster en la opción index templates del menú more….


5. Montaje y administración de un cluster

En este capítulo vamos a ampliar el cluster de Elasticsearch utilizado como ejemplo de esta serie de tutoriales (ver el primer tutorial para más información).
Originalmente, sólo existe un nodo de Elasticsearch en el servidor statsserver.
Ahora vamos a añadir otro servidor llamado supportserver exactamente con las mismas características.


5.1. Despliegue del segundo servidor

Como el ejemplo utiliza Vagrant y Ansible para la creación y provisionamiento de las máquinas virtuales, este proceso va a ser muy sencillo:

  1. Editar el fichero Vagrantfile para añadir un tercer servidor a la lista de máquinas virtuales.
    Esto se consigue añadiendo un tercer elemento a cada uno de los arrays servers, ssh_ports, kibana_ports, es_http_ports y es_transport_ports.
    Los valores son respectivamente: “supportserver”, 2225, 5602, 9201 y 9301.
  2. Vagrantfile (fragmento)
    servers = ["appsserver", "statsserver", "supportserver"]
          ssh_ports = [2223, 2224, 2225]
          kibana_ports = [0, 5601, 5602]
          es_http_ports = [0, 9200, 9201]
          es_transport_ports = [0, 9300, 9301]

  3. Editar el fichero ansible/environments/tutorial/inventory para indicar que supportserver debe provisionarse igual que el otro servidor de estadísticas:
  4. ansible/environments/tutorial/inventory
    [apps]
          appsserver
    
          [stats]
          statsserver
          supportserver

  5. Aunque esto no es imprescindible, aprovecharemos para instalar el plugin ElasticHQ para monitorizar los recursos consumidos por Elasticsearch.
    Para ello, añadiremos un nuevo elemento al diccionario plugins de la configuración elasticsearch en el grupo de hosts stats:
  6. ansible/environments/tutorial/group_vars/stats (fragmento)
    ---
    
            # ElasticSearch
    
            elasticsearch:
              ...
              plugins:
                - { name: "kopf", path: "lmenezes/elasticsearch-kopf/2.0" }
                - { name: "hq", path: "royrusso/elasticsearch-HQ" }

  7. Desde una terminal en el directorio elk-tutorial ejecutar los comandos siguientes:
  8. Terminal
    $ vagrant up
          $ vagrant provision


5.2. Monitorización del cluster

Tras el despliegue, los dos nodos deberían sincronizarse rápidamente, y la primera consecuencia es que el estado del cluster pasará de amarillo a verde como se puede ver en la siguiente captura del plugin Kopf:
elk-img16

El código de color en Elasticsearch releja el estado de las shards.
Rojo significa que al menos una shard primaria no está disponible; amarillo que alguna shard de respaldo no está asignada a ningún nodo y verde que todas las shards están activas.

También es interesante fijarse en las estrellas a la izquierda del nombre de los nodos.
Una estrella sólida significa que ese nodo es el master y una estrella hueca que el nodo es master-eligible; si no hay estrella es que el nodo no puede ser elegido master.
Como en este despliegue minimum_master_nodes es 1, sólo uno de los nodos elegibles es maestro.

Bajo los iconos de estrella también aparece uno de un disco duro que significa que ese nodo puede almacenar datos, es decir, es un nodo data.

Aparte de esto, en la dirección http://localhost:9200/_plugin/hq/#cluster podemos acceder a ElasticHQ para monitorizar los recursos consumidos por el cluster y los nodos.
Pulsar el botón Connect para cargar la información:
elk-img17

Se puede entrar a ver el estado de un servidor concreto pulsando sobre el botón con su nombre, por ejemplo statsserver:
elk-img18

Pero lo más interesante son los indicadores del cluster, a los que se llega pulsando el botón Node diagnostics:
elk-img19

ElasticHQ tiene una serie de umbrales preestablecidos para estos indicadores que nos permiten averiguar de un vistazo la salud general del cluster a partir de los colores verde, amarillo y rojo.

Los problemas pueden venir por diferentes frentes:

  • Si se están indexando muchos documentos Lucene puede no tener tiempo de compactar y mezclar sus índices.
    Esto provoca “throttling”, es decir, una disminución de la velocidad de almacenamiento de los documentos que influye mucho en el tiempo de ejecución de las consultas porque los índices no están optimizados.
    Indicadores a vigilar: Documents Deleted, Indexing – Index e Indexing – Delete.
  • Cuando las consultas implican muchos datos y transformaciones, Elasticsearch puede llegar a consumir enormes cantidades de memoria y CPU, pudiendo llegar a provocar la caída del servicio.
    Los indicadores que dan pistas sobre esta situación son: Search – Query, Search – Fetch, Field Evictions y Filter Evictions.

6. Conclusiones

No hay recetas mágicas para la administración de un servidor Elasticsearch y la recomendación general pasa por evaluar las necesidades en función del uso, a ser posible con un entorno de pruebas realista.
Si esto no es posible, entonces dimensionar el cluster pensando en el futuro y monitorizarlo continuamente para descubrir desviaciones y comportamientos indeseables lo antes posible.

Lo normal es que antes de caerse, el servicio dé señales de fatiga, por ejemplo con una bajada importante del rendimiento.
Si no estamos atentos, al final puede que nos encontremos con una caída que quedará registrada en el syslog del servidor como puede verse aquí:

/var/log/syslog (fragmento)
Apr  6 11:42:04 vagrant-ubuntu-trusty-64 kernel: [31483.385167] Out of memory: Kill process 6321 (java) score 524 or sacrifice child
  Apr  6 11:42:04 vagrant-ubuntu-trusty-64 kernel: [31483.385503] Killed process 6321 (java) total-vm:3306088kB, anon-rss:1051848kB, file-rss:20928kB

Otra cuestión pendiente es la seguridad, que no es que haya sido ignorada aquí sino que es tan importante que en sí misma daría materia para otro tutorial (tomo nota mental para el futuro).
Básicamente tendríamos dos opciones:

  • Autenticación externa mediante un proxy inverso Apache o Nginx con posibilidad de autorizar según patrones de URLs.
  • O uno de los varios plugins de autenticación y autorización disponibles, incluyendo Shield que es el producto que vende Elastic.
Recordar que si no se adopta ninguna medida, cualquier cliente puede acceder a Elasticsearch a través de su API REST sin ningún tipo de restricción.

Por último recordar que el próximo y último tutorial de la serie abordará la explotación de datos con Kibana.


7. Referencias

Análisis de logs con Kibana

$
0
0

Llega por fin el último tutorial de una serie que ha explorado con bastante detalle las características y posibilidades de la plataforma ELK (Elasticserach, Logstash y Kibana).
En este caso se trata de ver cómo utilizar Kibana para analizar los logs de la aplicación Spring Boot ejecutada como parte del primer tutorial.
Anteriormente se explicó cómo administrar Elastisearch.

Índice de contenidos


1. Introducción

Kibana es una aplicación web que facilita la explotación visual de información almacenada en una base de datos Elasticsearch.

Aunque su popularidad está vinculada a la de Logstash como sistema de recogida y análisis de logs, lo cierto es que Kibana es de propósito general y puede representar cualquier conjunto de datos con tablas y gráficas bastante ricas.

Después de este tutorial conocerás los elementos con los que trabaja Kibana y serás capaz de componer un dashboard con la complejidad y riqueza que desees.
El ejemplo utilizado perseguirá el objetivo de extraer la información más útil e interesante de los mensajes de log de una aplicación Spring Boot y presentarla de una manera intuitiva sin perder detalle.


2. Entorno

Los detalles del entorno se pueden consultar en la introducción a la plataforma ELK.


3. Instalación y configuración


3.1. Con Ansible

Siguiendo con el ejemplo de la serie de tutoriales, la instalación y configuración de Kibana forma parte del entorno virtual montado con Vagrant y Ansible.

El rol de Ansible responsable de Kibana se llama kibana como no podía ser de otra manera.
Sus opciones de configuración son tan simples que realmente no es necesario tocar nada excepto cuando Elasticsearch esté desplegado en un servidor separado, ya que la dirección por defecto es http://localhost:9200.
En ese caso, la URL deberá indicarse en la propiedad elasticsearch del diccionario kibana en el fichero ansible/environments/tutorial/group_vars/stats, por ejemplo:

ansible/environments/tutorial/group_vars/stats (fragmento)
# Kibana

  kibana:
    elasticsearch: "http://elasticsearch-server:9200"
    version: 4.4
    plugins:
      - {name: kibana/timelion, shortname: timelion }

Después de realizar cualquier cambio hay que decirle a Vagrant que vuelva a provisionar la máquina virtual:

Terminal
$ vagrant provision statsserver

3.2. Instalación manual

La empresa detrás de Kibana publica en esta página repositorios para las distribuciones Linux más populares.
La aplicación es autocontenida y no depende de ningún paquete externo (internamente corre sobre Node.js).

Para configurar Kibana manualmente simplemente hay que editar el fichero /opt/kibana/config/kibana.yml y reiniciar el servicio.
Los ajustes de configuración están bien documentados en el mismo fichero y no es necesario entrar en detalle aquí.


3.3. Acceso y configuración

La aplicación web está publicada por defecto en el puerto 5601, así que para acceder al entorno virtual montado en esta serie de tutoriales simplemente habrá que apuntar el navegador a http://localhost:5601.

Un inciso: si Kibana se queda aparentemente colgado cargando datos, hay que entrar al servidor, eliminar el directorio /opt/kibana/optimize/bundles y reiniciar el servicio. En el caso de Ubuntu los comandos serían estos:

Terminal
$ sudo rm /opt/kibana/optimize/bundles
  $ sudo service kibana restart

Según se vio en el primer tutorial, la primera vez que se entra a Kibana nos pedirá definir sobre qué datos de Elasticsearch queremos hacer las consultas.
Esto se hace creando uno o más patrones de índices (index patterns), que en el caso que nos ocupa es simplemente el valor por defecto: logstash-*
Además, debemos marcar el checkbox que indica que los datos consisten en eventos ordenados en el tiempo y seleccionar el campo que contiene la marca de tiempo de cada documento (@timestamp en este ejemplo).

En cualquier momento se puede volver a esta pantalla de configuración a través de la pestaña Indices de la opción de menú Settings:
elk-img22

Para cada patrón de índice se mostrarán los campos conocidos de los documentos almacenados.
Si los campos listados no se corresponden con los datos reales será necesario pulsar el botón amarillo de recarga.
Esto ocurrirá si en algún momento cambia la estructura de los documentos indexados, es decir, si se añaden o modifican campos.

Como se verá más adelante, la otra pestaña de configuración interesante es Objects; el resto no vale la pena tocarlas.


4. Explotación de datos

El menú Discover de Kibana da acceso a la consola de ejecución de consultas sobre Elasticsearch, cuyos elementos principales son los siguientes:
elk-img23

En primer lugar debemos ajustar el intervalo de tiempo que nos interesa haciendo clic sobre el botón de la ventana temporal en la esquina superior derecha. El intervalo se puede establecer de manera rápida a partir de una lista predefinida, de manera relativa o de manera absoluta.

Después introduciremos un filtro en el formulario de búsqueda para extraer los documentos que cumplan esa condición de filtrado (más sobre esto en el siguiente apartado).

El panel de campos y el propio listado de resultados son interactivos y nos permiten inspeccionar los datos con más detalle, seleccionar las columnas que nos interesan y/o aplicar filtros adicionales.

Kibana también nos ofrece guardar y recuperar búsquedas mediante los iconos del panel de herramientas.


4.1. Expresiones de búsqueda

La expresión de búsqueda se pasa tal cual a Elasticsearch que a su vez delega en Apache Lucene.
Estas expresiones consisten en cadenas de texto y/o filtros de campos que se pueden encadenar con los operadores lógicos AND, OR y NOT en mayúsculas o sus alias &&, || y ! respectivamente. También se pueden usar paréntesis para cambiar la precedencia por defecto.

Los filtros de campos tienen el formato básico: campo:condición
donde campo es el nombre del campo a filtrar y condición es el criterio de filtrado que se quiere aplicar.
Aquí van unos cuantos ejemplos representativos:

  • amount:>100, aquellos documentos cuyo campo amount es mayor que 100.
  • level:WARN, el campo level es igual a la cadena WARN.
  • quality:[0.8 TO 1], el valor de quality está dentro del intervalo 0.8 a 1, ambos incluidos.
  • quality:{0.8 TO 1}, igual que el anterior pero el intervalo es abierto, o sea excluyendo los extremos.
  • level:(WARN OR ERROR), el campo level tiene el valor WARN o ERROR.
  • class:*LogItemProcessor, el valor de class termina con la cadena LogItemProcessor.
    El asterisco se puede poner en cualquier parte de la cadena.
  • host:appssserver?, coge los documentos cuyo host es appsserver seguido de algún otro caracter, por ejemplo appsserver1 o appsserver2.

Un último ejemplo que combina lo dicho, para buscar los mensajes problemáticos generados por la clase LogItemProcessor:

class:*LogItemProcessor AND level:(WARN OR ERROR)

4.2. Análisis de datos crudos

Kibana también ofrece algunas opciones para analizar los resultados, entre las que destacan la agregación por un campo, la selección de columnas y el filtrado rápido por valores concretos.

Por ejemplo, si queremos saber cómo se reparten los mensajes de log de nivel ERROR o WARN entre las clases de nuestra aplicación haríamos lo siguiente:

  1. Introducir la expresión de búsqueda: level:(WARN OR ERROR)
  2. En el panel de la izquierda titulado Available fields, seleccionar el campo class para desplegar una vista rápida del reparto de clases que han generado mensajes de esos niveles.
Se mostrará algo parecido a esto:
elk-img24

Además, si hacemos clic sobre el pequeño botón add que aparece a la derecha del campo class en el panel de Available fields, el listado mostrará únicamente esta columna.


5. Gráficos y dashboards

La presentación visual de información en Kibana se basa en gráficos (visualization) agrupados en paneles que componen un cuadro de mandos o dashboard.

El primer paso antes de ponernos a montar un dashboard es definir qué información debe mostrarse y en qué formato.
El objetivo es conocer lo que interesa de un vistazo rápido y poder indagar en los detalles si es necesario.

Una vez tenemos claro a dónde queremos llegar, desde la pantalla Discover hay que componer los filtros de búsqueda con los que recuperar los documentos necesarios.
La búsqueda se guardará con un nombre para usarla en el paso siguiente.

A continuación se diseñarán los gráficos desde la pantalla Visualize para guardarlos con un nombre que facilite añadirlos al dashboard más adelante.

Por último, desde la pantalla Dashboard iremos añadiendo las visualizaciones y colocándolas según nos interese.
El propio dashboard se guardará para poder consultarlo en cualquier momento.


5.1. Un caso práctico

A lo largo de este capítulo vamos a montar un dashboard con los eventos de log de la aplicación Spring Boot del primer tutorial.
Para tener una serie de datos decente para visualizar he ejecutado la aplicación durante 1000 segundos y después he fijado la ventana temporal de Kibana al intervalo durante el que ha ocurrido esa ejecución.

La información más relevante que deseamos conocer acerca de estos eventos de log es la siguiente:

  • Cuántos eventos se generan a lo largo del tiempo y qué nivel tienen (cómo de críticos son).
  • Ver fácilmente qué clases Java están generando mensajes de log problemáticos (WARN y ERROR).
  • Ver información detallada sobre el número de eventos de cada nivel que produce cada clase Java.

De estas tres informaciones, sólo la segunda trabaja con un subconjunto de documentos, en concreto, con los mensajes cuyo level es WARN o ERROR.
Así, hay que ir a la pantalla Discover e introducir el filtro level:(WARN OR ERROR) y guardarlo con un nombre representativo:
elk-img25


5.2. Gráficos

Los gráficos se definen en la pantalla Visualize.


5.2.1. Funciones de agregación

Todos ellos trabajan con funciones de agregación de Elasticsearch, así que antes de continuar hay que hacer una pequeña introducción a ellas.
Para lo que nos interesa, existen dos tipos de agregraciones en Elasticsearch: metrics y buckets.

Las metrics calculan uno o varios valores a partir de un campo del conjunto de documentos seleccionado.
Es decir, el resultado de una metric suele ser un número y entre ellas están las habituales count, sum, max, min, avg, etc.

Las agregaciones bucket sirven para seleccionar y agrupar documentos de muchas maneras y es un concepto similar a la cláusula GROUP BY de SQL.
Un bucket funciona tomando los documentos seleccionados y aplicando un filtrado, agrupación o clasificación según algún criterio. Por lo tanto, su resultado es uno o más conjuntos de documentos.
Lo más interesante es que a ese resultado se le puede aplicar a su vez otras agregaciones, ya sean de tipo bucket o metric.

Ahora podemos continuar con los gráficos diciendo que todos aquellos que representan datos bidimensionales utilizan buckets para el eje X y metrics en el eje Y.
Además, podemos representar varios conjuntos de datos en la misma gráfica utilizando buckets dentro de buckets.

La tarta por su parte consta de un bucket que determina cuáles son las porciones y una metric para fijar el tamaño de cada porción.


5.2.2. El histograma múltiple

Esto se verá más claro con la primera gráfica del caso práctico: un histograma con todos los eventos de log distinguiendo su nivel.

Pulsar el botón New Visualization en la barra de herramientas para crear un nuevo gráfico, seleccionar Area chart y pulsar From a new search puesto que nos interesa trabajar con todos los datos.

En el panel de la izquierda se puede ver que el eje Y por defecto ya tiene una metric count, así que no hay que hacer nada.

El eje X debe ser un histograma, así que añadiremos un bucket de tipo X-Axis con una agregación Date histogram.
Si ejecutamos la gráfica pulsando el botón verde de la parte superior del panel aparecerá la gráfica de histograma sin distinguir niveles:
elk-img26

Para separar por niveles hay que añadir un sub-bucket Split Area que visualiza varios grupos en la misma área.
La función de agregación será terms sobre el campo level.raw (siempre se deben utilizar los subcampos raw):
elk-img27

Al pulsar el botón verde se podrá ver el resultado esperado, así que ya sólo falta grabar la gráfica pulsando el botón Save Visualization en la barra de herramientas.


5.2.3. La tarta de problemas

Seguiremos unos pasos similares para definir una gráfica de tarta con las clases que más problemas han generado.

Ahora, tras pulsar el botón New Visualization, hay que seleccionar Pie chart y a continuación From a saved search ya que se van a tener en cuenta solamente los eventos devueltos por la consulta Problematic events.

De nuevo, Kibana supone que lo que vamos a hacer es contar cosas, así que la metric por defecto es count.

En cuanto a los grupos de documentos, el bucket debe ser un Split Slices porque queremos todo sobre la misma tarta.
La función de agregación será terms sobre el campo class.raw, es decir, queremos un grupo por cada clase Java.
Antes de terminar hay que cambiar el parámetro Size a un valor muy grande (1000 por ejemplo) porque por defecto solamente se muestran los 5 grupos más grandes.

Pulsar el botón verde para ver la gráfica y guardarla con el botón Save Visualization de la barra de herramientas:
elk-img28


5.2.4. Las barras conflictivas

El dashboard ofrecerá una vista alternativa en forma de diagrama de barras de las clases más problemáticas.

El proceso para definir esta gráfica es casi idéntico al del diagrama de tarta, excepto que el tipo de visualización es Vertical bar chart lógicamente.
Recordar que el conjunto de datos de partida es la consulta Problematic events.

Aquí el eje X consistirá en un bucket de tipo X-Axis con agregación terms sobre el campo class.raw.
También hay que cambiar el número máximo de elementos a algo muy grande (como 1000) para que muestre todos los datos.

Ya solamente falta pulsar el botón verde para actualizar la gráfica y pulsar el botón Save Visualization de la barra de herramientas para guardarla con un nombre representativo:
elk-img29


5.2.5. La tabla de detalles

La última visualización es una tabla que muestra el número de mensajes de cada nivel producidos por cada clase Java, así que tras pulsar New Visualization en la barra de herramientas, seleccionaremos Data table.
Como estamos interesados en todos los niveles de log debe elegirse From a new search.

Como siempre, el eje Y está preconfigurado para contar elementos, que es justamente lo que queremos hacer.

En el eje X añadiremos un Split Rows para agrupar por clases Java, así que la agregación bucket será terms sobre el campo class.raw.
Estamos interesados en todas las clases, por lo que el parámetro Size debe ser un número alto como 1000.

También queremos romper los datos por nivel del mensaje. Para conseguirlo hay que añadir un sub-bucket de tipo Split Rows con función de agregación terms del campo level.raw.

Tras pulsar el botón verde para actualizar la gráfica, se mostrará una tabla con tres columnas: clase Java, nivel y número de elementos.
Ya solamente falta guardarla con el botón Save Visualization de la barra de herramientas:
elk-img30


5.3. Dashboards

Él último paso del proceso es juntar todas las gráficas en un bonito dashboard.
Esto se hará desde la pantalla Dashboard y pulsando sobre el botón New Dashboard en la barra de herramientas.

El propio Kibana nos sugiere ir pulsando el botón + (Add Visualization) para ir añadiendo gráficas, así que ¡adelante!
Los paneles de cada gráfica se pueden mover y redimensionar a nuestro gusto.
Tras algunos experimentos yo me quedo con esta distribución bastante clásica:
elk-img31

No olvides guardar tu obra de arte con un nombre interesante.


6. Conclusiones

Kibana es una herramienta muy flexible y con bastantes posibilidades, pero no hay que perder de vista que su utilidad real dependerá en gran medida de los datos de partida.
Concretamente, hay que asegurarse que la aplicación genera mensajes de log en los puntos y momentos que realmente aportan información de interés (muchas veces no es así) y también que cada mensaje lleva asociado datos que nos permitan agrupar, clasificar y filtrar los mensajes para hacernos una idea clara de qué está pasando (por ejemplo, identificar la sesión del usuario).

Por otro lado, como es lógico este tutorial no ha profundizado en otros aspectos interesantes de esta aplicación como el plugin Timelion para representación de series temporales.
Y no olvidar que para poder aprovechar Timelion y el propio Kibana al máximo hay que dominar los lenguajes de consultas de Elasticsearch y Apache Lucene.
En el apartado de referencias se dejan un par de enlaces sobre estos temas.


7. Referencias

Primeros pasos con Concordion

$
0
0

En este tutorial nos iniciaremos en el mundo de los tests de aceptación de la mano del framework Concordion.

Índice de contenidos

1. Introducción

Concordion es un framework orientado al desarrollo de tests de aceptación. Funciona a través de tests escritos en HTML o a través de lenguaje de marcado (a partir de la versión 2.0) que a su vez son instrumentalizados con atributos especiales que el framework interpreta para ejecutar dichos tests.

En vez de forzar a que los Product Owners tengan que especificar los requisitos en un lenguaje con una determinada estructura, Concordion permite definirlos utilizando lenguaje natural, pudiendo utilizar párrafos, tablas, y signos de puntuación. Esto hace que las especificaciones sean mucho más legibles y sencillas de escribir, además de ayudar enormemente a la compresión y aceptación de lo que se supone debe de hacer la funcionalidad que vamos a probar.

2. Entorno

El tutorial se ha realizado en el siguiente entorno:

  • MacBook Pro 15″ – 2 GHz Intel Core i7 – 8 GB 1333 MHz DDR3
  • OS X El Capitan v10.11.4
  • Maven 3
  • Java JDK 1.8
  • Eclipse IDE Mars (4.5.0)

3. Preparación del proyecto

Para la realización del tutorial, prepararemos un proyecto básico utilizando el arquetipo “maven-archetype-quickstart” de Maven.

Para ello, accedemos a través de nuestra consola al directorio donde queramos generar nuestro proyecto (típicamente nuestro workspace local), y ejecutamos el siguiente comando.

mvn archetype:generate -DgroupId=com.examples.concordion -DartifactId=helloworld-concordion -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Una vez Maven haya generado el proyecto, abrimos el Eclipse IDE, seleccionamos el workspace correspondiente e importamos el proyecto Maven en nuestro workspace local.

Después de importarlo, añadimos la carpeta “src/test/resources” al proyecto, y a continuación la incluimos en el buildpath del mismo, a través del menú de propiedades del proyecto en Eclipse IDE. Posteriormente, eliminamos los ficheros autogenerados por el arquetipo y quitamos también la dependencia de Junit en el POM.

Por último, modificamos el fichero pom.xml para incluir la dependencia de Concordion.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.examples.concordion</groupId>
	<artifactId>helloworld-concordion</artifactId>
	<packaging>jar</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>helloworld-concordion</name>
	<url>http://maven.apache.org</url>

	<dependencies>
		<dependency>
			<groupId>org.concordion</groupId>
			<artifactId>concordion</artifactId>
			<version>2.0.0</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

Finalizada la preparación, el proyecto deberá de tener este aspecto:

proyecto-importado-vacio

4. Creación de los tests HTML

Después de haber preparado el proyecto, es hora de meterse en harina empezando por la redacción de los tests de aceptación que queremos disponer en nuestro proyecto. En el caso de este tutorial, se va a utilizar el formato HTML para la creación de los mismos (a partir de la versión v2.0 de Concordion, es posible utilizar lenguaje de marcado).

En este tutorial realizaremos dos tests:

  • Ejemplo básico en donde el test es un pequeño párrafo de HTML
  • Ejemplo con uso de tablas, donde cada fila de la tabla servirá como dato de entrada para generar una salida determinada.

4.1. Creación y preparación de la estructura básica HTML

Para preparar nuestros tests HTML, primero debemos crear nuestro fichero en la ruta ‘src/test/resources’, colgando del paquete correspondiente en donde posteriormente ubicaremos nuestro fixture de instrumentalización (escrito en Java).

Creamos el fichero HTML ‘src/test/resources/com/examples/concordion/HelloWorld.html’. A continuación le damos la estructura básica de HTML, incluyendo el namespace de Concordion, para poder utilizar la funcionalidad del framework en la creación de nuestros tests de aceptación.

<html xmlns:concordion="http://www.concordion.org/2007/concordion">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	</head>
	<body>

		<h1>Primeros pasos con Concordion</h1>

		<h3>Ejemplo básico</h3>

		<!-- Aquí irá el ejemplo básico -->

		<h3>Ejemplo con tabla</h3>

		<!-- Aquí irá el ejemplo con tabla-->

	</body>
</html>

Una vez dispuesta la estructura básica, pasamos a dotarla de contenido a través de los ejemplos que hemos enumerado anteriormente.

4.2 Contenido del ejemplo básico

En este ejemplo básico, se dispondrá el test para comprobar que el saludo de un hipotético usuario es el correcto.

<p>
Dado una persona llamada <b concordion:set="#userName">Frodo Bolsón</b><br/>
el saludo deberá ser <span concordion:assertEquals="greetings(#userName)">Hola Frodo Bolsón!</span>
</p>

Observad que utilizando el atributo ‘concordion:set=”#userName”‘, vamos a almacenar el contenido del tag ‘b’, en este caso ‘Frodo Bolsón’, en la variable ‘#userName’ de Concordion. Posteriormente, se utiliza el atributo ‘concordion:assertEquals=”greetings(#userName)”‘, para invocar al método “greetings” de nuestro fixture asociado escrito en código Java (explicado en pasos posteriores).

4.3 Contenido del ejemplo con tabla

En este otro ejemplo, nos apoyaremos en el uso de una tabla para poder ser más ágiles a la hora de realizar tests de aceptación que cuya funcionalidad deba ser la misma, pero aplicada esta vez a una lista de elementos; en nuestro caso de nombres de usuario.

<p>
	<table concordion:execute="#greeting = greetings(#userNameInTable)">
		<tr>
			<th concordion:set="#userNameInTable">Nombre Usuario</th>
			<th concordion:assertEquals="#greeting">Saludo del sistema</th>
		</tr>
		<tr>
			<td>Arkady Darell</td>
			<td>Hola Arkady Darell!</td>
		</tr>
		<tr>
			<td>Raistlin Majere</td>
			<td>Hola Raistlin Majere!</td>
		</tr>
		<tr>
			<td>Myca Vykos</td>
			<td>Adiós Myca Vykos!</td>
		</tr>
	</table>
</p>

Utilizando la tabla HTML, declaramos en el tag ‘table’ el atributo ‘concordion:execute=”greeting = greetings(#userNameInTable)”‘, el cual lo que hará será ejecutar por cada una de las filas de la tabla la funcion ‘greetings(..)’, almacenando el resultado en la variable ‘#greeting’.

Posteriormente, a través de la definición de las cabeceras de la tabla, estamos indicando a Concordion qué fila concreta corresponde a qué dato, y qué función de Concordión se debe ejecutar en cada una de ellas.

Como se puede observar, en la primera fila vamos a definir el nombre del usuario a través del atributo ‘concordion:set=”#userNameInTable”‘, y en la segunda fila vamos a evaluar el saludo del sistema esperado a través del atributo ‘concordion:assertEquals=”#greeting”‘.

5. Creación del fixture en Java

Una vez hemos escrito los tests de aceptación que queremos realizar en nuestro tutorial, vamos a realizar el fixture de Concordion que instrumentalizará el contenido de dichos tests de aceptación.

Para ello, creamos el fichero ‘src/test/java/com/examples/concordion/HelloWorld.java’. Daos cuenta de que la paquetería y el nombre del fichero son exactamente los mismos que la paquetería y el nombre del fichero HTML, con la diferencia de que uno cuelga de ‘src/test/java’ y el otro cuelta de ‘src/test/resources’. Este punto es importante, ya que si no seguimos estas directrices, Concordion no va a saber encontrar el fichero de instrumentalización.

En nuestro fixture, incluimos el runner de Concordion, para indicar que dicho test lo ha de ejecutar el framework, y posteriormente incluímos la función “greetings” que habíamos utilizado en nuestros tests HTML.

Como es una buena práctica separar la lógica que subyace al test del propio test, el encargado de generar el saludo del usuario será un pequeño servicio que veremos posteriormente. Esta práctica es capital para mantener unos tests robustos, que no estén acoplados a la implementación.

@RunWith(ConcordionRunner.class)
public class HelloWorld {

	private final GreetingService greetingService = new GreetingService();

	public String greetings(final String userName) {
		return greetingService.greetings(userName);
	}

}

6. Creación del servicio

Finalmente, vamos a crear el servicio que generará el saludo del usuario. Este servicio, al no ser una clase de test, debemos incluirlo dentro del buildpath ‘src/main/java’, como viene siendo habitual en los proyectos Maven. Creamos la clase ‘src/main/java/com/examples/concordion/GreetingService.java’

En este servicio, creamos un método ‘greetings(…)’, el cual utilizará nuestra clase de test (nuestro fixture) para poder generar el saludo.

public class GreetingService {

	public String greetings(final String username){
		final StringBuilder sb = new StringBuilder();
		return sb.append("Hola ").append(username).append("!").toString();
	}

}

7. Puesta en ejecución de los tests de aceptación

Finalizado la codificación de nuestros tests de aceptación junto con sus fixtures, el proyecto debería de tener una pinta parecida a esta:

finalizacion-proyecto

Por último, ya solo resta ejecutar el fixture ‘HelloWorld.java’, utilizando la configuración de JUnit. Tras la ejecución, se presentará en la consola del Eclipse IDE el resumen de los tests ejecutados y la ruta donde se ha generado el informe de Concordion.

Para este ejemplo, se ha dejado un test en estado fallido para visualizar el aspecto de dichos tests.

consola-eclipse

Accedemos al informe HTML de Concordion, y comprobamos el resultado final.

informe-concordion

8. Conclusiones

En este tutorial hemos visto como dar nuestros primeros pasos en el uso de Concordion como framework de tests de aceptación utilizando un ejemplo básico y un ejemplo basado en tablas, utilizando HTML como lenguaje de escritura de los tests.

Puedes echar un vistazo del código fuente completo a través de este enlace a mi repositorio de GitHub: Primeros pasos con Concordion – GitHub

9. Saber Más

Si quieres profundizar en los conceptos de Tests de aceptación o en los atributos de Concordion disponibles, te dejo estos enlaces.

Primeros pasos con Express

$
0
0

En este tutorial vamos a ver una breve introducción a Express, un framework MVC de NodeJS para hacer páginas Web y servicios REST.

0. Índice de Contenidos

1. Introducción

Al comienzo, usar JavaScript en el lado del servidor parecía una excentricidad: un proyecto loco que quería retorcer la tecnología para escribir una aplicación completa, tanto en cliente como en servidor con este lenguaje. Muchos pensábamos que no tenía ninguna viabilidad en el mundo real, pero nos equivocamos de pleno. Con el paso de los años se ha demostrado lo útil e interesante que está siendo la plataforma NodeJS, tanto para la ejecución de scripts en el ordenador como para servir de base para los nuevos servidores.

Y es que Javascript es un lenguaje con unas características que se adaptan bastante bien a las necesidades actuales, sobre todo en cuanto a asincronía, programación funcional y orientada a objetos, lenguaje dinámico y rapidez en el desarrollo, gracias a un prácticamente maduro ecosistema de aplicaciones y herramientas de soporte.

Dentro de las posibilidades que nos brinda NodeJS está la de crear un servidor Web con Javascript. De hecho, dentro de los módulos de NodeJS lleva uno básico incorporado como veremos. Pero si queremos hacer una aplicaicón minimamente organizada, necesitaremos un framework en el que basarnos para no volver a reinventar la rueda.

Como ya sabrás, si hablamos de frameworks para aplicaciones Web, tenemos que hablar del famoso patrón MVC (Modelo-Vista-Controlador), que se ha mostrado hasta ahora como la organización ideal: Struts, Spring MVC, ASP .NET, Code Igniter o Laravel en PHP… Así que NodeJS no podía ser menos y cuenta también con los suyos, donde el más reconocido es Express.

Vamos a ver cómo instalar Express en nuestra máquina Ubuntu (más o menos es lo mismo para otros sistemas operativos) y cómo dar los primero pasos con este framework, entendiendo su estructura y funcionamiento.

2. Instalación

Partimos de un Ubuntu 16.04 recién instalado, asi que hay que cargar algunos programas si no los tienes instalados…

Comenzaremos por el propio NodeJS

sudo apt-get install nodejs -y

Ahora instalamos el gestor de paquetes NPM (Node Package Manager), que nos permitirá instalar aplicaciones basadas en NodeJS

sudo apt-get install npm

Creamos un directorio par albergar nuestra aplicación

mkdir expressAdictos

Lo mejor para comenzar a utilizar Express ya mismo es que empleemos un generador que nos va a crear toda la estructura de directorios básica, con ficheros de demostración. Así, además podremos ver la estructura básica de una aplicación Express y nos ahorraremos un buen trabajo.

Ejecutamos en un terminal para instalar el generador de la versión 4.X:

* Quizá le tengas que dar permisos de administrador porque tiene que acceder a “/usr/local/lib”.

npm install -g express-generator@4

Con el parámetro -g indicamos que sea un comando disponible para todo el sistema en el que lo instalamos.

Ahora vamos a decirle al generador que genere la estructura en el directorio expressAdictos que hemos creado.

express expressAdictos/

Así creará una serie de directorios y ficheros básicos para comenzar a trabajar con Express

1

Pero aún no hemos acabado: accedemos al directorio porque vamos a modificar el fichero package.json. Si vienes del mundo de Java, este fichero es como el fichero pom.xml de Maven: indica dependencias y algunas propiedades del proyecto, como versiones, nombres, comandos…

Vamos dentro del directorio y ejecutamos:

npm init

Mediante unas preguntas podremos configurar versión, repo de Git, licencia, autor… Esta información se une a la ya creada por el generador de express, como son las dependencias o indicación del script de inicio del servidor:

2

Ahora vamos a instalar un par de utilidades básicas e imprescindibles para desarrollar. Sí, estoy hablando de las herramientas para hacer testing. A mí me gusta Mocha y Chai como base, aunque se pueden añadir otras como Jasmine por ejemplo.

Las podemos instalar muy fácilmente gracias a NPM:

npm install chai --save-dev
npm install mocha --save-dev

Con el parámetro –save-dev logramos que, además de instalarlas, queden las dependencias reflejadas en las dependencias de desarrollo. Tambien podríamos haber puesto “-D” que es más corto :). Aquí podemos ver estas dependencias…

"devDependencies": {
	"chai": "^3.5.0",
	"mocha": "^2.4.5"
}

Y ya que estamos, podemos añadir el comando para hacer test al package.json, justo al lado del comando start en el apartado de scripts. Es decir, quedaría algo así:

"scripts": {
	"start": "node ./bin/www",
	"test": "mocha"
},

Con esto, podemos crear un directorio /test donde dejaremos los archivos de test de la aplicación:

mkdir test

Así, cuando escribamos npm test, se ejecutará el comando “mocha”, que buscará los test en el directorio “test” (su comportamiento por defecto).

También, podemos instalar mocha como un comando del sistema (como sucede con express) para que pueda ser invocado desde cualquier lugar de nuestro ordenador. Es muy fácil, otra vez podemos instalar con la opción “-g”

* Puede que necesites emplear de nuevo “sudo” porque tiene que escribir en el directorio “/usr/local/bin”.

sudo npm install mocha -g

3. Arrancando el servidor

Ya deberíamos tener todo lo necesario para acceder a la aplicacion básica que se ha generado con el generador de Express que hemos utilizado. Vamos a iniciar el servidor.

Quizá te estés preguntando dónde está el servidor… no hemos hablado de nada de Apache, ni IIS, ni Tomcat… ¿No es extraño?

Realmente no es tan extraño: lo lleva la propia aplicación incorporada gracias a las librerías que utiliza NodeJS. Simplemente dentro del código de la aplicación se invoca a una librería con el servidor, se configura y se arranca.

Para iniciarlo, tenemos que ejecutar el comando de npm para arrancar un proyecto de NodeJS:

npm start

¿Qué hace esto? Realmente va al package.json y comprueba el comando que está configurado como “start”, que no es otro que:

"start": "node ./bin/www"

Si vamos al directorio “bin” de la aplicación, podremos ver el fichero “www” que es el que se ejecuta. Luego analizaremos en detalle su contenido. Ahora nos quedaremos con las consecuencias, que no son otras que nuestra primera aplicacion en Express funcionando.

Esto es lo que se ve en el navegador si ponemos http://localhost:3000

4

Y esto es lo que se ve en la salida de la consola:

3

Por defecto, emplea la consola desde la que estamos ejecutando como salida, así que si pulsamos CTRL+C para cancelar la ejecución o cerramos la ventana, el servidor se cortará.

¡Ah!, quizá si no te funciona por versiones de librerías tengas que hacer un npm update

npm update

4. Extructura de una aplicación Express

Vamos a ver una pequeña introdución a la estructura de la aplicación para que sepas dónde tienes que meter tu código y entiendas lo básico sobre su funcionamiento.

4.1. El Servidor

Como he indicado antes, el arranque se lleva a cabo en el fichero /bin/www. Sí es un nombre raro “www” pero es el que le han dado. Si hacemos un “cat” sobre el fichero nos llamarán la atención algunas líneas (omito con puntos suspensivos “…” otras instrucciones):

...
var app = require('../app');
var http = require('http');
...
var port = normalizePort(process.env.PORT || '3000');
...
app.set('port', port);
...
var server = http.createServer(app);

Básicamente se encarga de:

  • Cargar los módulos app.js y http. El primero es del propio express y contiene lo necesario para configurar la aplicación y cargar el resto de módulos de express. El módulo http por su parte es el propio servidor
  • Establece una variable “port” a valor 3000 si no se ha indicado otro por entorno. Si cambiamos este 3000 por cualquier otro puerto, es válido (siempre que no esté ocupado, claro).
  • Establece el valor del puerto como una propiedad de la aplicación.
  • Y arranca el servidor http creando el servidor para la aplicación establecida en app.js.
Y ya está… con esto tenemos el servidor funccionando. Lo he congelado con un tag en mi repositorio de Git por si quieres ver cómo estaba en este momento: https://github.com/4lberto/expressAdictos/releases/tag/Start

4.2. El Modelo-Vista-Controlador

Como la mayoría de frameworks para el mundo web, se basa en el patrón Modelo-Vista-Controlador (MVC). Esto nos facilita mucho las cosas si estamos acostumbrados a este patrón: Spring MVC, Struts, AngularJS…:

  • Modelo: las clases de datos que se manejan en la aplicación.
  • Vista: la representación para el usuario. Por ejemplo páginas Web (HTML) o JSON en REST.
  • Controlador: donde se realiza el procesamiento del modelo para dárselo “masticado” a la vista. Contiene la lógica de negocio.

Vamos a ver parte por parte dónde se ubican, aunque mejor en el orden natural de una petición: controlador, modelo y vista.

4.3. Controlador (middleware)

La parte de controlador tiene mucho que ver con el enrutamiento y con lo que en Express llaman middleware: funciones que reciben una entrada (request) y una salida (response). Lo que sucede entre la entrada y la salida es el controlador.

Para que se seleccione una u otra función de middleware hace falta mapear las funciones a una url determinada. Aquí tenemos dos opciones:

  • Mapear al objeto aplicación con app.use(“url”, funciónMiddleware), o con app.[MétodoHTTP] como por ejemplo app.get o app.post;
  • Mapear a un objeto router. Se hace del mismo modo: router.use(“url”, funcionMiddleware) o también con route.[MétodoHTTP] como por ejemplo route.delete o route.post.

La diferencia entre uno y otro método es más bien de organización. Si nos decantamos por app.use (o sus derivados) se mapeará a la aplicación directamente. Si usamos un router, lo hará al router, pero con la ventaja de que este es más específico y podremos, por ejemplo, aplicarle funciones de autentiación.

Si ahora te digo que lo adecuado es mapear un router a un objecto aplicación, igual lo ves más claro:

var myRouter = express.Route();
app.use("/coches", myRouter);
myRouter.use(/carreras, function(.....));

¿Y qué contienen las funciones de middleware? Muy fácil. Vamos a routes/indes.js para ver el ejemplo que viene con el generador

var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
	res.render('index', { title: 'Express' });
});

module.exports = router;

Como puedes intuir, se mapea en la url “/” (raíz) de este router que está en el fichero index.js (sí, se ha separado en un fichero nuevo). Dentro, lo que hace es reenviar con res.render la salida al fichero de vista index.jade, pasándole el modelo para que lo pinte, que es un JSON con atributo “title” con vaor “Express”

¿Dónde se incluye este router index.js? Pues en el fichero app.js de la aplicación principal. Como te he adelantado antes, lo que hace es registrar el router en la aplicación:

...
var routes = require('./routes/index');
...
app.use('/', routes);

Muy fácil: 2 pasos nada más. Con el primero se carga el fichero index.js y se asigna a la variable “routes”. En el segundo paso, asigna a la URl “/” ese router. Entonces cuando accedamos a http://localhost:3000/, nos redirigirá al index.jade con el atributo “title” con valor “Express”. Fácil y sencillo.

Como dice la documentación oficial http://Express.com/es/guide/using-middleware.html, se pueden soportar varios middleware para un solo mapeo de URL. Una vez pasado por uno, se llama al siguiente con “next()”. Un ejemplo que se incluye en la documentación:

app.use('/user/:id', function(req, res, next) {
	console.log('Request URL:', req.originalUrl);
	next();
}, function (req, res, next) {
	console.log('Request Type:', req.method);
	next();
});

Pasa por la primera función de middleware y le pasa la ejecución a la siguiente.

¿Y qué más puedo hacer con las funciones de middlware?

Además de pasar al siguiente con “next()” y reenviar a una vista con “res.send()” como hemos visto antes, puedes enviar al siguiente router que cuadre en la url con “next(‘route’)” (tal cual) o puedes escribir directamente en la respuesta con res.send(‘Mensaje’);

Esto es lo que muestra el ejemplo que viene en el fichero users.js, que está mapeado a la url (‘/users’) en el fichero app.js. Su contenido es:

router.get('/', function(req, res, next) {
	res.send('respond with a resource');
});

Si lo pruebas verás que pone ‘respond with a resource’.

Igual que hemos puesto un texto, podremos devolver un JSON fácilmente, simplemente pasando por parámetro un objeto de Javascript (que para eso es JSON). Asi que hacer un API REST con express es muy sencillo.

Por ejemplo, si tengo un nuevo router que response a “/rest” con este contenido en la función de middleware:

router.get('/', function(req, res, next) {
	var persona={nombre:'4lberto'};
	res.send(persona);
});

La salida, será la representación del objeto persona en JSON, sin hacer nada más (pobre Jackson de Java, no tiene nada que hacer aquí =()

{"nombre":"4lberto"}

¿Y si quiero añadir un controlador nuevo?

Existen varias formas como hemos visto, pero la forma de organizar que nos propone esta plantilla de Express creo que está bien.

Creamos el fichero coches.js en /routes con este contenido:

var express = require('express');
var router = express.Router();

/* GET users listing. */

router.get('/carreras', function(req, res, next) {
	res.send('Response con un coche de carreras');
});

router.get('/:id', function(req, res, next) {
	res.send('Responde con el coche: ' + req.params.id);
});

module.exports = router;

Como verás, he incluído dos URL, cada una con su función de middleware oportuna que escribe por la salida directamente (como un servicio REST) sin redirigir a ninguna vista:

  • La mapeada en “/carreras” escribe un mensaje directamente.
  • La mapeada en “/:id” tiene más complejidad: espera un parámetro en la URL que está identifiado por “:id”. Este valor lo lee con req.params.id, y lo incluye en la salida. Ya puedes imaginar lo fácil que es hacer un servicio REST con Express :).

Enganchamos este nuevo router que está en el fichero coches.js

Fácilmente (que diría el amigo de Matías). Nos vamos a app.js y conectamos el router nuevo con la aplicación.

...
var coches = require('./routes/coches');
...
app.use('/coches', coches);
...

Pero hay una cosa particular ahora: el router está asignado a “/coches” y dentro del router están sus respectivos mapeos a los middleware. La consecuencia es que las URL se sumarán:

  • http://localhost:3000/coches/carreras devolverá “Response con un coche de carreras”.
  • http://localhost:3000/coches/renault devolverá “Response con un coche renault”.

Si quieres descargar el repositorio en este punto lo he tageado como https://github.com/4lberto/expressAdictos/releases/tag/WithCoches.

Quizá la forma más adecuada de organizar ciertas funciones de middleWare y routers, sea empleando ficheros con módulos de NodeJS para facilitar la mantenibilidad. Si no sabes cómo funciona el sistema de módulos de NodeJS, te lo explico en este apartado de otro tutorial. En este otro apartado te explico cómo usar TDD con Mocha y Chai para módulos de NodeJS, que también te valdrá para Express.

4.4. Modelo

Del modelo no voy a decir mucho: se trata de los datos que se pasan a la vista desde el controlador, y se hace en formato de objectos de Javascript: pares key-value anidables, osea JSON.

El modelo se puede obtener desde cualquier fuente que pueda emplearse en otro lenguaje:

  • El request de entrada.
  • Una base de datos.
  • Ficheros locales.
  • Otros servicios publicados en la nube.

El modelo, una vez construído, irá indefectiblemente en la llamada a la vista desde la función de middleware o controlador.

4.5. Vista

Es lo que ve el consumidor del servicio web que creamos o de la página Web. Por principio del MVC es la parte “menos inteligente”, ya que no tiene que realizar ningún procesamiento, simplemente pintar de forma adecuada la información que le pasa el modelo cuando le redirige la salida.

En el generador de Express que estamos utilizando en este tutorial, las vistas están ubicadas en el directorio “views”. Por defecto vienen dos ficheros: index.jade y error.jad.

¿Cómo indica el controlador que tiene que redirigir a una vista? En el código del controlador, concretamente en el que enlaza la URL del servidor con una función de middleware, se indica el nombre al que redirige:

router.get('/', function(req, res, next) {
	res.render('index', { title: 'Express' });
});

Como puedes ver, con res.render, se indican dos elementos:

  • La página: index (correspondiente a index.jade)
  • El modelo que se pasa en formato de objeto Javascript: title:’Express’.

Una cosa que seguro que te llama la atención es que las vistas no están en HTML puro sino en un lenguaje de marcado llamado “JADE”. En realidad es un motor de plantillas para faciliar la escritura de las vistas.

El uso de este motor de plantillas queda indicado en el código de app.js:

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

Vamos a ver el contenido de la plantilla principal: index.jade

extends layout

block content
h1= title
p Welcome to #{title}

Que de cara al usuario se transforma en:

<!DOCTYPE html>
<html>
<head>
<title>Express</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<h1>Express</h1>
<p>Welcome to Express</p>
</body>
</html>

Como puedes ver, sí que es una forma más sencilla de escribir HTML, aunque me pregunto cómo de flexible será si se quieren utilizar plantillas más ricas… Por ejemplo se podría usar esta adaptacion de Bootstrap 3 de Twitter para Jade: https://github.com/ALT-F1/bootstrap3-jade-node-express-grunt, pero esto daría para otros tutoriales.

Lo que nos debe interesar es la forma de integrar el modelo que nos han pasado (title:’Express’) en la plantilla. La susititución de la variable es muy sencilla:

p Welcome to #{title}

Efectivamente, se cambiar #{title} por el valor title del modelo, que es ‘Express’. Jade permite hacer cosas más complejas, como iterar sobre un array del modelo, incluir código de procesamiento básico, condicionales… Si quieres más información de Jade, puedes consultarla en su página propia: http://jade-lang.com/.

4.6. Elementos Públicos

En una aplicación Web no todos son recursos dinámicos que necesitan de un middleware para procesar. También tenemos recursos estáticos: imágenes, hojas de estilo, scripts…

Como podrás suponer, dentro de la estructura creada por el generador de express, tenemos la carperta “public”, donde podremos ubicar todos esos ficheros.

Esta carpeta está configurada en el fichero app.js, como no podría ser de otro modo:

app.use(express.static(path.join(__dirname, 'public')));

Importante: la carpeta public se mapea en el directorio “/”. Es decir, el directorio public/images de nuestro disco será http://localhost:3000/images directamente.

Por cierto, el resultado final lo puedes descargar aquí: https://github.com/4lberto/expressAdictos

5. Conclusiones

El mundo de Javascript en la parte servidora avanza imparable, y uno de los principales causantes es su capacidad para servir contenidos en el mundo del HTTP, bien como páginas Web o bien como sericios REST, tan de moda últimamente.

El principal exponente de los frameworks para servir contenidos dinámicos en NodeJS es Express. Se trata de un framework MVC que hace la competencia al siempre presente PHP, Spring MVC o los disponibles de ASP .NET

En este tutorial hemos visto los primeros pasos que tenemos que dar para desrrollar en este entorno, viendo desde la instalacion de un generador que nos facilita la creación de una plantilla de base, hasta los archivos y directorios más importantes donde debemos intervenir para crear una aplicación a nuestro gusto.

Desarrollo de microservicios con Spring Boot y Docker

$
0
0

Siguiendo con la serie de tutoriales dedicados a Docker, vamos a ver cómo desplegar un microservicio desarrollado con Spring Boot en un contenedor Docker. Posteriormente veremos como escalar y balancear este microservicio a través de HAProxy.

0. Índice de contenidos


1. Introducción

En el tutorial Introducción a los microservicios del gran José Luis vimos una introducción al concepto de microservicios, cuales son sus ventajas e inconvenientes y cuando podemos utilizarlos.

El objetivo que perseguimos con el presente tutorial es desarrollar un microservicio con Spring Boot, empaquetarlo dentro de una imagen Docker, dentro de la fase de construcción de maven, y una vez podamos levantarlo, ver una posibilidad de escalabilidad gracias a Docker Compose y HAProxy.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: MacBook Pro 15' (2.3 GHz Intel Core i7, 16GB DDR3 SDRAM)
  • Sistema Operativo: Mac OS X El Capitan 10.11
  • Software: Docker 1.11.1, Docker Machine 0.7.0, Docker Compose 1.7.1
  • Software: Spring Boot 1.4.0.M3

3. El Microservicio

El objetivo del tutorial no es tanto el desarrollo de microservicios con Spring Boot, sino su empaquetamiento y despliegue, por tanto vamos a implementar un microservicio ‘tonto’ cuya única funcionalidad es devolvernos un mensaje de hola.

El código lo podeís encontrar en mi cuenta de github aquí, lo primero que deberíamos implementar es un @RestControler como el que describimos a continuación:

package com.autentia;

  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.RestController;

  @RestController
  public class MicroServiceController {


      private final AddressService service;

      @Autowired
      public MicroServiceController(AddressService service) {
          this.service = service;
      }

      @RequestMapping(value = "/micro-service")
      public String hello() throws Exception {

          String serverAddress = service.getServerAddress();
          return new StringBuilder().append("Hello from IP address: ").append(serverAddress).toString();
      }


  }
Como podemos observar es un ejemplo muy sencillo, hemos declarado un controlador rest, al cual le hemos inyectado un servicio que recupera la IP del servidor, y devuelve un string del tipo “Hello from, IP address xx.xx.xx.xx”

La clase principal de nuestro microservicio encargada levantar un tomcat embebido con nuestro microservicio tendría un aspecto parecido a este:

package com.autentia;

  import org.springframework.boot.SpringApplication;
  import org.springframework.boot.autoconfigure.SpringBootApplication;

  @SpringBootApplication
  public class MicroServiceSpringBootApplication {

  	public static void main(String[] args) {

  		SpringApplication.run(MicroServiceSpringBootApplication.class, args);
  	}
  }

Podemos levantar nuestro servicio de la siguiente manera:

mvn clean spring-boot run

01_spring-boot-run

Una vez levantado el microservicio podemos invocarlo de la siguiente manera:

curl http://localhost:8080/micro-service

02_spring-boot-run

Hasta ahora nada impresionante … pasemos al siguiente punto


4. Dockerizar el microservicio

En este apartado vamos a ver como podemos ‘empaquetar’ nuestro microservicio dentro de un contenedor docker, para ello vamos a usar el plugin de maven spotify/docker-maven-plugin.

Antes de meternos de lleno en el uso de este plugin, vamos a generar un Dockerfile de nuestro microservicio, para ello nos creamos un directorio src/main/docker y creamos nuestro Dockerfile de la siguiente manera:

FROM frolvlad/alpine-oraclejdk8:slim
MAINTAINER jpacheco@autentia.com
ADD micro-service-spring-boot-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

Repasemos el Dockerfile:

  • FROM: Tomamos como imagen base frolvlad/alpine-oraclejdk8 esta imagen está basada en Alpine Linux que es una distribución Linux de sólo 5 MB, a la cual se le ha añadido la OracleJDK 8.
  • ADD: Le estamos indicando que copie el fichero micro-service-spring-boot-0.0.1-SNAPSHOT.jar al contenedor con el nombre app.jar
  • EXPOSE: Exponemos el puerto 8080 hacia fuera (es el puerto por defecto en el que escuchará el tomcat embebido de nuestro microservicio)
  • ENTRYPOINT: Le indicamos el comando a ejecutar cuando se levante el contenedor, como podemos ver es la ejecución de nuestro jar

El siguiente paso en añadir el plugin a nuestro pom.xml de la siguiente manera

<properties>
  	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  	<java.version>1.8</java.version>
  	<docker.image.prefix>autentia</docker.image.prefix>
  </properties>
   .....

   <plugins>
     <plugin>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-maven-plugin</artifactId>
     </plugin>
     <plugin>
       <groupId>com.spotify</groupId>
       <artifactId>docker-maven-plugin</artifactId>
       <version>0.4.10</version>
       <configuration>
         <imageName>${docker.image.prefix}/${project.artifactId}</imageName>
         <dockerDirectory>src/main/docker</dockerDirectory>
         <serverId>docker-hub</serverId>
         <registryUrl>https://index.docker.io/v1/</registryUrl>
         <forceTags>true</forceTags>
         <imageTags>
           <imageTag>${project.version}</imageTag>
         </imageTags>
         <resources>
           <resource>
             <targetPath>/</targetPath>
             <directory>${project.build.directory}</directory>
             <include>${project.build.finalName}.jar</include>
           </resource>
         </resources>
       </configuration>
       <executions>
         <execution>
           <id>build-image</id>
           <phase>package</phase>
           <goals>
             <goal>build</goal>
           </goals>
         </execution>
         <execution>
           <id>push-image</id>
           <phase>install</phase>
           <goals>
             <goal>push</goal>
           </goals>
           <configuration>
             <imageName>${docker.image.prefix}/${project.artifactId}:${project.version}</imageName>
           </configuration>
         </execution>
       </executions>
     </plugin>

En el apartado properties definimos:

  • docker.image.prefix: que indica el prefijo de la imagen a generar

En el apartado configuration de la sección de plugins definimos los siguiente parámetros :

  • imageName: Nombre de la imagen (prefijo + artifactId del proyecto)
  • dockerDirectory: Directorio en el que se encuentra el Dockerfile definido anteriormente
  • serverId: Identificador del registry de Docker (opcional: si queremos realizar un docker push a nuestro registry)
  • registryUrl: URL del registry de Docker (opcional: si queremos realizar un docker push a nuestro registry)
  • imageTag: Definimos las tags de nuestra imagen
  • resource: Le indicamos el recurso que vamos a empaquetar dentro de la imagen (‘targetPath’ path base de los recursos, ‘directory’ directorio de los recursos, ‘include’ incluimos el jar resultante )

En el apartado executions vinculamos los goals del plugin a las fases de maven:

  • build-image: Vinculamos a la fase package de maven, el goal docker:build que construye la imagen con el microservicio
  • build-image: Vinculamos a la fase de install de maven, el goal docker:push que sube nuestra imagen al registro de docker

Una vez configurado podemos ejecutar alguno de los goals del plugin:

mvn clean package

03_spring-boot-build

Como se puede ver en los logs, después de realizar el empaquetado se construye la imagen. Comprobamos si se han generado la imagen:

docker images

04_spring-boot-build

Podemos observar que en nuestro registro local, están disponibles tanto la imagen base de la que hemos partido frolvlad/alpine-oraclejdk8:slim, como nuestra imagen con los tags 0.0.1-SNAPSHOT y latest. El siguiente paso es arrancar un contenedor a partir de nuestra imagen

docker run -d -p 8080:8080 --name microservicio jpacheco/micro-service-spring-boot:0.0.1-SNAPSHOT
Con esto arrancamos nuestro contenedor, podemos comprobarlo ejecutando
docker ps

05_spring-boot-start

Una vez levantado el contenedor accedemos a nuestro servicio de manera análoga a la anterior sustituyendo ‘localhost’ por la IP de nuestro docker-machine

curl http://192.168.99.100:8080/micro-service

06_spring-boot-start

Podemos observar que la IP que devuelve es la IP interna del contenedor 172.17.0.2.

El último paso que nos quedaría para completar el ciclo seria realizar el ‘push’ de nuestra imagen a nuestro docker registry (es este ejemplo usaremos docker-hub, como hemos definido en el pom.xml con los parámetros: serverId, registryUrl), nos faltaría añadir nuestras credenciales de docker-hub en el settins.xml de maven

<server>
      <id>docker-hub</id>
      <username>myuser</username>
      <password>mypassword</password>
      <configuration>
        <email>user@company.com</email>
      </configuration>
    </server>

Ya estamos listos para realizar un ‘push’ de nuestra imagen. Recordar que hemos vinculado el push a la tarea maven ‘install’

mvn install

Podemos acceder a nuestra cuenta de docker-hub y comprobar que se ha creado nuestra imagen

07_spring-boot-registry

La cosa se empieza a poner interesante … ya tenemos nuestro microservicio empaquetado en un contenedor y disponible en nuestro registry


5. Escalando la solución

El siguiente paso que vamos a estudiar, es como podemos lanzar varias instancias de nuestro microservicio y como podemos balancear el trafico entre ellas.

Para esto vamos a usar HAProxy que es una herramienta open source que actúa como balanceador de carga (load balancer) ofreciendo alta disponibilidad, balanceo de carga y proxy para comunicaciones TCP y HTTP.

Como no podía ser de otra manera, vamos a usar Docker para levantar el servicio HAProxy Para ello vamos a usar docker-compose para definir tanto el microservicio, como el balanceador

microservice1:
      image: 'jpacheco/micro-service-spring-boot:latest'
      expose:
        - "8080"
    microservice2:
      image: 'jpacheco/micro-service-spring-boot:latest'
      expose:
        - "8080"
    loadbalancer:
      image: 'dockercloud/haproxy:latest'
      links:
        - microservice1
        - microservice2
      ports:
        - '80:80'

Como podemos ver en el fichero docker-compose.yml, hemos definido 2 instancias de nuestro microservicio (microservice1, microservice2) y un balanceador (loadbalancer) con enlaces a los microservicios definidos anteriormente. Lo que conseguimos con esta imagen de HAProxy es exponer el puerto 80 y redirigir a los 2 microservicios expuestos en el 8080 usando una estrategia round-robin. Levantamos nuestro entorno con:

docker-compose up -d

y podemos observar como se levantan los 3 contenedores

08_spring-boot-compose1

Vamos a invocar a nuestro microservicio a través del balanceador:

08_spring-boot-compose2

Como podemos observar en la consola, el balanceador va accediendo cada vez a una instancia del microservicio, logrando el balanceo de carga que íbamos buscando … No está mal no?, el ejemplo va tomando ‘cuerpo’.

Pero.. ¿y si queremos levantar más instancias de nuestro microservicio? ¿Tenemos que modificar el docker-compose.yml, y añadir ‘microservice3…microserviceN’?

Revisando la documentación de la imagen HAProxy encontramos una solución a esta problemática, la idea es levantar una primera instancia del balanceador y del microservicio y posteriormente en función de las necesidades, levantar más instancias del microservicio y que el balanceador se reconfigure para añadirlas. Veamos como quedaría el docker-compose.yml

version: '2'
    services:
       microservice:
        image: 'jpacheco/micro-service-spring-boot:latest'
        expose:
          - '8080'
       loadbalancer:
        image: 'dockercloud/haproxy:latest'
        links:
          - microservice
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock
        ports:
          - '80:80'

Repasemos las lineas más destacadas:

  • version: ‘2’ estamos indicando que use la v2 de docker-compose (necesaria para este ejemplo)
  • service: Tag raíz del que cuelgan nuestros contenedores
  • microservice: loadbalancer Definición de nuestros contenedores
  • volumes: El contenedor de HAProxy necesita acceder al ‘docker socket’ para poder detectar nuevas instancias y reconfigurarse
Vamos a levantar nuestros contenedores:
docker-compose -f docker-composeV2.yml up -d

09_spring-boot-compose3

Vemos como se han levantado una instancia del balanceador y otra del microservicio. Ahora vamos a escalar nuestro microservicio añadiendo 2 instancias más:

docker-compose -f docker-composeV2.yml scale microservice=3

10_spring-boot-compose4

Comprobamos que se han creado 2 nuevas instancias de nuestro microservicio, ahora vamos a probar que estas instancias se han añadido al balanceador:

11_spring-boot-compose5

Como podemos ver, cada petición es atendida por una instancia distinta …. podríamos ir añadiendo instancias según vayamos necesitando, bastaría con ejecutar docker-compose -f docker-composeV2.yml scale microservice=<Instancias_vivas+Nuevas>


6. Conclusiones

Como hemos podido ver a lo largo del tutorial, la combinación de Spring Boot y Docker nos permite desarrollar facilmente microservicios, incluso montar infraestructuras que permitan su escalabilidad. El siguiente paso sería investigar la posibilidad de escalarlo a través de un cluster de máquinas usando herramientas como Docker Swarn o Kubernetes, pero eso os lo dejo a vosotros 😉

Un saludo.


7. Referencias

Experimentando con D3JS. Gráfico de Barras

$
0
0

Experimentación con D3JS. En este primer laboratorio se elige un simple gráfico de barras para la toma de contacto, cómo beber de una fuente de datos externa, y mostrar un tooltip con el detalle de los campos retornados.

Índice de contenidos


1. Gráfico de barras con D3JS

Este artículo es el primero de una serie de laboratorios, para aprender y probar D3JS.

D3JS es una poderosa librería que mediante estándares web nos permite representar gráficos estadísticos complejos. Se apoya en el estándar de gráficos vectoriales SVG, de CSS3 para los estilos sobre los gráficos y JavaScript para tratar con los datos.

La representación de datos numéricos en un modelo que se preste a una rápida interpretación visual es un arte. A veces conviene usar unos modelos en lugar de otros. Hay muchísimos: gráficos de barras, curvas de nivel, grafos de densidad, redes con peso en los nodos, mapas de calor, distribuciones sobre mapas geolocalizados, nubes de dispersión, etc… ¿Cuál elegir? El que represente más fielmente la información y se preste a una interpretación sencilla. De nada nos sirven gráficos complejos que nos cuesta interpretar. Y ahí prima el sentido común. No es lo mismo tratar con datos de personas por rangos de edades para ambos sexos, donde a lo mejor es conveniente representarlo en forma de pirámide poblacional, que con casos de gripe por cada mil habitantes en España, donde una proyección sobre el mapa de la península parece inevitable.

D3JS nos permite hacer todo eso, pero la curva de aprendizaje no es elemental y requiere de una aproximación a esta tecnología mediante la propia experimentación.

En este primer ejercicio, me fijo la meta de dibujar un simple gráfico de barras en base a una petición REST que me devuelva los sueldos medios en Europa durante el 2014.

barChart_00

2. Un primer acercamiento a SVG

Dados los datos

<[135, 100, 150, 125, 225, 175]>
se quiere dibujar un gráfico de barras. Para ello, haremos uso de la etiqueta
<svg>
a la que hay que indicarle un ancho y un alto, y dibujaremos seis rectángulos con la altura indicada en los datos.
<svg width="315" height="410">
    <rect x="10" width="45" height="135" />
    <rect x="60" width="45" height="100" />
    <rect x="110" width="45" height="150" />
    <rect x="160" width="45" height="125" />
    <rect x="210" width="45" height="225" />
    <rect x="260" width="45" height="175" />
</svg>

Ver ejemplo 1: Simple SVG

Esto tiene el indeseable efecto de desplegar las barras hacia abajo, en lugar de crecer hacia arriba desde la línea base (eje X). ¿Esto por qué pasa? Todo lo que está dentro de la etiqueta SVG se posiciona de forma relativa, siendo el origen de coordenadas la esquina superior izquierda. De esta forma, igual que para cada columna nos posicionamos en su coordenada horizontal (“x”), a partir de la cual se suma su ancho, lo mismo hay que hacer con la coordenada vertical (“y”), a partir de la cual se suma su altura.

<style>
    svg {
        border: 1px solid #000000;
    }
    rect {
        stroke: SteelBlue;
        stroke-width: 2;
        fill: LightSteelBlue;
    }
    rect:hover {
        fill: SteelBlue;
    }
</style>
<svg width="315" height="410">
    <rect x="10" width="45" y="265" height="135" />
    <rect x="60" width="45" y="300" height="100" />
    <rect x="110" width="45" y="250" height="150" />
    <rect x="160" width="45" y="275" height="125" />
    <rect x="210" width="45" y="175" height="225" />
    <rect x="260" width="45" y="225" height="175" />
</svg>
barChart_01

Si queremos que todas las barras acaben en un supuesto eje horizontal, tenemos que hacerlo manualmente, de forma que la “y” y el “height” sumen lo mismo para cada barra, en el ejemplo que nos ocupa “400”. Y en la etiqueta svg se ha puesto que la altura es 410. Esos 10 píxeles de más es el margen que se ve en la imágen.

Para verlo mejor se ha añadido un borde de 1 píxel al estilo de la etiqueta svg. Otra cosa que observamos es que para dar estilo mediante CSS a los rectángulos no se usa background ni border, si no fill y stroke.

En este ejemplo, el gráfico respira mucho por arriba, pero se podía haber limitado la altura del svg al máximo de los datos más un margen. A todos estos cálculos nos ayuda D3JS.

Ver ejemplo 2: Simple SVG


3. El mismo ejemplo con D3JS

Se ve, que para seis columnas el código queda sencillo, pero que si son muchas más, hacer los cálculos es un engorro, además de que también nos puede interesar que los datos se carguen dinámicamente y sean cambiantes.

Vamos a ver cómo sería este ejemplo con D3JS:

<svg width="315" height="245"></svg>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
  var datos = [135, 100, 150, 125, 225, 175];
  var config = { columnWidth: 45, columnGap: 5, margin: 10, height: 235 };

  d3.select("svg")
      .selectAll("rect")
      .data(datos)
    .enter().append("rect")
      .attr("width", config.columnWidth)
      .attr("x", function(d,i) {
         return config.margin + i * (config.columnWidth + config.columnGap)
       })
      .attr("y", function(d,i) { return config.height - d })
      .attr("height", function(d,i) { return d });
</script>

Este código obtiene el mismo resultado que el ejemplo anterior. Vemos que si en lugar de 6 columnas hubiera 600, el código no crecería. Y los cálculos no tenemos que realizarlos a mano cada vez.

Vamos a explicar qué hace el código.

Lo primero que se observa es que se importa la librería de d3js

<script src="http://d3js.org/d3.v3.min.js"></script>

Luego hacemos:

d3.select("svg")
que es la forma que tenemos en D3JS para seleccionar un elemento del DOM, lo mismo que haríamos en jQuery con
$("svg")
o con JavaScript clásico con
document.getElementsByTagName("svg")[0]
o con el nuevo selector que nos ofrece html5
document.querySelector("svg")
.

Con

selectAll("rect")
seleccionamos todos los rectángulos que hubiera dentro de la etiqueta svg. Lo cierto es que hasta ahora no hay ninguno, pero si los hubiera, le aplicaría a cada uno lo que venga en el array datos.

Con

enter().append("rect")
indicamos que si le faltan rectángulos para los elementos del array, que los cree. Y el resto son indicaciones de como calcular los distintos atributos de rect.
<rect x="160" width="45" y="275" height="125" />

La indentación escalonada no es casualidad: hay una convención, por la cual se indenta igual mientras no cambie los elementos del DOM seleccionados. Como con enter().append(“rect”) añadimos elementos en el caso de que falten, se cambia la indentación.


4. Fuente externa de datos

Lo normal, es que los datos no sean estáticos, si no que cambien de forma dinámica y sea un servicio el que sirva dichos datos. De alguna forma tenemos que poder indicarle a D3JS ese DataProvider. Efectivamente, D3JS nos provee de una serie de métodos para poder cargar datos de recursos externos: ficheros de datos tabulados separados por comas (CSV), ficheros separados por tabuladores (TSV), XMLs, TXTs, un sin fin de formatos más, y por supuesto JSON, que es el que vamos a usar en nuestros ejemplos.

d3.json("salarioMedioEuropa.json", function(error, json) {
    if (error) {
        return console.warn(error);
    }
    renderData(json);
});

function renderData(datos){
    d3.select("svg")
        .selectAll("rect")
        .data(datos)
      .enter().append("rect")
        .attr("width", config.columnWidth)
        .attr("x", function(d,i) {
            return config.margin + i *
                (config.columnWidth + config.columnGap) })
        .attr("y", function(d,i) { return config.height - d.salarioMedio })
        .attr("height", function(d,i) { return d.salarioMedio })
        .attr("data-nombre", function(d,i) { return d.nombre })
        .attr("data-salarioMerdio", function(d,i) {
            return d.salarioMedio })
}

Ver ejemplo 3: SVG from external source

barChart_03

Esto debería mostrar los datos que vienen de la petición AJAX, que retorna los salarios medios en 40 países, pero vemos que el resultado no es el esperado.

Esto es porque efectivamente hemos mostrado los datos reales, pero la altura de las barras es en píxeles, y claro el salario medio anual de cualquier país es mayor que la altura que le hemos dado al gráfico SVG.

Podríamos dividir el salario medio de cada país para normalizar los datos, pero cabría preguntarse si D3JS tiene algún mecanismo para hacer esto.

Efectivamente lo tiene y se llama d3.scale, que pueden ser de muchos tipos: lineal, logarítmica, etc… Y se usan de la siguiente manera:

var SALARIO_MAX = d3.max(datos, function(d) { return +d.salarioMedio; });
var normalizeY = d3.scale.linear()
                    .domain([0, SALARIO_MAX])
                    .range([0, config.height - 10]);

Por un lado se calcula cual es el valor máximo. Y por otro se devuelve la variable normalizeY que retorna una función, de forma que hace una regla de tres lineal proyectando cualquier valor situado entre 0 y el salario máximo a un valor situado en el rango indicado, que es entre 0 y la altura del SVG (menos 10 píxeles de margen).

NOTA:

Conviene fijarse en cómo se calcula el máximo, y en que el return de la función lleva el signo +, esto es por que al provenir de JSON, todos los campos vienen como string si no indicamos lo contrario, y el “8.000” es mayor que “40.000” al prevalecer el orden lexicográfico. Así que hay que forzar los tipos, para que sean de tipo numérico. Hay dos opciones: signo + por la izquierda, o multiplicar el valor por 1.

Ahora, el gráfico queda como sigue.

Ver ejemplo 4: SVG with normalized data

barChart_04

5. Formateando números con D3JS

Ahora cada barra tiene la información siguiente:

<rect width="20" x="235" y="156.01693323314726" height="78.98306676685273" data-nombre="España" data-salarioMerdio="26162€"></rect>

Hemos guardado en cada barra el nombre del País y el salario medio del mismo, por si los necesitamos para hacer algo con la barra seleccionada, o para mostrar un tooltip, etc…

Se podría querer formatear el salarioMedio. Para eso nos provee de la función format que hace uso del minilenguaje de especificación de formatos de Python, el cual es ciertamente melindroso hasta que te lees el enlace anterior y lo entiendes todo.

var _format = d3.format("$,.2f");

Ahí le indicamos que queremos moneda, separador de miles, separador de decimales, con dos decimales y que es un número. Pero nos lo va a devolver en formato anglosajón. Para formatearlo en otro idioma tenemos que indicarle los parámetros del locale que queremos cambiar.

var ES_es = d3.locale ({
        "decimal": ",",
        "thousands": ".",
        "grouping": [3],
        "currency": ["", " €"],
        "dateTime": "%a %b %e %X %Y",
        "date": "%d/%m/%Y",
        "time": "%H:%M:%S",
        "periods": ["AM", "PM"],
        "days": ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"],
        "shortDays": ["Dom", "Lun", "Mar", "Mi", "Jue", "Vie", "Sab"],
        "months": ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
        "shortMonths": ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"]
});

var _format = ES_es.numberFormat("$,0f");

Ahora el formateo ya es el adecuado.


6. Una pausa para reflexionar

Hemos visto ya algunas cositas, pero no son ni la sombra de lo que puede hacer D3JS. Y sin embargo, cuando he empezado a hacer estos ejercicios me he sentido un poco incómodo, y es por cosas como esta:

var _format = ES_es.numberFormat("$,0f");
barChart_console_1

Aunque estamos declarando una variable, lo que se almacena en dicha variable es una función. En el momento en que se repara en este concepto, se ve claro como funciona D3JS. En casi todos los casos, cuando llamamos a un método de D3JS no nos devuelve un resultado, si no una función.

Así que realmente llamar a

_format(26000)
es lo mismo que llamar a
(ES_es.numberFormat("$,0f"))(26000)

Y lo mismo para otras variables que ya hemos visto, que en el fondo devuelven una función.

var normalizeY = d3.scale.linear()
                    .domain([0, SALARIO_MAX])
                    .range([0, config.height - 10]);

En el momento que se entiende ésto, ya no se nos hace extraño, y al revés, se ve cómodo y de forma natural.


7. Añadiendo los ejes y magnitudes

Por un lado, estamos calculando a mano dónde cae cada columna en el ejeX y por otro habíamos creado una función normalizeY que proyecta nuestro rango sobre el ejeY. Vamos a ver si hay una forma de automatizar eso.

var config = {
    columnWidth: 20,
    columnHeight: 235,
    columnGap: 5,
    padding: 100};

function renderData(datos){
    var NUM_COLUMNAS = datos.length;
    config.width = NUM_COLUMNAS * (config.columnWidth + config.columnGap)
                   + (2 * config.padding);
    config.height = config.columnHeight + 2 * config.padding;

    var SALARIO_MAX = d3.max(datos, function(d) {
            return +d.salarioMedio;
    });

    var x = d3.scale.ordinal()
            .rangeRoundBands([0, config.width - 2 * config.padding])
            .domain(datos.map(function(d) { return d.nombre; }));
    var y = d3.scale.linear()
            .range([0, config.columnHeight])
            .domain([0, SALARIO_MAX]);
…
}
barChart_console_2

De esta forma, la función x(“Suiza”) nos devolverá la coordenada en el ejeX donde se debe mostrar la columna correspondiente a Suiza

barChart_05

Ver ejemplo 5: SVG with normalized data

Ahora vamos con los ejes. Hay que mostrar las líneas del ejeX y del ejeY y alguna muesca que indique la magnitud de las cantidades que manejamos.

Para eso definimos una serie de variables con los ejes

var ejeX = d3.svg.axis().scale(x).orient("bottom");

var ejeY = d3.svg.axis().scale(y).orient("left");

Y ahora, como el gráfico SVG empieza a crecer mucho, lo almacenamos en una variable, para ir trabajando con él poco a poco.

var svg = d3.select("svg")
    .attr("width", config.width)
    .attr("height", config.height);

Y añadimos los ejes que hemos creado.

svg.append("g")
    .attr("class", "eje")
    .attr("transform", "translate(100,345)")
    .call(ejeX);

svg.append("g")
    .attr("class", "eje")
    .attr("transform", "translate(90,100)")
    .call(ejeY);

Eso obtiene el siguiente resultado

barChart_06

Ver ejemplo 6: SVG with axis

Lo primero que se aprecia es que los nombres de los países se muestran en horizontal y se tapan unos a otros. Y lo segundo, es que el eje Y va de 0 a 70,000 empezando por arriba. Recordemos, que esto es porque la coordenada 0,0 está situada arriba a la izquierda.

Como el

d3.svg.axis.scale()
espera recibir una escala, tenemos que invertir la que tenemos. Lo más fácil es definirla justo al revés: de columnHeight a 0.
var rangeY = d3.scale.linear()
    .range([config.columnHeight, 0])
    .domain([0, SALARIO_MAX]);

Y usar esta escala en el ejeY. Aprovechamos, y ya formateamos de paso las cantidades a mostrar en el ejeY.

var ejeY = d3.svg.axis()
    .scale(rangeY)
    .tickFormat(_format)
    .orient("left");

Para mostrar los países verticales, seleccionamos todos los elementos text una vez ya se han añadido al SVG y los rotamos 90 grados, lo posicionamos para que no tape la rayita y le ponemos text-anchor a start para que estén todos alineados al inicio, porque si no los alínea por defecto al medio.

svg.append("g")
    .attr("class", "eje")
    .attr("transform", "translate(100,345)")
    .call(ejeX);
  .selectAll("text")
    .attr("transform", "rotate(90)")
    .attr("x", "10")
    .attr("y", "-3")
    .style("text-anchor", "start");
barChart_07

Ver ejemplo 7: SVG with axis and formatted labels


8. Añadir un tooltip que muestre el detalle

A veces, si la cantidad de datos a mostrar es elevado, es conveniente filtrar la información relevante que se muestra en un primer vistazo, y ampliar la información con detalles pormenorizados. Para esto los tooltips son muy útiles.

D3JS tiene una librería que extiende la funcionalidad básica, y que dota a nuestras gráficas de esta posibilidad. La librería se llama d3-tip y es muy sencilla de utilizar.

Lo primero es descargarla e integrarla en nuestro proyecto.

Luego inicializamos el tooltip, se lo añadimos a nuestro gráfico SVG y lo llamamos. Posteriormente añadimos los controladores de eventos, para mostrarlo y ocultarlo.

var tooltip = d3.tip()
    .attr('class', 'tooltip')
    .offset([-10, 0])
    .html(function(d) {
        return "<strong>" + d.nombre + "</strong><br>"
             + "salario medio: " + _format(+d.salarioMedio);
            });

var svg = d3.select("svg")
    .attr("width", config.width)
    .attr("height", config.height);

svg.call(tooltip);

[...]

svg.selectAll("rect")
    .data(datos)
    .enter().append("rect")
    .attr("width", config.columnWidth)
    [...]
    .on('mouseover', tooltip.show)
    .on('mouseout', tooltip.hide)

Y añadimos los correspondientes estilos CSS para adaptarlo a nuestro gráfico.
.tooltip {
    font-family: Arial, helvetica, sans-serif;
    font-size: 10px;
    padding: 8px;
    background: rgba(0, 0, 0, 0.7);
    border: 1px solid #FFFFFF;
    box-shadow: rgba(0, 0, 0, 0.5) 1px 1px 4px;
    color: #FFFFFF;
    border-radius: 4px;
}
barChart_08

Ver ejemplo 8: SVG with tooltip


9 . Conclusiones

La librería D3JS es excepcionalmente potente, pero requiere de un pequeño entrenamiento para poder hacer uso de ella. Hay que tener una visión conceptual del plano, de qué es lo que se está haciendo, y conocer un poquito de SVG. Lo demás viene rodado. Éste ha sido el ejercicio más elemental con el que nos podíamos poner, para que sirviera a modo de toma de contacto.


10 . Enlaces y referencias


Informes dinámicos con DynamicJasper

$
0
0
Cómo generar informes al vuelo mediante DynamicJasper

Índice de contenidos

1. Introducción

Hoy en día es muy común que por pequeña que sea nuestra aplicación tengamos que generar algún tipo de informe. En el mundo Java existe un estándar de facto llamado JasperReports, ampliamente extendido. Si bien generar un informe al vuelo con JasperReports es técnicamente posible, requiere de un exhaustivo conocimiento de la herramienta, su comportamiento y posibilidades.
Para simplificar esa tarea y ayudarnos en la medida de lo posible, disponemos de un framework llamado DynamicJasper, que apoyándose en JasperReports, oculta su complejidad y nos permite realizar informes profesionales de una manera muy sencilla.

2. Entorno

El tutorial está escrito usando el siguiente entorno:
  • Hardware: Portátil MacBook Pro Retina 15″ (2.2 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS Yosemite 10.10
  • Entorno de desarrollo: IDEA IntelliJ 16.01 EAP
  • Maven 3.3.9
  • Flyway
  • PostgreSQL 9.5.1

3. Creación del proyecto con Maven

Lo primero que necesitamos es importar la librería de DynamicJasper. Optamos por la manera más sencilla, que es crearnos un proyecto Maven, en el que incluimos el repositorio de DynamicJasper y su dependencia:
...
  <repositories>
    <repository>
      <id>fdvsolution.public</id>
      <url>http://nexus.fdvs.com.ar/content/groups/public/</url>
    </repository>
  </repositories>
  ...
  <dependencies>
    ...
    <dependency>
      <groupId>ar.com.fdvs</groupId>
      <artifactId>DynamicJasper</artifactId>
      <version>5.0.2</version>
    </dependency>
    ...
  </dependencies>
...
Con esta sencilla configuración ya estamos listos para utilizar DynamicJasper en nuestro proyecto.

4. Nuestro primer informe

La estructura de nuestro primer informe va a ser muy sencilla. Va a constar de un encabezado de página, un subtítulo que indique la fecha y hora de generación y una linea de detalle en la que mostrar nuestra información.

Lo que a priori parece sencillo, utilizando JasperReports directamente supondría realizar tareas de configuración del informe, definición de elementos, gestión de bandas de impresión, parámetros, etc. Ahora veremos como queda resuelto con este framework.

Para el caso que nos ocupa, vamos a dividir nuestro proyecto en datasource, exporter y clase principal. Únicamente vamos a comentar las clases de generación y exportación del informe. Para el resto del proyecto, podéis importarlo desde el repositorio de GitHub aquí.

public DynamicReport buildReport() throws ClassNotFoundException {
  FastReportBuilder fastReportBuilder = new FastReportBuilder();

  fastReportBuilder.addColumn("ID", "id", Long.class.getName(), 50)
    .addColumn("Nombre", "firstname", String.class.getName(), 200)
    .addColumn("Apellidos", "surname", String.class.getName(), 200)
    .addColumn("Fecha Incorporación", "startDate", String.class.getName(), 120)
    .addColumn("Salario", "salary", String.class.getName(), 120)
    .addColumn("Departamento", "department", String.class.getName(), 180)
    .setTitle("Mi primer Informe con DynamicJasper").setSubtitle("Generado el " + new Date())
    .setPrintBackgroundOnOddRows(true).setUseFullPageWidth(true);

  return fastReportBuilder.build();
}
public static void exportReport(JasperPrint jp, String path) throws JRException, FileNotFoundException {
  LOGGER.debug("Exporing report to: " + path);
  JRPdfExporter exporter = new JRPdfExporter();

  File outputFile = new File(path);
  File parentFile = outputFile.getParentFile();
  if (parentFile != null) parentFile.mkdirs();
  FileOutputStream fos = new FileOutputStream(outputFile);

  exporter.setParameter(JRExporterParameter.JASPER_PRINT, jp);
  exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, fos);

  exporter.exportReport();

  LOGGER.debug("Report exported: " + path);
}

Como podemos ver, la generación de informes sencillos de manera dinámica no podría ser más simple. Sólo tenemos que instanciar el builder que nos proporciona DynamicJasper, ir añadiendo las columnas que necesitemos en el orden de salida, títulos y demás propiedades. La exportación se delega a los métodos de JasperReports, ya que lo que obtenemos es un objeto JasperPrint.

5. Conclusiones

Si bien hemos conseguido generar un informe muy simple, DynamicJasper nos brinda multitud de posibilidades a la hora de trabajar con reports. Podemos gestionar virtualización, grupos, layouts, tablas, estilos, subreports, plantillas, etc. con la misma facilidad que este pequeño informe. Merece la pena dedicar un tiempo a revisar todas las posibilidades que nos ofrece esta librería.

6. Referencias

Facelets: entendiendo los templates de JSF

$
0
0

En este tutorial vamos a ver los templates de JSF, cómo funcionan, cómo se crean y ejemplos prácticos de ello.

Facelets: entendiendo los templates de JSF



0. Índice de contenidos.



1. Por qué necesitaba una guía así.

En adictos ya existe un tutorial que explica las etiquetas para usar las Facelets de JSF 2. En esta entrada dejaré de lado la creación de componentes y explicaré únicamente el concepto de los templates y cómo se crean y usan.

Cuando comencé a aprender JSF y me topé con los templates tuve serios problemas para entender el concepto. Acababa de introducirme en AngularJS y tendía a relacionarlo inconscientemente con el routing y los partials de este framework, una idea completamente contraria. Durante las horas que tardé en formarme una idea, eché en falta que alguien me explicase qué es un template en JSF. Esta entrada es para explicar cómo considero que se puede entender mejor, por si a alguien le puede ser de ayuda en algún momento.

Sin entrar demasiado en detalles, y antes de seguir con el tutorial, comentaré la diferencia principal entre ambos. Podríamos decir que en JSF 2 un template es un fichero de tipo xhtml, que define zonas en las que vamos a insertar contenido desde la página principal. En AngularJS es esta página principal la que declara la zona que contendrá el contenido y en función de la url se llenará con un partial (el template) que corresponda. Hay un par de formas de simular el comportamiento del segundo con JSF 2, pero en principio la idea no es esa.



2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Mac Book Pro 15″ (2,4 Ghz Intel Core i5, 8 GB DDR3)
  • Sistema Operativo: Mac OS X El Capitan
  • Entorno de desarrollo: Eclipse Java EE IDE, Mars Release (4.5.2)
  • Apache Maven 4.0.0
  • JSF 2.2


3. Qué es un template.

Antes de comentar cómo se usan, es necesaria una explicación de qué son los templates y para qué se usan. De ahora en adelante, y salvo que diga lo contrario, siempre hablaré de templates en el ámbito de JSF 2. Hay dos definiciones muy importantes para entender cómo funciona:

  • Template: fichero que define zonas de contenido dinámico a ocupar por quien lo utilice.
  • Cliente: fichero principal, que utilizará el template y especificará qué contenido va en cada zona. Al indicar una ruta en la url, es al cliente al que se accede.

Con esos dos conceptos no habré ayudado demasiado. Quizá sea mejor si vemos cómo se aplicaría en un entorno “real”. Si pensamos en una web estándar, la plantilla definirá la imagen corporativa (header, footer, menús, colocación de elementos…) y dejará un hueco en la parte central para cambiar el contenido en función de nuestras necesidades. Después, cada una de las páginas cliente concretas utilizarán esta plantilla y decidirán qué contenido va en el medio. El código común solo se escribe una vez, aunque se use muchas.

Dibujo explicativo del uso de un template.

4. Cómo se crea un template.

A lo largo de lo que resta de tutorial, recurriré al ejemplo que se introduce en el libro JavaServer Faces 2.0, The Complete Reference y lo expandiré para aplicarlo a todas las etiquetas que tenemos a nuestra disposición. El template será para nosotros una hoja de papel ya escrita, con una serie de agujeros que nos permiten ver lo que hay debajo. El cliente será otra hoja que pondremos debajo y del que solo veremos el contenido que no tape el template.

Dibujo explicativo de qué es un template.

Definir un template es realmente simple. Partiendo de un un xhtml ya formado, solo habrá que usar el par de etiquetas <ui:insert name=”nombre_del_hueco”></ui:insert> para definir los huecos que queramos. Entre ellas podremos poner un contenido por defecto que se mostrará en caso de que el cliente no especifique nada.

Dibujo explicativo de la creación de un template.

Este es el código para crear un template con todo el contenido accesorio en una página web, con un hueco para el contenido. Por motivos de espacio, y para no repetir 3 veces un código muy similar, no corresponde a la imagen anterior.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:ui="http://java.sun.com/jsf/facelets">

<head>
	<title>Tutorial de Facelets en JSF 2</title>
</head>
<body&
	<!-- Menús, headers y todo lo que vaya antes del contenido -->
	<ui:insert name="contenido">
		Contenido por defecto para que no quede en blanco...
	</ui:insert>
	<!-- Footer y otros componentes que vayan después del contenido -->
</body>
</html>
 

5. Usar los templates que hemos creado.

Sabemos cómo crear un template pero, ¿cómo podemos usarlo? Disponemos de otro par de etiquetas para rellenar los huecos declarados con <ui:insert>. Se trata de <ui:define name=”nombre_del_hueco”></ui:define>. En nuestra analogía sería equivalente a calcular la zona de la hoja cliente que deja visible un determinado hueco y escribir el contenido debajo. Entre las etiquetas de apertura y cierre se escribirá todo el contenido xhtml que deba renderizarse.

Pero <ui:define> no puede aparecer de forma independiente. Es necesario decir qué template usar, no solo el hueco. Disponemos de dos pares de etiquetas para lograr esto, con comportamientos ligeramente distintos entre sí.


5.1. Tapando todo el cliente.

La primera opción que tenemos es usar <ui:composition template=”ruta_relativa_template.xhtml”> </ui:composition>. Dentro de estas etiquetas escribiremos los <ui:define> correspondientes a los <ui:insert> del template. Es importante destacar que todo el contenido que no sea un <ui:define> válido se pasará por alto a la hora de mostrarlo al usuario.

El comportamiento especial de estas etiquetas es que a la hora de renderizar va a obviar por completo todo lo que se haya escrito fuera del par de apertura/cierre. Volviendo nuestro ejemplo, sería como usar un papel cliente que es más pequeño (o igual) que la plantilla que ponemos encima. Es decir, la hoja template que hay arriba taparía por completo todo escrito en el cliente, excepto lo que se pueda leer por los recortes.

Dibujo explicativo del uso de la etiqueta composition.

En este dibujo ya hemos visto dónde se ubican exactamente los <ui:define> dentro de la hoja del cliente. A continuación lo veremos en un pequeño ejemplo para usar el template que creamos con el código del apartado anterior:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets">

<body>
	<!-- Lo que escribamos aquí NO aquí se va a renderizar. -->
	Esto seguro que no lo ves.

	<ui:composition template="template.xhtml">
		<ui:define name="contenido">
			<h1>Contenido que inserta el cliente en el template.</h1>
		</ui:define>
	</ui:composition>

	<!-- Ni TAMPOCO esto. -->
	Y esto tampoco.
</body>
</html>
 

5.2. Viendo contenido propio del cliente fuera de la plantilla.

La segunda opción es usar las etiquetas <ui:decorate template=”ruta_relativa_template.xhtml”> </ui:decorate>, que se usan exactamente igual que las anteriores. La diferencia está en que esta vez sí se renderizará lo que hay alrededor del uso del template. El nombre lo deja claro, decora la plantilla con lo que hemos escrito en la página.

Aplicado a nuestro ejemplo, es como si el papel cliente fuese de un tamaño mayor al de la plantilla. Todo lo que esté escrito fuera de la zona que tapa la hoja superior se podrá ver perfectamente.

Dibujo explicativo del uso de la etiqueta decorate.

Aquí tenemos un código de ejemplo para consumir el template que habíamos creado anteriormente, esta vez añadiendo una imagen de cabecera y un footer para el copyright:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets">

<body>
	<!-- Lo que escribamos aquí SI aquí se va a renderizar. -->
	<b>Aquí iría la imagen para la cabecera.</b>

	<ui:decorate template="template.xhtml">
		<ui:define name="contenido">
			<h1>Contenido que inserta el cliente en el template.</h1>
		</ui:define>
	</ui:decorate>

	<!-- Y esto TAMBIÉN. -->
	<b>Y aquí todo el contenido para el copyright.</b>
</body>
</html>
 

6. Insertar fichero externo íntegro dentro de nuestra página.

Aunque no haga uso de los templates que hemos generado hasta ahora, los Facelets nos ofrecen otro par de etiquetas con un efecto relacionado. Al usar <ui:include src=””ruta_relativa_del_fichero.xhtml> </ui:include> podemos insertar el contenido íntegro de otro fichero xhtml en el punto donde se utiliza. Podríamos decir que es como si cortásemos el papel cliente en ese punto y pegásemos otra hoja con más contenido entre las dos partes.

Dibujo explicativo de la inclusión de contenido sin plantillas.

Además podemos utilizar dentro del <ui:include> la etiqueta <ui:param name=”nombre_parametro” value=”valor”> para pasarle parámetros al fichero incluido. Para usarlos se hará con la sintaxis #{nombre_parametro}.

Aquí podemos ver un pequeño ejemplo de estas dos etiquetas. Por un lado tenemos el xhtml que vamos a incluir y en el que usamos el parámetro name:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
	Este es el contenido que hemos pegado en nuestra página <b>#{name}</b>.
</body>
</html>

Y por otro el código del xhtml que incluirá ese contenido y le pasará el parámetro:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets">
<head>
	<title>Tutorial de Facelets en JSF 2</title>
</head>
<body>
	<!-- Lo que escribamos aquí SI aquí se va a renderizar. -->
	<ui:include src="toInclude.xhtml">
		<ui:param name="name" value="cliente"></ui:param>
	</ui:include>
	<!-- Y esto TAMBIÉN. -->
</body>
</html>

A modo de curiosidad, utilizar un <ui:decorator> sin ningún <ui:define> válido es equivalente a un <ui:include> sin parámetros. Ambos insertan el contenido del fichero objetivo en el punto de uso de las etiquetas. De todos modos, aunque tengamos esta posibilidad siempre deberemos usar el segundo método cuando necesitemos incluir contenido externo. Tengamos claro en todo momento el objetivo de uso de los templates.


6.1. Simular el comportamiento de los templates de AngularJS con JSF 2.

En AngularJS se utiliza la etiqueta <ng-view> para declarar una zona de la página donde insertaremos el contenido de otro html. Haremos uso del servicio $route (en el módulo ngRoute) para especificar el fichero concreto que se cargará en función de la url a la que se acceda.

Simular este comportamiento con JSF 2 es muy sencillo. La inclusión del contenido de ficheros externos se puede hacer con el <ui:incude> que acabamos de ver. Para el enrutamiento podríamos declarar un solo cliente y utilizar los ViewParams para especificar en la propia url cuál es el fichero a incluir en él. No es el objeto de esta entrada explicar los ViewParams, así que nos quedaremos aquí. Siempre que podamos, deberíamos utilizar los recursos que pone a nuestra disposición JSF 2 para escribir aplicaciones de la forma más simple que podamos. Esta última sección solo está para situar el modus operandi de Angular JS dentro del ecosistema de JSF y así poder entender mejor este último si, como yo, venías de ahí. No pretendo aconsejar el uso de una implementación a todas luces retorcida.



7. Código del tutorial.

En mi repositorio de GitHub podéis encontrar un proyecto con todo el código escrito usado a lo largo del tutorial. Para ejecutarlo solo hay que importarlo y desplegarlo en un servidor de aplicaciones a elección. Para navegar por las diferentes páginas clientes solo debemos añadir su nombre al final de la url principal. Aunque también se puede editar el welcome-file en el /src/main/webapp/WEB-INF/web.xml.



8. Conclusiones.

En esta entrada solo he explicado la punta del iceberg en lo relativo al uso de templates con JSF 2. Son una herramienta muy potente para favorecer la reutilización de código en nuestros proyectos web. Además, se pueden llegar a combinar y utilizar templates unos dentro de otros, compartir <ui:insert> entre varios para meterles contenido de una sola vez, declararlos en el <head> del xhtml en lugar de en el <body>, etc. Con las bases dadas en este tutorial se pueden crear páginas muy completas y es dificil que para uso diario se necesite algo más. No obstante, os animo a experimentar para lograr soluciones más complejas a vuestros problemas.



9. Reconocimientos.

Para la creación de mis imágenes he utilizado los siguientes iconos de The Noun Project:

  • Scissors by Boudewijn Mijnlieff from the Noun Project
  • Masking tape by hatayas from the Noun Project

Sobre el IBM Model-M y otros teclados mecánicos

$
0
0

En este post hacemos un repaso a esos dispositivos que conectan nuestro cerebro con el del ordenador: los teclados y en particular a los teclados mecánicos.

Índice de contenidos


1. Introducción

En Autentia soy conocido, entre otras rarezas personales, por mi afición a los teclados mecánicos, especialmente desde que el primer día que me incorporé a la empresa: entré por la puerta con un teclado Cherry de generosas dimensiones, dando la nota (desafinada según algunos) en una oficina llena de ordenadores Apple Mac con sus minimalistas teclados para usuarios “refinados” y de oído sensible.

Al final tuve que retirar de circulación mi teclado mecánico porque el escándalo en un ambiente tan silencioso era evidente, no sin antes ganarme una fama que llevo con orgullo y una ilustración de aquel momento por TaleByTale :).

1

Al menos en mi casa no tengo (casi) ninguna queja del teclado y su particular traqueteo, así que es único lugar en el que puedo disfrutar de este tipo de periféricos… Y tanto lo disfruto que me he decidido a preparar esta entrada en la que intentaré transmitir mi pasión por los teclados mecánicos.


2. ¿Por qué pienso (yo) que son importantes los teclados mecánicos?

Si te dedicas profesionalmente a alguna actividad o al menos pasas la mayor parte del día haciéndola, es importante que utilices las herramientas adecuadas: lo último que tiene que hacer una herramienta es condicionarte para mal y hacerte sufrir.

Algunos ejemplos que se me pasan por la cabeza:

  • En un taller de coches no verás llaves de poca calidad que se pueden mellar o tener poca durabilidad.
  • Si eres aficionado al bricolaje y te gusta trabajar con buen material, seguramente te decantes por un taladro de Bosch de color verde pino, pero los profesionales utilizan la serie verde turquesa de Bosch que seguro que no has visto en tu tienda de bricolage favorita.
  • O un guitarrista profesional no utiliza una guitarra de marca Sonora de 100€ sino una Gibson “made in USA” de 3000€. No sólo porque suena bien (las de 500€ también suenan estupendamente), pero hay cosas que sólo una Gibson te puede transmitir y eso influye en el modo de tocar.

Te aseguro que un profesional de otra profesión no duda a la hora de qué material comprar. Es lo mismo que nos sucede en Autentia con los equipos que utilizamos: MacBook Pro tope de gama y Monitores de 27 pulgadas con resolución 4k…

Pero seguro que estarás pensando que la informática es otro tema. Vale, puedes usar más memoria, procesadores más rápidos o discos duros SSD y pantalla más grande, y eso tiene un impacto directo y demostrado en el rendimiento… pero ¿tan importante es el teclado?

Bueno, no digo que disponer de un teclado mecánico sea un tema vital, pero si te ayuda a estar más cómodo y agusto, deberías tenerlo en cuenta. Cuando empiezo a programar en un ordenador con un teclado al que no me siento acostumbrado me invade una gran frustración que no me permite concentrarme en lo que estoy escribiendo. Y a veces esta sensación no se me va nunca: no me acostumbro al teclado.

El desarrollo de software es una profesión puramente intelectual y creativa (más que puramente, tremendamente creativa diría mejor) y por definición es complicado establecer una métrica de cuánto influye cualquier factor sobre la creatividad de tus ideas. ¿Cómo se mide la creatividad? ¿Cómo se aisla de otros factores?

Hace poco leí y comenté para este portal el libro de Nicolas Carr: ¿Qué está haciendo internet a nuestras mentes?. Una de las cosas que me llamó la atención fue el impacto que le atribuía a las herramientas en el proceso creativo. Así, muchos escritores de libros suelen utilizar máquinas de escribir para desarrollar las primeras versiones de los textos, justo donde la creatividad es más necesaria, aunque las correcciones las hagan a ordenador. Incluso en ese libro, relata que el filósofo (¿pensador mejor?) alemán Nietzche se vio muy condicionado por la utilización de la máquina de escribir. Aquí tienes más información en un blog que he encontrado sobre el tema http://www.papelenblanco.com/creacion/la-maquina-de-escribir-que-se-compro-nietzsche-o-como-cambia-nuestra-escritura-cuando-usamos-un-ordenador-i

¿Y todo esto para justificarme por usar teclados mecánicos? Seguramente sí. No creo que nadie programe mejor ni peor por usar o no teclados mecánicos. Simplemente es una cuestión de estar a gusto escribiendo en el ordenador. Si no encuentras ninguna mejora por usar un teclado mecánico y estás más a gusto con tu teclado slim, te respeto y sobre todo te envidio porque al final uno se acaba gastando más dinero del que debiera en estos artilugios y montando más escándalo del que debiera :)


3. ¿Qué diferencia a un teclado mecánico de un teclado normal?


3.1. Teclados Normales (de membrana)

Normalmente los teclados son “de membrana”, es decir: debajo de cada tecla hay una membrana de goma que pone en contacto dos electrodos cuando se pulsa con suficiente presión. No suele haber elementos mecánicos y las teclas vuelven a subir por efecto de esta membrana gomosa.

Por tanto tienen un tacto gomoso y lineal, es decir, la presión que se necesita ejercer es la misma a lo largo del recorrido de toda la tecla. Es decir, el mecanismo de las maquinitas a pilas de videojuegos que hemos tenidos todos desde niños.

2

El mecanismo de membrana es más barato y sencillo (suele ser una gran membrana para todo el teclado con actuadores por cada tecla) y por eso es el más empleado en la mayoría de los teclados. También es cierto que ocupa menos espacio, sobre todo vertical, lo que permite desarrollar teclados de tipo “slim” que tan famosos son hoy en día desde la popularización de los portátiles, y que se han transmitido a los sobremesa.

Pero este mecanismo tiene un inconveniente: es menos resistente. La razón es sencilla: la membrana de goma, con el uso pierde cualidades al ser deformada tantas veces a través de las pulsaciones, deteriorándose su tacto e incluso rompiéndose. Esto es algo que he podido experimentar en más de una ocasión: trabajar con un teclado durante un par de años y coger el mismo teclado de un ordenador servidor que no utiliza nadie y ver una diferencia abismal en el tacto… a peor para el más usado claro. O ver cómo después de unos cuantos años la tecla del espacio comienza a dejar de funcionar y hay que pulsarla con más fuerza.

Esto es una pena porque he tenido teclados con los que me he sentido muy a gusto, que al cabo de los años estaban para tirar, y el fabricante ya los había dejado de fabricar. De hecho, probé mi primer teclado mecánico ante la frustración de no volver a encontrar un teclado que me encantaba y ya había desgastado.

¡Por cierto!, los teclados de los Apple actuales, que no son nada baratos, también entran dentro de esta categoría de teclados de membrana, aunque seguramente sean de mayor calidad que cualquier teclado de 12€ (o no… es sólo una suposición). Aquí te dejo algunos enlaces a artículos con fotos de los teclados Apple (los portátiles tienen los mismos mecanismos de membranda):


3.2. Teclados Mecánicos

Los teclados mecánicos, por el contrario, tienen un sistema independiente por cada tecla, que esta formado por algún tipo de artilugio mecánico (obviamente) que se encarga de ceder a medida que presionamos la tecla y de subirla de nuevo al terminar la pulsación: muelles o placas de metal. No se trata de una goma que vuelve a su sitio.

La ventaja en este caso de los mecanismos metálicos es que se pueden diseñar teclas que permitan diferentes tipos de intensidad en la presión a lo largo de su desplazamiento, con unos recorridos no lineales (suave al principio y algo maś resistente al final) y también se pueden incluir el típico “click” que nos indica que la tecla ha sido pulsada.

De este modo los teclados mecánicos están ajustados para ser más agradables al tacto, incluso más suaves que los de membrana, y que nos proporciona más información al sentido del tacto sobre si la tecla ha sido pulsada o no. Todo redunda en una mejor experiencia a la hora de usar el teclado.

3

Desafortunadamente los mecanismos de los teclados mecánicos, al llevar tantos componentes físicos son más “altos”, por lo que únicamente se pueden emplear en teclados de tipo alto, es decir, los teclados “grandes” de toda la vida: nada de teclados de portátiles o teclados de tipo slim (como los mac) que tan de moda están ahora. Es cierto que Cherry tiene una variedad para teclas slim, pero no parece tener mucho éxito.

Por el contrario, los teclados mecánicos son prácticamente “eternos” porque sus mecanismos son mucho más resistentes: los muelles no pierden propiedades con el tiempo a esos niveles de presión, y están probados para entornos profesionales. Así que en unos años, por mucho que uses tu teclado mecánico, seguramente siga teniendo el mismo tacto agradable, al contrario que los teclados de membrana.


4. ¿Qué teclados mecánicos podemos encontrar hoy en día?

Desde la aparición y popularización de los teclados de membrana, los teclados mecánicos han quedado reducidos al ámbito profesional. Pero no me estoy refiriendo al ámbito profesional de oficina o de informática, sino de profesiones en las que se les va a dar un uso digamos “fuerte”.

Es normal que encontremos teclados mecánicos adosados a maquinaria de coste elevado y que necesitan un gran rendimiento y no pueden fallar: cajas registradoras de supermercado, maquinaria industrial que se maneja a través de un ordenador; incluso los he llegado a ver en entornos médicos… aunque también, si te rascas el bolsillo podrás tener tu propio teclado mecánico aunque sea para jugar al Call of Dutty.

Ante una demanda relativamente escasa es normal encontrarse un mercado monopolizado: al resto de marcas no les interesa competir en un mercado tan residual. En este caso el fabricante más importante es Cherry (oficialmente ZF Electronics GmbH), que fabrican teclados desde 1967, y que como no podría ser de otro modo son alemanes (les encantan las cosas relacionadas con el trabajo).

Pero no pienses que tienes que comprar un teclado Cherry para tener un teclado mecánico: afortunadamente Cherry fabrica los mecanismos de cada una de las teclas, así que cualquier otro fabricante puede incorporarlos en sus teclados. Esto nos da una gran variedad de teclados diferentes que emplean sus exitosos y más que probados mecanismos.

6

Imagen por: https://deskthority.net/wiki/User:Daniel_beardsmore

Entonces un teclado mecánico emplea (salvo contadísimas excepciones) actuadores o mecanismos Cherry MX y luego se le añaden características a gusto del consumidor como:

  • Diferentes layouts a la moda con decoraciones específicas.
  • Carcasas de teclas de mejor calidad.
  • Iluminación (incluso en colores controlable por cada tecla.)
  • Teclas de funciones específicas: multimedia, para videojuegos programables…

Pero lo importante son los mecanismos. En esta página por ejemplo describen con detalle los tipos de mecanismos:https://deskthority.net/wiki/Cherry_MX, o en esta otra http://www.wasdkeyboards.com/mechanical-keyboard-guide puedes encontrar las gráficas de Fuerza VS Recorrido para que te hagas una idea de las características de cada modelo de switch. Vamos a ver los principales:

  • Cherry MX Blue: son mis favoritos para teclear, pero seguramente los más escandalosos si no estás solo en la habitación. Son muy ligeros (50g) y hacen “click” (feedback sonoro y táctil), notando a mitad de la pulsación que ésta se ha realizado. Hay gente que opina que son demasiado blandos.
  • Cherry MX Brown: son los sustitutos silenciosos de los blue, ya que son muy ligeros también (45g) pero no tienen el efecto clicky que tanto gusta (al menos a mí).
  • Cherry MX Black: son completamente lineales, lo que les hace bastante parecidos a un teclado de membrana. Son algo más duros que los Blue, con una fuerza de unos 60 gramos para su activación, y eso se nota. Por el contrario no hacen ruido.
  • Cherry MX Red: la versión ligera de los Black: muy ligeros, sólo 45 gramos para activarlos pero no tienen feedback sonoro. Especiales para videojuegos, ya que también son lineales.
  • Cherry Mx Green: los más duros (80 gramos) pero a la vez hacen ruido (clicky).

Así que tienes una buena variedad para elegir. Si tienes duda acerca de qué tipo de mecanismo es el que más se adapta a tus necesidades, siempre puedes comprar un kit de muestra que incluye los 6 tipos principales de switches de Cherry MX por unos 15€. Por ejemplo aquí: http://www.wasdkeyboards.com/index.php/products/sampler-kit/wasd-6-key-cherry-mx-switch-tester.html

5



5. Algunos Teclados Mecánicos Interesantes

Ahora que ya te he explicado que los teclados mecánicos que nos vamos a encontrar están formados por los mecanismos de Cherry MX que elijamos, vamos a ver algunos de los teclados mecánicos más conocidos.

Podemos dividir en dos conjuntos: los teclados para desarrolladores y los teclados para gammers, que últimamente están pasándose al mundo mecánico con productos cada vez más avanzados. Algunos de los más conocidos.

  • Cherry G80-3000: es la implementación básica de teclado de la propia Cherry. Yo tengo dos: uno con Cherry MX Blue y otro con Cherry MX Black, ambos extendidos (el único que hay).

    Se trata de un teclado de construcción barata con lo justo para poder poner los actuadores Cherry MX: por no tener no tiene ni tornillos (encaja con pestañas). Es el teclado mecánico más económico que se puede comprar… eso sí, es complicado de localizar a buen precio dependiendo del país.

    Mis dos ejemplares los tuve que adquirir a través de eBay en Alemania y me costaron entre 50 y 60 € más gastos de envío (sí, tengo las teclas en alemán… ¿qué más da?). No tiene ningún alarde de diseño ni teclas especiales multimedia ni nada destacado, pero es un buen teclado. Si te decides a comprar unos, asegúrate, mirando el código que acompaña al “g80-3000” de que lleva el color de mecanismos Cherry MX que quieres. Aquí puedes ver los dos míos:

    7

    Y aquí la prueba del mecanismo de tipo Cherry MX Blue:

    8

  • Das Keyboard : son teclados mecánicos de alta calidad de construcción… y bastante elitistas. Llevan bastante tiempo en un mercado muy especializado y son famosos por vender versiones sin serigrafiado en las teclas (todas las teclas iguales, indistinguibles). Tiene versiones con los mecanismos más demandados (blue, brown…), extendidas y compactos, y como ahora está de moda, también teclados para gammers, con más teclas de función y decorados. Eso sí, a partir de los 130$.

    9

  • Filco: otros clásicos dentro de este mundo. A partr de más de unos 120€. Tienen versiones compactas que son muy apreciadas porque ocupan el espacio mínimo e imprescindible para las teclas mínimas y nada más (teclado de portátil), como el Filco Majestouch Minila (http://www.keyboardco.com/keyboard/majestouch-minila-mechanical-keyboards.asp

    10

  • Razer: que se sube al carro de los teclados mecánicos con los conocidos BlackWidow, que son todo lo que un gammer puede esperar: utilizan los Cherry MX Red, Blue o Green, dependiendo del modelo y están orientados a gamming. Sobre todo destacan por las teclas de acceso directo y por poser una retroiluminación (modelo Chroma) que permite iluminar las teclas de cualquier color. Además tienen sus propios interruptores, similares a los Cherry MX pero con otras características propias.

    11

  • Otros: la lista es muy extensa y cada día hay más marcas que se suben al carro de los teclados mecánicos. Por ejemplo Logitech ahora está sacando versiones de gama alta con mecanismos mecánicos (G910), luces y multitud de accesos directos; Cooler Master; Ducky Shine…

  • 12

    12b



6. Algunos Accesorios para Teclados Mecánicos


6.1. Teclas personalizadas

Una de las cosas más interesantes de los teclados mecánicos es que las fundas de las teclas (las keycaps) son intercambiables. Es decir, todas las teclas tienen que acoplarse al mecanismo de los Cherry MX (tiene que encajar). Así, hay empresas que se dedican a vender teclas para dar un aire más personalizado a tu teclado. Aquí puedes ver algunas de http://www.keypop.net/

13

14



6.2. Aros silenciadores de impactos

Otro accesorio que puede venir bien para tus compañeros que sufren el tormento de escucharte teclear con ese martillo neumático a USB, son las gomas que mitigan los impactos de las teclas al final del recorrido de la pulsación. Se trata de unos aritos de goma o silicona que se ponen debajo de cada una de las teclas.

No son muy caros y son fáciles de poner, aunque en mi experiencia le quitan un poco de tacto al teclado, haciendo que sea más gomoso en el impacto final, aunque no interfiere excesivamente en la pulsación: los clicks siguen sonando, pero se elimina el golpe de la tecla al final del mecanismo, que no es poco ruido.

Por 15$ más gastos de envío, en el siguiente enlace puedes conseguir 125 piezas de estas anillas para un teclado con Cherry MX: http://www.wasdkeyboards.com/index.php/products/keyboard-accessories/cherry-mx-rubber-o-ring-switch-dampeners-125pcs.html. Seguro que en eBay o en otros lugares las puedes conseguir más baratas. Se acoplan a cualquier teclado con mecanismo Cherry MX.

15


7. IBM Model-M. El teclado de teclados

Pero si hay un teclado mítico dentro del mundo de los teclados mecánicos este es el Model-M de IBM. Representa lo que una Harley Davidson al mundo de las motos o lo que es un Ford Mustang Fastback al mundo de los coches: grandes, pesados, ruidosos y muy placenteros de usar… y además es la excusa para escribir este post.

Un día Alejandro Pérez iba a deshacerse de un ejemplar que tenía desde hace mucho tiempo y que no utilizaba apenas. Conocía de mi afición por los teclados mecánicos y ya sabía quién le iba a dar un buen uso… y qué mejor forma de gradecérselo que con un post homenaje a esta joya del hardware.

Seguro que si tienes cierta edad y ves una foto del IBM Model-M dirás que en su día tuviste o trabajaste con uno, pero quizá tu memoria te esté jugando una mala pasada. En la época del Model-M casi todos los teclados tenían el mismo aspecto exterior, pero no muchos eran Model-M. En realidad muchos todos eran simples inspiraciones en membrana.



7.1. Qué hace tan especial al IBM Model-M frente a otros teclados mecánicos?

Proviene de una época en la que se buscaban hacer buenos productos para una audiencia que se los podía permitir y los apreciaba. Los ordenadores eran un artículo de lujo que poca gente podía tener y por tanto su público exigía unos acabados y prestaciones a la altura. Se cuidaban todos los detalles, y conceptos como la obsolescencia programada o la manufactura asiática eran algo inconcebible.

No es lo mismo leerlo que vivirlo. Si te gustan las cosas bien construídas y en especial los teclados, se aprecia a primera vista la calidad del teclado. Hay algunas cosas que le diferencian de otros teclados (incluso los mecánicos modernos), como por ejemplo:

Su construcción es de otro planeta comparada con otros teclados de hoy en día: plásticos de buena calidad que hoy, más de 20 años después lucen como nuevos tras una simple limpieza con un jabón neutro. Aquí tienes una foto general y en detalle:

27

Nada que ver con los típicos Logitech y otras marcas conocidas que con el tiempo amarillean…¿por eso está de moda hacerlos de color negro?).

16

La base del chasis es una placa metálica para darle peso (casi 2 kilos) y solidez a la estructura. Se nota bastante a la hora de teclear. No es que otros teclados se muevan, pero el IBM Model-M tiene un tacto especial… no hay vibraciones al teclear: tienes la impresión de estar tecleando sobre algo muy sólido y preciso.

17

Foto por Blacke Patterson

Esto hace que sea un teclado voluminoso y pesado. Dio 1930 gramos en la báscula de la cocina :).

25

Y aquí lo tienes comparado con un generoso teclado Cherry G80, que ya de por sí es grande comparado con los teclados actuales:

26

Si mis teclados Cherry no tienen tornillos sino pestañas, este IBM Model-M directamente no tiene tornillos de cabeza de estrella sino hexagonales para una llave de tubo.

18

La apariencia una vez quitadas las teclas, en la que >se puede ver cómo los mecanismos internos están protegidos por las teclas, que actúan de tapón. Es imposible que caiga suciedad o líquidos dentro del mecanismo si las teclas están puestas. Además el plástico es de gran calidad. Los mecanismos están adheridos al chasis inferior.

19

O que las teclas estén divididas en dos partes: la propia tecla exterior que está en contacto con los dedos y la que contiene la parte que está en contacto con el mecanimo interior. La serigrafía también es de muy alta calidad y resistencia, y en todo este tiempo y uso que tiene este teclado no se ha visto ningún tipo de desgaste.

20

Desafortunadamente no tiene tecla “Win” o “Mac”, ya que es anterior a éstos, lo que hoy en día se soluciona con cualquier tipo de programa de mapeo de teclas. Lo habitual es cambiar el bloqueo mayúsculas que apenas se usa por esta tecla. Fácil solución.

Otro detalle curioso es que la barra espaciadora tiene una especie de conexión que parece hacer de anclaje antiestático (eso me parece, no encuentro otra explicación).

21

Finalmente, este ejemplar de 1993 tiene el cable extraíble PS/2, conectándose a través de una especie de conector telefónico o ethernet. Es curiso que se trata de un cable de tipo telefónico de unos 4 metros de longitud. Seguramente en aquella época las CPU se colocaban bastante lejos. Tengo el recuerdo de que los cables de conexión de los monitores tamién eran bastante largos.

22

¡Ah! Y la etiqueta que indica el modelo y que está manufacturado en “United Kingdom”… de cuando las cosas se hacían bien…

23



7.2. El mecanismo

No esperes que el teclado de IBM lleve los actuadores Cherry MX… Es un teclado que comenzó a fabricarse en 1984 y cuyos mecanismos provienen de otros modelos anteriores de IBM que datan de 1981.

Se trata del mecanismo “Buckling Spring”. Consiste en un muelle de cierta longitud por cada una de las teclas, que al ser presionado y doblarse, choca contra las paredes entre las que está encerrado (como un cilindro) y no le queda más remedio que traspasar la fuerza de la pulsación a una placa metálica inferior que percibe la pulsación. Así se consigue que la depresión de la tecla se lleve a cabo en dos fases: una suave en la que el muelle se dobla y no comprime, y otra en la que al no poder doblarse más, se comprime y pasa la fuerza a la placa. El resultado es un mecanismo de tacto característico y muy agradable de teclear. En la entrada de la Wikipedia tienes unos esquemas muy claros sobre estas fases (pulsa en la imagen para ver el Gif animado):

Bucklingspring-animation-300ms

Como el paso del tiempo se ha encargado de demostrar, es un mecanismo agradable, fiable y resistente. A día de hoy, los que han sabido conservar los teclados IBM Model-M los siguen utilizando sin pérdida en prestaciones. Desde luego que otros teclados no han soportado este ritmo.



7.3. Interfaz PS/2

Una de las malas noticias si usas un teclado de este tipo es que, al ser antiguos, no están preparados para USB. Tendrás que adquirir un interfaz adecuado. Hay de dos tipos:

  • 1. Conexión directa: que hacen corresponder cada pin de PS/2 a cada pin de USB. Por tanto son como cables sin ningún procesamiento. Para ello el teclado o periférico correspondiente tiene que estar preparado. Este no nos vale para nada.
  • 2. Adaptador: que además de conectar los dispositivos, realiza un procesamiento de conversión. Es el apto para este tipo de necesidades en la que el teclado o periférico no es consciente de que va a ser conectado a un USB.

En mi caso me decanté por uno de la empresa StarTech que está preparado especialmente para estos teclados viejos. Tranquilo, vale poco más de 10€ en Amazon: StarTech.com USB to PS2 Keyboard and Mouse Adapter, male/female, Negro, Any Windows

24

Por cierto, he leído que los teclados PS/2 son además cotizados por los gammers porque soportan varias pulsaciones simultáneas de teclas, aprovechan a combinar diferentes tipos de switch y soportan mayor estrés , algo en los que los teclados de membrana están más limitados. No obstante, este Model-M solamente reconoce dos teclas a la vez.



7.4. Partes negativas

Entre tanta alabanza hay que ser un poco objetivo y citar algunas cosas que debes tener en cuenta, aunque sólo sea por mejorar la credibilidad de este post:

1. El sonido: es el gran “pero” de los teclados mecánicos, y por lo cual te odiarán en tu empresa si te lo llevas a la oficina. O por el cual despertarás a tus vecinos si te pones a programar de madrugada.

¿Que si suena mucho? La verdad es que Youtube está plagado de vídeos de gente tecleando con sus Model-M, así que te puedes hacer una idea por ti mismo de lo que suenan sus diferentes variedades: https://www.youtube.com/results?search_query=ibm+model+m+sound

2. Funcionalidad: es complicado conseguir una versión que tenga la tecla “win” (o “cmd” en Mac). De hecho no tengo claro que en alguno original de IBM la tuviera jamás (en aquella época el gigante azul intentaba luchar en el mercado de los sistemas operativos con OS/2 Warp). Esto se soluciona fácil con scripts en cada sistema operativo para cambiar la funcionalidad del “Caps Lock” por esa tecla. Tampoco tienen botones multimedia obviamente, como control de volumen o accesos directos… A mí no me suponen grandes problemas la verdad.

3. Estética: si te gustan las cosas vintage no hay problema, pero si eres un adolescente amante de los videojuegos pues igual suena raro. Yo como soy viejuno, me encanta tener una herramienta tan clásica y representativa conectada a un ordenador moderno.

4. Portabilidad y espacio: es grande y pesado… También los Ford Mustang V8 son complicados de conducir en calles estrechas por su anchura y longitud… pero… ¿quién quiere conducir un Mustang por ciudad? Esto es una cosa seria.



7.5. El mercado de segunda mano

¿Y dónde consigo un IBM Model-M? Quizá en su día estuvieses rodeado de ellos en las oficinas donde trabajabas o en el centro en el que estudiabas… pero ya sabemos que la informática “nos hace prisioneros” en su evolución, y la mayoría de ellos acabaron en un vertedero. Es una pena, pero seguramente fuese así.

No obstante, como has visto por sus cualidades tienen su demanda por los entusiastas de los teclados. Así que hay gente que, enterada de esta demanda, o porque saben apreciar lo bueno, o porque se lo quedaron como artículo “vintage”, tiene algunos de ellos, e incluso los han puesto a la venta.

Dependiendo del estado del teclado, modelo (los compactos sin teclado numérico son más cotizados), o de la urgencia del vendedor, los puedes encontrar desde unos 50€ hasta 400€ o más euros. Sí, se pagan cientos de euros por uno en buen estado… ¿A que ahora no tiene tanta gracia cuando los operarios de limpieza lanzaban al contenedor los viejos ordenadores IBM personales de la empresa en la que trabajabas y los cambiaban por unos nuevos Dell con teclados con controles para manejar los altavoces de plástico que venían en el pack?

La gente los compra incluso para tener piezas de repuesto, ya que se dejaron de fabricar en 1996. Y también para especular como cualquier otra inversión.

¿Dónde comprarlos?

  • El sitio por antonomasia es ebay. Tiene hasta una página específica: http://www.ebay.com/bhp/ibm-model-m
  • En clickykeyboards.com/ se dedican a restaurar teclados Model-M. Los venden por ente 85 y 250$, dependiendo si está fabricado por IBM o por alguna fábrica con licencia como Lexmark, aunque dicen que no tienen los mismos estándares de calidad que IBM…
  • O puedes comprar uno nuevo del fabricante Unicomp en pckeyboard.com por unos 80-100$. Por fuera son similares, pero dicen que no es lo mismo… no lo sé… pero seguro que mejor que uno de membrana sí que es :-)

Ya sabes, corre al almacén de tu empresa, es posible que todavía quede alguno cubierto de polvo y puedas hacerte de oro :-). Aunque ya te aseguro que posiblemente no fuese un Model-M de verdad…



8. Conclusiones

Hemos hecho un repaso a los teclados mecánicos que existen hoy en día, viendo sus principales características e intentando justificar una pasión totalmente subjetiva. Aunque seguramente no te hagan mejor programador o mejor profesional delante del ordenador, disponer de unos medios adecuados y agradables hacen el trabajo más llevadero y quizá, sólo quizá, mejoren un poco tu creatividad.

El mercado de los teclados mecánicos actuales están copados por los mecanismos Cherry MX, que son montados por la mayoría de fabricantes. Estos mecanismos existen en diferentes variedades que le dan un tacto diferente a gusto del usuario: unos más blandos y con sonido, otros más duros, otros con menos recorrido de activación…

Por otra parte en los últimos años se está incrementando la demanda de teclados mecánicos, quizá por una corriente de “hipsterismo” o intentar diferenciarse del resto de usuarios de ordenadores ahora que la informática se han convertido en un producto de consumo. Da igual, el caso es que hay una mayor variedad en la oferta, y los precios se están comenzando a moderar. También surgen alternativas en el mundo del gamming, donde se aprecian las cualidades de tacto y durabilidad de estos aparatos.

Finalmente, hemos visto que dentro de los teclados mecánicos, destaca un viejo conocido de los años 80: el IBM Model-M. Todavía sigue siendo un teclado muy apreciado por el público especializado, y tiene su cuota en el mercado de segunda mano y restauración. En esta entrada hemos visto las características que lo hacen tan especial.


P.S. Siento que haya quedado un post tan largo y seguramente aburrido, pero es culpa de mi subconsciente que me hace escribir textos largos para que disfrute del Model-M 😉

Eliminar Croma de una imagen en movimiento con Adobe After Effects CC

$
0
0
Este tutorial explica como eliminar el fondo verde de una imagen utilizando el efecto Keylight 1.2 de Adobe After Effects CC.

Índice de contenidos

1. Introducción

El croma es una técnica audiovisual que consiste en extraer un color de una imagen (por lo general el verde o el azul, ya que son los tonos más alejados del color de la piel) y sustituirla por otra, lo que nos permite, por ejemplo, cambiar el fondo de una imagen sin tener que desplazarse al lugar para grabar.

Para que el resultado sea satisfactorio tenemos que asegurarnos que el personaje no lleve ninguna prenda del mismo tono que el croma y además éste debe estar iluminado de manera uniforme, lo que nos facilitará la posterior edición.

En este caso utilizaremos un efecto que nos ofrece After Effects CC (y sus anteriores ediciones) llamado Keylight 1.2.

2. Entorno

El tutorial está escrito usando el siguiente entorno:
  • Hardware: Portátil MacBook Pro 15′ (2,2 Ghz Intel Core i7, 8GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11
  • Entorno de desarrollo: Adobe After Effects CC

3. Gestión de proyecto

En primer lugar creamos un nuevo proyecto en After Effects. Por defecto nos pide hacer una nueva composición, pidiéndonos las características de resolución, duración, etc. Si no las sabemos no pasa nada ya que después crearemos otra con las características de nuestro vídeo.


Interfaz b

Como veis nos crea la composición por defecto pero no la utilizaremos, por lo que la podemos eliminar. El siguiente paso es importar los materiales que vamos a necesitar pulsando sobre el botón derecho en la ventana proyecto o en la barra de herramientas Archivo -> Importar.

En este caso para el ejemplo usaremos el vídeo de la cabecera de “Ni Monos Ni Lagartos” y una fotografía fija de un fondo.


Captura de pantalla 2016-05-06 a las 9.56.21 Captura de pantalla 2016-05-06 a las 9.56.39

Una vez importados los archivos procedemos a crear la composición. Si lo que queremos es una composición con la misma resolución y duración que nuestro vídeo, lo más sencillo es arrastrar el video hasta el icono de composición que encontramos en la parte inferior de la ventana proyecto.


Ventana proyecto Icono nueva composición

4. Efecto Keylight 1.2

Una vez tenemos la composición la abrimos pinchando dos veces sobre ella y con la capa del vídeo seleccionada dentro de la composición buscamos el efecto Keylight 1.2 en Efecto-> Incrustación -> Keylight 1.2 y se lo aplicamos, lo que nos dará lugar a la siguiente ventana de controles en la que realizaremos los ajustes.


Captura de pantalla 2016-05-06 a las 10.15.59

En primer lugar con el cuentagotas seleccionaremos el tono que queremos eliminar (si mantenemos pulsada la tecla Alt mientras arrastramos con el cuentagotas sobre la pantalla nos muestra qué nos elimina, lo que es muy útil cuando tenemos varios tonos o arrugas en el croma ya que nos permite ver en qué zona estamos eliminando más verde). Cuando ya tenemos eliminado el fondo cambiamos en view el modo a Screen matte, lo que nos permite ver la máscara, es decir, nos muestra la imagen en escala de grises en las que los blancos son zonas opacas y los negros zonas en las que veríamos la imagen del fondo.

Al cambiarlo el resultado es el siguiente:


Captura de pantalla 2016-05-06 a las 10.28.14

5. Ajustar

Aunque en ocasiones sólo con realizar los pasos anteriores obtenemos un resultado satisfactorio, como podéis ver en este caso no se ha realizado del todo bien aunque aparentemente creamos que sí, ya que la zona del suelo no es blanca, y en la parte de la pared aún tenemos manchas lo que significa que no hemos eliminado todos los verdes.

Para afinar esto desplegamos la pestaña screen matte en el control de efectos y subimos clip black hasta que desaparezcan las manchas del fondo y bajamos clip white hasta que tanto la silueta como el suelo sean blancos.


Captura de pantalla 2016-05-06 a las 11.09.32

Una vez hecho esto volvemos a cambiar en View el modo a “final result” y comprobamos el resultado. Para que sea más preciso ponemos el tamaño de imagen al 100% en la parte inferior de la pantalla de previsualización para ver la imagen a tamaño real.


Captura de pantalla 2016-05-06 a las 11.13.45

Como podemos comprobar aún tenemos un pequeño halo alrededor de la silueta. Para eliminar esto, en el controlador de efectos y dentro de “Screen mate”, utilizamos “Screen Shrink/Gro”, lo que expande o contrae los bordes hasta eliminarlo y posteriormente “Screen Softness” para aplicar un calado o difuminado que ayudará a que la imagen se incruste mejor con el fondo.


Captura de pantalla 2016-05-06 a las 11.19.15

Por último, sólo nos queda añadir a nuestra composición el fondo que queramos ver detrás, colocándolo debajo del vídeo en el Timeline.

En caso de que nuestra imagen o vídeo tenga mayor calidad o tamaño que el vídeo original y necesitemos realizar cambios, podemos hacerlo pinchando en el Timeline en la capa que corresponde a nuestra imagen y pulsando “S” para la escala, “P” para la posición, “R” para la rotación y “T” para la opacidad.

6. Conclusiones

Captura de pantalla 2016-05-06 a las 11.22.38

Como habéis podido comprobar, es una manera bastante sencilla que da en general buenos resultados y nos ofrece muchas posibilidades, ya que aunque en este caso hemos utilizado una imagen fija para el fondo, podemos añadir lo que nos parezca, ya sean fotos o vídeos, a partir de aquí, ¡depende de vuestra imaginación!.

Ejecutando test de SoapUI Open Source en JUnit en un proyecto Maven

$
0
0

En este tutorial vamos a ver cómo podemos ejecutar los tests de SoapUI desde nuestros @Test de JUnit en un proyecto Maven.

0. Indice de contenidos

1. Introducción

El desarrollo de aplicaciones actualmente pasa por exponer o consumir ciertos servicios: bien por arquitecturas orientadas a servicios (SOA), por la publicación de servicios REST para comunicarse con el mundo exterior, o bien por el boom de los microservicios, que dividen los grandes monolitos en pequeñas microaplicaciones que se comunican a través de servicios empleando unos u otros protocolos.

Esto nos lleva inevitablemente, dentro de un desarrollo responsable en el que existe el testing, a tener que probar estos servicios a través de test de integración. Estos test los podemos programar con Java, o para algunos casos, podemos usar herramientas especializadas como SoapUI.

En este tutorial vamos a cubrir el espacio que hay entre nuestros test unitarios como desarrolladores realizados en JUnit o equivalentes, y los test realizados con la herramienta SoapUI: automatizaremos el lanzamiento y recogida de resultados de los test de SoapUI desde nuestro código Java. De este modo podremos incluir los test de SoapUI dentro del entorno de integración continua, como un test (de integración) más.

¿Y por qué queremos hacerlo? Pues ya deberías saber a estas alturas que una de las máximas de los test: sirven para que los desarrolladores podamos regular nuestro nivel de stress :). Cuanto más y mejores son los test, menos stress tendremos.

2. Entorno

Cualquier sistema que pueda ejecutar un proyecto maven y SoapUI nos vale:

  • Hardware: Acer Aspire One 753 (2 Ghz Intel Celeron, 4GB DDR2)
  • Sistema Operativo:Ubuntu 16.04
  • Soap UI Open Source versión 5.2.1
  • Eclipse Mars
  • Java 1.7, Maven 3

El código de este tutorial puede ser descargado en: https://github.com/4lberto/soapUiJunit

3. Preparando el proyecto de SoapUI

No voy a hablar aquí de SoapUI en detalle porque ya tenemos tutoriales para eso, como por ejemplo: https://www.adictosaltrabajo.com/tutoriales/introduccion-soap-ui/, pero sí que vamos a ver lo mínimo para afrontar este tutorial.

Básicamente, SoapUI es una herramienta gráfica preparada para desarrollar test contra servicios de diferente naturaleza, especialmente SOAP y REST.

Se define un “proyecto” que en su interior contiene una “suite de test”. En cada suite de test se incluyen casos de prueba o “test cases”, dentro de cada uno están los “test steps”, que realizan una operación y que contienen unos asserts de validación. Si todos los “test steps” de un “test case” están en verde (pasan los asserts), el test case estará verde. No es muy complicado.

3.1. El test

Para este tutorial he hecho un test muy sencillo. Consiste en:

  1. Hacer una llamada REST con el verbo GET a http://localhost:3000/rest/NOMBRE
  2. Comprobar que en la respuesta del servidor se devuelve “NOMBRE”

Con esto tendremos suficiente para probar con JUnit que:

  • El test se lanza desde JUnit y devuelve un valor de “success”
  • El valor que se devuelve es el que se ha enviado

3.2. El servidor

Como se trata de tener un servicio REST de ejemplo para el tutorial, simplemente he creado con Express en NodeJS un servidor con una ruta como la siguiente:

var express = require('express');
var router = express.Router();

router.get('/:name', function(req, res, next) {
  var persona={persona:req.params.name};
  res.send(persona);
});

module.exports = router;

Así que para una URL: http://localhost:3000/rest/NOMBRE, devuelve un JSON como el siguiente:

{"persona":"NOMBRE"}

Tú seguro que tienes algún otro servicio que probar que te es mucho más interesante. Cámbialo por el tuyo y te será de utilidad :).

3.3. Preparando el test de SoapUI

Vamos a utilizar la versión Open Source de SoapUI, que nos da menos facilidades que la versión de pago, y que seguramente será la que estás usando si la empleas circunstancialmente. Además, lo que veamos aquí también sirve para la profesional.

Se siguen estos pasos:

  1. Menú File > New REST Project
  2. Incluimos la URL de nuestro servidor, que es http://localhost:3000

    soap1

  3. Abrimos el “resource viewer” que está en la rama debajo de la URL del servidor. Este recurso apuntará a un recuros REST del servidor

    soap2

  4. Introducimos en el campo “Resource Path” el valor /rest/${#TestCase#name} que hace referencia al recurso “rest” y al valor “name” que daremos posteriormente en las propiedades del “TestCase”, que es el nombre que le hemos dado al Test Case. Debería quedar algo así:

    soap103

    .
  5. Haciendo click secundario sobre el proyecto, en el menú indicaremos “New Test Suite”.
  6. Haciendo click secundario sobre el Test Suite, en el menú indicaremos “New Test Case”.
  7. Expandiendo el árbol, llegaremos hasta “Test Step”. Haciendo click secundario > Add Step > REST Reques y seleccionaremos el primer método para la request, que incluye la llamada al recurso:

    Soap102

  8. Finalmente seleccionaremos “Test Case” y abajo de la pantalla a la izquierda seleccionaremos la pestaña “Custom Properties”. Pulsaremos sobre el icono verde “+” y añadiremos una propiedad con los valores “name” y “luis” por ejemplo.

    soap6

Nos queda el paso de validación con los asserts antes de ejecutar el caso. Si no, no devolverá nada (ni verde ni rojo). Es muy fácil:

  1. Hacemos doble click sobre el nombre del paso del test que hemos creado dentro de “Test Steps”. Aparecerá una ventana.
  2. En la ventana, abajo a la izquierda, aparece “Assertions (0)”. Haz click para que se expanda un rectángulo con los asserts. De momento está vacío.
  3. Pulsa sobre la cruz verde para añadir uno nuevo. En la venta elige “Property Content” a la izquierda y a la derecha “Contains”. Pulsa abajo sobre el botón “Add”. Y dentro indica que tiene que contener el valor de la custom property, que es ${#TestCase#name}:

    soap111

Ahora, si tenemos el servidor levantado, podremos ir al Test Case, hacer doble click y ejecutar el caso. Yo he conseguido que funcione, así que tengo un verde y si voy a la respuesta en JSON veo que devuelve “luis”. Cuando usemos JUnit cambiaremos ese “luis” por otra cosa.

soap8

3.4. Las “Custom Properties”

Si has prestado atención, antes he indicado que en el Test Case se debe indicar una “Custom Propertie”, que era “name” y le hemos dado un valor de “luis”.

Esta property será la que personalicemos cuando lancemos el caso desde JUnit, puesto que admite por parámetro un mapa de propiedades. Simplemente indicaremos que hay una propiedad “name” con el valor que indiquemos en el test, sobreescribiendo a “luis”.

Como en SoapUI indicamos tanto para el parámetro como para el assert del paso del test, que lea ${#TestCase#name}, no habrá que editar el SoapUI para cada caso… Por cierto, ${#TestCase#name}, es la notación de scripts de Soap para acceder a una variable declarada.

4. Creando el proyecto de Maven

Ya tenemos el proyecto SoapUI creado, ahora lo vamos a ejecutar con JUnit en un proyecto Maven. Si no usásemos Maven, sino un proyecto de Eclipse normal y corriente, las cosas serían más fáciles… pero no vamos a renunciar a la gestión de dependencias y del ciclo de vida que nos da Maven sólo porque sea más complicado :)

¡Importante! El proyecto de SoapUI que acabamos de crear, lo dejaremos en el directorio src/test/resources del proyecto Maven que estamos creando.

4.1. Incluyendo soapui.jar

Si seguimos las instrucciones oficiales de SoapUI para poder utilizarlo, se trata de ir al directorio “bin” de la instalación de SoapUI y tomar el fichero “soapui-5.2.1.jar” para incluirlo en nuestro proyecto.

Como decía, hacer esto en Eclipse es más sencillo a golpe de Clicks, pero par amaven hay que montar un repositorio. Lo ideal es tener un repositorio como Nexus en el que subir los artefactos y usarlo, pero voy a usar una técnica un poco más “chapuzas” ya que estamos haciendo una prueba “piloto” y cómo no, en sincero homenaje a Ángel de Andrés.

Se trata de copiar el fichero soapui-5.2.1.jar en “src/test/resources” como recurso simple para los test.

Seguidamente se va al pom.xml y se declara un repositorio con la siguiente sintaxis:

<repositories>
...
    <repository>
        <id>my-local-repo</id>
        <url>${project.basedir}/src/test/resources/</url>
    </repository>
...
</repositories>

con esto indicamos que hay un repositorio de archivos en esa dirección. Y luego usamos el artefacto:

<dependency>
    <groupId>com.smartbear.soapui</groupId>
    <artifactId>soapui</artifactId>
    <version>5.2.1</version>
</dependency>

¡Ojo! ¡No uses esto en producción ni para nada que requiera un mínimo de seriedad

4.2. El resto de dependencias

Aquí está la mayor dificultad de este tutorial. La librería de SoapUI tiene bastante dependencias que hay que resolver. Según su documentación oficial, se debe incluir el directorio “lib” de la instalación de SoapUI en el proyecto, que son decenas de archivos .jar… E incluirlos en Eclipse es fácil, pero en Maven hay que ir uno por uno.

En un primer intento traté de localizar los artefactos en maven central, pero es una tarea demasiado complicada.

Al final decidí ir al repositorio de GitHub de la versión Open Source y “robarles” las dependencias de su pom.xml.

Dentro de toda la estructura de SoapUI Open Source, se puede ver que hay 4 proyectos hijos de maven, y dentro de uno de ellos, “soapui”, están las dependencias que necesitamos. Concretamente en https://github.com/SmartBear/soapui/blob/next/soapui/pom.xml. Bueno, en realidad hay más… he intentado limpiar algunas en mi versión de GitHub que puedes consultar aquí:https://github.com/4lberto/soapUiJunit

Si todo está bien, al menos en mi repositorio de GitHub a la hora de hacer este tutorial, debería estar todo lo necesario para poder trabajar con la versión 5.2.1

5. Creando los @Tests de JUnit

Y ahora llegamos a la parte buena… la de los test de JUnit donde vamos a utilizar las librerías que hemos incluido antes para ejecutar el test…

Hay dos métodos para ejecutar nuestro test:

  • Ejecución de todos los test del fichero de SoapUI que estemos manejando.
  • Ejecutar solamente un test case particular.

Vamos a ver las diferencias de código entre uno y otro.

Por cierto, el fichero que hemos creado anteriormente con los test, estará en: “src/test/resources/REST-Project-1-soapui-project.xml”

5.1. Ejecución de todos los test del fichero.

Tienen la parte negativa de que no permite hacer asserts sobre el resultado, ya que este es global, por lo que el control es mejor. Quizá si preparamos los tests en SoapUI de forma adecuada nos pueda servir.

private static final String TEST_FILE = "src/test/resources/REST-Project-1-soapui-project.xml";

@Test
	public void shouldExecuteAllTestCases() throws Exception{
			SoapUITestCaseRunner soapUITestCaseRunner = new SoapUITestCaseRunner();
		    soapUITestCaseRunner.setProjectFile(TEST_FILE);
		    soapUITestCaseRunner.run();
		    //No asserts in this mode
	}

Como puedes ver no hay asserts en esta parte. Podrías tomar el resultado del método “run()” que es un boolean para hacer una comprobación y que saliese el semáforo verde en JUnit…

¡Ah! Este es el modo que se usa cuando se emplea como plugin de maven para ejecutar los test de un fichero cuando se ejecuta como tarea.

5.2. Ejecución de TestCase específicos de SoapUI.

Pero también podemos ejecutar los casos de test específicos que contenga el proyecto de SoapUI, y lo que es mejor, nos permite, entre otras cosas:

  • 1. Personalizar los parámetros de entrada.
  • 2. Recoger el resultado del test y poder incluirlo en un assert.

Aquí tenemos un ejemplo de @Test de JUnit que ejecuta un testCase llamado “TestCase” dentro de “TestSuite” 10 veces con diferente parámetro:

private static final String TEST_FILE = "src/test/resources/REST-Project-1-soapui-project.xml";

@Test
	public void shouldExecuteSpecificTestCase() throws XmlException, IOException, SoapUIException {
		// given
		WsdlProject project = new WsdlProject(TEST_FILE);
		TestSuite testSuite = project.getTestSuiteByName("TestSuite");
		TestCase testCase = testSuite.getTestCaseByName("TestCase");

		for (int i = 0; i < 10; i++) {

			PropertiesMap propertiesMap = new PropertiesMap();
			//Sets parameter defined in Soap UI teste case
			testCase.setPropertyValue("name", "aName_" + i);

			TestRunner runner = testCase.run(propertiesMap, false);
			assertEquals(Status.FINISHED, runner.getStatus());
		}
	}

Por cada uno de ellos se hace una comprobación del estado para asegurarnos de que cada test ha acabado correctamente. Ten en cuenta que esto está condicionado por los asserts que se hayan puesto en el test de SoapUI…

En los JavaDoc de la API de SoapUI Open Source (http://www.soapui.org/apidocs/index.html) podemos encontrar más métodos del TestRunner que nos devuelven más información sobre cómo ha ido el test:

Una cosa tremendamente interesante es la posibilidad de establecer aquí las propiedades a nivel de Test Case. Si recuerdas, al hacer el test en SoapUI hemos indicado que en el Test Case haya una propiedad llamada “name” y que tenía un valor llamado “luis”. Ahí, te dije que se podría incluir un mapa para alterar ese parámetro en cada llamada. Pues ahí lo tienes:

PropertiesMap propertiesMap = new PropertiesMap();

testCase.setPropertyValue("name", "aName_" + i);

TestRunner runner = testCase.run(propertiesMap, false);

Entonces en cada iteración del bucle, se mandará “aName_1”, “aName_2″… Esta es la traza del servidor por si no te lo crees :).

soap9

Y como se cumplen los asserts de SoapUI y por tanto el estado de los test es Finished, todo aparece en verde :)

soap11

6. Ficheros extra de salida

Cuando ejecutes los test, verás que en el directorio raíz del entorno de ejecución (en este caso la base desde la que salen “src”), aparecerán unos ficheros de log con trazas sobre las operaciones de test que se han hecho con SoapUI. Por enumerarlos:

  • global-groovy.log: la salida de los scripts de setup y tear-down que se han escrito en SoapUI utilizando Groovy. En este ejemplo dejado una simple traza con log.info.
  • soapui-errors.log: trazas de error de SoapUI por no encontrar clases por ejemplo.
  • soapui.log: el log de SoapUI al ejecutarse.

Simplemente ten en cuenta estos ficheros si las cosas no van bien… ¡te servirán de gran ayuda para rastrear los problemas!.

7. Conclusiones

En este tutorial hemos visto cómo podemos automatizar utilizando JUnit el lanzamiento de test de SoapUI Open Source, y así poder incluirlo en nuestra “red de seguridad” de desarrollo ágil, es decir, en nuestro sistema de integración continua.

Aunque existen otras alternativas, como por ejemplo utilizar un plugin de Maven o adquirir la versión Profesional del producto (de pago) o emplear el lanzamiento desde la línea de comandos, utilizar JUnit puede facilitar la realización de comprobaciones de grano fino y más controlables, incluso parametrizando las entradas para test de stress o comprobar un gran número de parámetros.

Espero que te sirva de ayuda para dormir un poco más tranquilo, estando seguro de que el código de tus servicios REST o SOAP está más que probado :)

Pasado y presente de las Librerías de Iconos

$
0
0

Cambios en las librerías de iconos según la evolución de la web: desde los sprites hasta los glyphos.

Índice de contenidos


1. Introducción

No hace mucho mantuve una conversación donde descubrí que algo que yo creía muy conocido, no lo era tanto. Se trataba de cómo están hechas y funcionan las librerías de iconos. Me sirvió para darme cuenta que quizás sería conveniente reseñarlo en un artículo.

La naturaleza de estas librerías y porqué están hechas así, responde a la evolución misma de la web.


2. Colecciones de Iconos

En los albores de internet se quería disponer de un juego de iconos y usar sólo aquellos que necesitáramos. Así llegábamos a descargar un zip que solía contener una carpeta “icons” donde estaban cada una de las imágenes. Normalmente estaban en formato GIF, que era el único formato web que permitía fondo transparente, hasta que apareciera PNG. GIF era formato propietario (de CompuServe) pero lo soportaban todos los navegadores, mientras que PNG era estándar, pero Internet Explorer, que era el navegador más utilizado entonces, no soportaba su transparencia. Así que durante bastante tiempo siguieron siendo ficheros GIF, que además permitían una versión que mostraba secuencias de imágenes, conocidos como “GIFs animados”.

Se usaban como si se cargaran imágenes individuales en la web.

<img src="http://homepage.ufp.pt/cmanso/icon-home.gif" width="32" height="32" border="0" />
<img src="http://homepage.ufp.pt/cmanso/icon-mail.gif" width="32" height="32" border="0" />
<img src="http://homepage.ufp.pt/cmanso/icon-mundo-find.gif" width="32" height="32" border="0" />

Esta forma de utilizar los iconos como imágenes individuales en la página web tenía la ventaja de que cargábamos sólo los iconos que usábamos, pero tenía el inconveniente que por cada icono se hacía una petición al servidor web. Si había muchos iconos, se ralentizaba enormemente la carga de la página.


3. Sprites

En aras de solucionar ese problema, esas librerías se compactaron en un único lienzo sobre el que se ponían todos los iconos, de forma que sólo se hiciera una petición al servidor. Ha pasado el tiempo, y este fichero ya sí es un PNG, que se cachea en el navegador la primera vez que se pide. De esta forma, se acelera mucho la carga del site, pero ¿cómo mostramos sólo el icono adecuado cada vez?. Eso se hace asignando un hueco al icono y desplazando el lienzo con los iconos por debajo. Una técnica que se llama CSS Sprite y que Javier Eguíluz la explica muy bien.

Estamos tan acostumbrados a ver sprites que ni nos damos cuenta. Por ejemplo, en esta misma web tenemos los siguientes sprites:

Y como ésta, casi todas. O los emojis del iPhone 6


4. Web Fonts y Glyphos

Pero hay una vuelta de tuerca. Y es que la web se vuelve responsive. Eso quiere decir que los websites se tienen que ver a todas las resoluciones, y los contenidos crecer en consecuencia para adaptarse a sus contenedores. El problema de la técnica de CSS Sprite es que no escala bien, y parte del supuesto de que el diseño va a tener siempre el mismo tamaño. En este sentido esta estrategia se queda coja. Se necesitaría algo que escalara bien, y no perdiera calidad al hacerse muy grande. Parece lógico pensar en gráficos vectoriales como SVG, pero este formato no ha sido soportado por todos los navegadores hasta hace muy poco.

A la par que la web evoluciona hacia responsive e imágenes vectoriales escalables, también empiezan a aparecer distintos formatos para tipografías web (TTF, EOT, SVG, WOFF, WOFF2), sin que triunfase ninguno sobre otro. Cada navegador apostaba por un formato distinto. Hasta que se han ido estandarizando y de ahí viene la solución: de la mano de las WebFonts.

Las tipografías se definen de forma vectorial, siguiendo estándares, por lo que escalan bien al aumentar el tamaño de la fuente, se pueden cambiar de color mediante CSS, se hace una única petición al servidor, y al ser vectoriales, son elementos muy ligeros: justo lo que necesitamos


5. Pero ¿Cómo funcionan las Web Fonts?

Lo único que tenemos que hacer es importar un CSS que utilizará la regla @font-face donde se definirán uno o varios ficheros externos a descargar con la fuente

@font-face{
  font-family:'FontAwesome';
  src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');
  src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),
        url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),
        url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),
        url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),
        url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');
}

Este ejemplo, se refiere a FontAwesome, que es uno de los más extendidos. Si nos fijamos en el código anterior, nos damos cuenta de que se requieren al menos de dos peticiones al servidor: una para el CSS y otra para la fuente específica para tu navegador.

Chrome Firefox Safari Opera Internet Explorer
EOT 6.0
SVG 4.0 3.2 9.0
WOFF 5.0 3.6 5.1 11.1 9.0
WOFF2 36.0 35.0 26.0
TTF/OTF 4.0 3.5 3.1 10.0 9.0

Las más ampliamente soportadas son OTF y WOFF.

Estos iconos están pensados para usarse en contenedores de tipo “inline”. Recordemos que la propiedad display puede tomar varios valores, siendo los principales “en linea” o de “bloque”, aunque hay más. Estos iconos se usan en los de “en linea”: span, i, b, em, etc…

<span class="fa fa-home" aria-hidden="true"></span>
<i class="fa fa-coffee" aria-hidden="true"></i>

Si nos fijamos en el CSS, la clase .fa-home es

.fa-home:before{content:"\f015"}

El “content” es el que dice qué icono se muestra, y es que al tratarse de un “tipo de letra”, al final, los iconos se tratan como letras. Así cada letra se corresponde con un icono. El carácter “\f015” se refiere al correspondiente carácter unicode. Y es que se hace necesario: hay que tener en cuenta que la librería FontAwesome tiene más de 600 iconos. ¡¡¡Imagina un alfabeto de 600 caracteres!!! Se hace necesario el unicode. De hecho, hay una lucha sobre la extensión del unicode entre lingüistas y creadores de emojis.


6. Librerías de iconos

Ya hemos visto que estamos rodeados de iconos, y que prácticamente todos los sites cargan uno o dos juegos de los mismos. A veces son convenientes los sprites, sobre todo para las imágenes propias de maquetación, backgrounds, etc… Y a veces los glyphos, para que escalen bien a todos los tamaños.

Y ya para cerrar este post, pongo unos cuantos enlaces donde podemos encontrar librerías de iconos vectoriales y glyphos.

Repasando los clásicos: “Patterns of Enterpise Application Architecture” de Martin Fowler

$
0
0

Cuando se trata de aplicaciones empresariales, se hacen necesarios otro tipo de patrones que afectan más a la arquitectura que al desarrollo. Y este libro va de esos patrones o “buenas prácticas” a nivel de arquitectura.

Seguramente ya conozcas los patrones de diseño o te hayas pegado con alguno. El concepto es muy sencillo: soluciones probadas aplicadas a problemas recurrentes. Pero por lo general son soluciones locales que ni implican más allá de 2 o 3 clases que resuelven problemas concretos en el desarrollo (vale, hay algunos patrones que condicionan toda la arquitectura). Pero cuando se trata de aplicaciones “grandes”, y con grandes quiero decir, aplicaciones “empresariales”, se hacen necesarios otro tipo de patrones que afectan más a la arquitectura que al desarrollo. Y este libro va de esos patrones o “buenas prácticas” a nivel de arquitectura.

Los libros de patrones son siempre una gran oportunidad para ver cómo trabajan otros desarrolladores. A estas alturas no creo que tenga que explicarte que el autor de este libro, Martin Fowler, es uno de los desarrolladores más relevantes y reconocido por su experiencia y calidad de divulgaciones (como este libro), así que cuando vi este libro no dudé en conseguirlo.

Como otros libros de esta temática, la estructura no puede ser más sencilla: se trata de una enumeración patrón por patrón, en cada uno de los cuales se presenta una introducción que justifica su uso y una explicación del desarrollo, acompañada de código en un lenguaje conocido, en este caso Java y C# (.NET). Esta definición de patrones conforma la segunda y más extensa parte del libro (páginas 107-510), dejando para la primera parte una buena introducción sobre aspecto generales de la arquitectura.

Efectivamente, las 100 primeras páginas del libro tratan sobre consideraciones sobre diferentes temas de arquitectura que hay que tener en cuenta a la hora de desarrollar un proyecto. Me parece la parte más relevante del libro y que más aporta a un lector a día de hoy (luego explicaré el porqué la segunda parte lo es menos), sobre todo teniendo en cuenta de que le sirven como excusa para ir introduciendo patrones concretos que se tratarán en la segunda parte. Como en otros libros de Fowler como el de refactoring, cada patrón está perfectamente numerado para ser localizado rápidamente por número de página de aparición.

En esta primera parte se tratan temas como:

  • La división en capas de la arqutiectura: las clásicas tres capas y cómo distribuirlas.
  • La lógica del dominio del problema: introduciendo conceptos del tan de moda hoy “Domain Driven Design“.
  • Relación de la arquitectura con bases de datos (relacionales en aquella época).
  • La capa Web de presentación con trazos de MVC aunque bastante desfasado en las tiempos actuales.
  • La problemática de la concurrencia: ACID, niveles de aislamiento, transaccionalidad…
  • La sesión y la importancia de lograr servicios sin sesión.
  • Estrategias de distribución de módulos en los entornos físicos.

Aunque son temas bien tratados y escritos, no obstante, y es la grandísima “contra” de este libro, hay que ubicarlos en otra época. Y es que en el mundo del desarrollo los años no perdonan, y este libro ya cuenta con 13 años a sus espaldas desde su primera edición. A medida que uno lee las valiosas cuestiones que trata, le vienen a la cabeza soluciones existentes hoy en día que minimizan los problemas (aunque en algunos casos han cambiado de dimensión).

Hay algunas cuestiones que son universales y tienen un tratamiento valioso en el libro, con independencia del statu quo en el momento de su aplicación: sesiones sin estado, la importancia del dominio, el acceso a bases de datos relacionales o ciertas partes de la concurrencia. Pero hay otras que han cambiado demasiado y no son consejos tan útiles: ya podrás imaginar las diferentes en la capa de distribución Web que existen actualmente (frameworks MVC en JS + REST) con las que habia en 2002-03 (JSP o algún template rudimentario); o la forma de distribuir aplicaciones con el uso de máquinas virtuales, la nube o los contenedores como Docker…

Respecto a la segunda y más extensa parte del libro, se trata de una exposición de patrones que Martin Fowler y otros colaboradores se han encontrado a lo largo de su experiencia como consultores de arquitectura de software. Muchos de estos patrones a día de hoy ya forman parte de la jerga habitual de la profesión o se han ido desarrollando dentro de frameworks hasta que se han convertido en un estándar. Algunos de ellos puede que te resulten más exóticos, mientras que los más conocidos supone una forma estupenda para volver a conocerlos desde un punto de vista más formal.

Los Patrones de arquitectura expuestos se agrupan en torno a diferentes temáticas, por citar algunas:

  • Domain Logic Patterns: como el Domain Model para establecer un buen dominio basado en clases del problema en su capa, o Table Module para que sea el diseño de base de datos el que organice el dominio, o Service Layer a modo de wrapper de una arquitectura completa.
  • Data Source: para el acceso a fuentes de datos y su relación con un dominio basado en programación orientada a objetos: Data Mapper para las operaciones de un objeto de dominio en su tabla de base de datos por ejemplo (a lo MyBatis).
  • Object-Relational Structural: que tratan de la relación entre el modelo en una base de datos relacional y el modelo orientado a objetos. Ejemplos son: el mapeo de foreign keys, tratamiento de asociaciones o herencias, etcétera.
  • Web presentation: como el conocido MVC de separación de responsabilidades, plantillas con Template View, el Application Controller.
  • Distribution: como los Data Transfer Object (DTO) como POJO para la comunicación entre capas. – Offline Concurrency como los diferentes tipos de “lock”: optimistic, pessimistic, coarse o implicit lock.
  • Session State: Client / Server / Database session state, para diferentes estrategias de almacenamiento de la sesión en servidores.
  • Base: Gateway para el aislamiento de otros subsistemas; Record Set para el acceso al resultado de una query; Value Object o Plugin.

No he citado todos aunque algunos que son representativos. Tienes el catálogo completo con una breve explicación de cada uno en la propia página de Martin Fowler: http://martinfowler.com/eaaCatalog/. Como podrás ver muchos de ellos te serán de sobra conocidos, o al menos los habrás utilizado y leído su nombre, por lo que siempre es interesante leer de un autor de esta importancia el motivo de su existencia y quizá aprendas otros posibles escenarios de uso que no te habías planteado hasta ahora.

Cada uno de los patrones está acompañado de un minucioso análisis introductorio y de desarrollo, aunque a veces realmente excesivo, justificando la utilidad del patrón, así como una pequeña implementación en código .NET o Java para dotar de mayor precisión a la explicación.

En resumen, se trata de otro de los libros clásicos sobre el desarrollo de software cuyo éxito ha sido fundirse dentro de las buenas prácticas de esta profesión, pero que ya por sí solo no representan grandes novedades. Antes de adquirirlo te recomiendo que visites la página que Martin Fowler tiene dedicada a estos patrones: Puedes consultar los patrones que se exponen en este libro en la propia página de Martin Fowler. Si encuentras algunos de ellos interesantes, puedes ampliar con las exhaustivas indicaciones que hay en el libro, pero si no ves ninguna novedad, mejor invierte tu tiempo (y dinero) en algo más puntero.

Si te pica la curiosidad y quieres añadir otro clásico de la literatura de desarrollo de software a tu biblioteca, puedes adquirirlo en Amazon.es a través de este link:

patterns
Cómpralo en Amazon


Informes de cobertura para el Front con Karma-Coverage y Jasmine

$
0
0

En este tutorial nos centraremos en explicar cómo obtener informes de cobertura para conocer el grado en que nuestras aplicaciones del front han sido probadas.



Índice de contenidos

1. Introducción

Cuando nos proponemos realizar aplicaciones de calidad es importante establecer métricas sobre las que podamos evaluarlas. En tal sentido, una métrica comúnmente utilizada para evaluar objetivamente la calidad de nuestras aplicaciones es comprobar lo exhaustivas que han sido las pruebas sobre su código fuente.

Por lo tanto, la motivación principal en este tutorial es centrarnos en explicar cómo obtener informes de cobertura para conocer el grado en que nuestras aplicaciones del front han sido probadas.

2. Entorno

El tutorial está escrito usando el siguiente entorno:
  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11.4
  • Node 5.7.1
  • npm 3.8.8
  • Jasmine 2.4.1
  • Karma v0.13.22
  • Phantom JS 2.1.7

3. Creación del proyecto con NPM

El primer paso que ejecutaremos será crear un proyecto JavaScript con la utilidad npm init para guiarnos en la creación del fichero package.json:
MacBook-Pro-de-jmangialomini:TutorialKarma jmangialomini$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (TutorialKarma) tutorialkarma
version: (1.0.0)
description: Tutorial Karma Coverage
entry point: (index.js)
test command: karma start karma.conf.js
git repository:
keywords:
author: Jose Mangialomini
license: (ISC)
About to write to /Users/jmangialomini/workspace/TutorialKarma/package.json:

{
  "name": "tutorialkarma",
  "version": "1.0.0",
  "description": "Tutorial Karma Coverage",
  "main": "index.js",
  "scripts": {
    "test": "karma start karma.conf.js"
  },
  "author": "Jose Mangialomini",
  "license": "ISC"
}

Is this ok? (yes) yes
Aunque se configuraron varios parámetros básicos, uno importante que se debe destacar es el comando para los test que más adelante utilizaremos para ejecutar las pruebas de cobertura con karma.

3.1.Configurando las dependencias

Una vez configurada la estructura básica del proyecto, el siguiente paso es configurar las dependencias necesarias. Las primeras que se deben incorporar son las de Karma y Karma-Coverage, con ellas se podrán conectar un conjunto específico de navegadores web, ejecutar pruebas y luego recoger el informe de la cobertura:
npm install karma karma-coverage --save-dev
Luego de instaladas las dependencias de Karma será necesario configurar las de Jasmine para contar con un framework de pruebas para nuestro código JavaScript:
npm install jasmine karma-jasmine --save-dev
Finalmente y para automatizar la interacción web se instalarán las dependencias de PhantomJS como navegador sin interfaz gráfica:
npm install phantomjs-prebuilt karma-phantomjs-launcher --save-dev
Una ventaja muy útil de instalar las dependencias con npm es que se actualizan automáticamente en el fichero package.json. El de nuestro ejemplo debería quedar de la siguiente manera:
{
  "name": "tutorialkarma",
  "version": "1.0.0",
  "description": "Tutorial Karma Coverage",
  "main": "index.js",
  "scripts": {
    "test": "karma start karma.conf.js"
  },
  "author": "Jose Mangialomini",
  "license": "ISC",
  "devDependencies": {
    "jasmine": "^2.4.1",
    "karma": "^0.13.22",
    "karma-chrome-launcher": "^1.0.1",
    "karma-coverage": "^1.0.0",
    "karma-jasmine": "^1.0.2",
    "karma-phantomjs-launcher": "^1.0.0",
    "phantomjs-prebuilt": "^2.1.7"
  }
}

3.2. Configurando Karma

El siguiente paso para obtener nuestros informes de cobertura es hacer que Karma funcione, para ello debemos configurar el fichero karma.conf.js con la utilidad de karma init:
MacBook-Pro-de-jmangialomini:TutorialKarma jmangialomini$ karma init

Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine

Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no

Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> PhantomJS
>

What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>

Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>

Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes

Config file generated at "/Users/jmangialomini/workspace/TutorialKarma/karma.conf.js".

MacBook-Pro-de-jmangialomini:TutorialKarma jmangialomini$
Para que Karma ejecute el análisis de la cobertura de las pruebas modificaremos manualmente el fichero karma.conf.js para indicarle que debe reportar la cobertura utilizando Phantom JS como navegador para simular la interacción web.
// Karma configuration
	// Generated on Wed May 04 2016 15:42:31 GMT+0200 (CEST)

	module.exports = function(config) {
  	config.set({

    	// base path that will be used to resolve all patterns (eg. files, exclude)
    	basePath: '',


    	// frameworks to use
    	// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    	frameworks: ['jasmine'],


    	// list of files / patterns to load in the browser
    	files: [
     	 'app/*.js',
      	 'tests/*.test.js'
    	],

    	// list of files to exclude
    	exclude: [
    	],

    	// preprocess matching files before serving them to the browser
    	// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    	preprocessors: {
      	'app/*.js': ['coverage']
    	},

    	// test results reporter to use
    	// possible values: 'dots', 'progress'
    	// available reporters: https://npmjs.org/browse/keyword/karma-reporter
    	reporters: ['progress', 'coverage'],

    	// web server port
        port: 9876,

    	// enable / disable colors in the output (reporters and logs)
    	colors: true,

    	// level of logging
    	// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN
        // || config.LOG_INFO || config.LOG_DEBUG
    	logLevel: config.LOG_INFO,

    	// enable / disable watching file and executing tests whenever any file changes
    	autoWatch: true,

    	// start these browsers
    	// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    	browsers: ['PhantomJS'],

    	// Continuous Integration mode
    	// if true, Karma captures browsers, runs the tests and exits
    	singleRun: false,

    	// Concurrency level
    	// how many browser should be started simultaneous
    	concurrency: Infinity
  	})
	}
Nótese que en este fichero se modificaron los valores de los parámetros  preprocessors y reporters para incorporar el valor coverage.

4.Creando un ejemplo con código JavaScript

Ya casi para terminar y para poder validar los reportes de cobertura generados por Karma, vamos a crear un pequeño ejemplo con un fichero javascript y su prueba.

Primero que todo y promoviendo el uso de TDD vamos a crear dentro de la carpeta specs/ un fichero hello.test.js donde con Jasmine programaremos el resultado esperado para una función Hello Word tal cual se muestra a continuación:
describe("Hello world", function() {
     it("says hello", function() {
     expect(helloWorld()).toEqual("Hello world!");
     });
});
Por último, implementamos el fichero hello.js dentro la carpeta app/
function helloWorld() {
     return "Hello world!";
}

5. Ejecutando las pruebas

Finalmente para conocer la cobertura de las pruebas de nuestro ejemplo utilizaremos el comando npm test:
MacBook-Pro-de-jmangialomini:TutorialKarma jmangialomini$ npm test

> tutorialkarma@1.0.0 test /Users/jmangialomini/workspace/TutorialKarma
> karma start karma.conf.js

14 05 2016 14:19:50.067:WARN [karma]: No captured browser, open http://localhost:9876/
14 05 2016 14:19:50.076:INFO [karma]: Karma v0.13.22 server started at http://localhost:9876/
14 05 2016 14:19:50.080:INFO [launcher]: Starting browser PhantomJS
14 05 2016 14:19:50.729:INFO [PhantomJS 2.1.1 (Mac OS X 0.0.0)]: Connected on socket /#08IjLdHbr2riwVYoAAAA with id 60926882
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 1 SUCCESS (0.003 secs / 0.002 secs)
De esta ejecución se genera un fichero index.html dentro de la carpeta /coverage/PhantomJS 2.1.1 (Mac OS X 0.0.0), tal y como se refleja a continuación: Captura de pantalla 2016-05-14 a las 15.34.56

6. Conclusiones

Siempre que nos interese desarrollar aplicaciones de calidad, es importante que podamos demostrarlo y una manera de hacerlo es utilizar la cobertura como métrica que lo refleje. La cobertura de nuestras pruebas nos permitirá identificar debilidades y determinar si puede ser necesario añadir pruebas adicionales para asegurar la calidad del código. Por tanto, es importante tener en cuenta que la cobertura representa la calidad de sus pruebas en lugar de la calidad del código.

7. Referencias

Mapas interactivos con leaflet.js

$
0
0

En este artículo veremos cómo integrar los mapas de OpenStreetMap utilizando la librería Javascript Leaflet.js

Índice de contenidos


1. Introducción

Leaflet es una librería javascript para la creación de mapas interactivos. Nos permite utilizar los tiles del servicio que prefiramos para pintar el mapa. Para este tutorial utilizaremos los tiles de OpenStreetMap.

Crearemos un pequeño proyecto con el que podremos probar la librería. Puedes clonar el repositorio https://github.com/DaniOtero/leaflet-demo


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15’ (2.5 GHz Intel Core i7, 16 GB 1600 MHz DDR3).
  • Sistema Operativo: Mac OS X El Capitán 10.11.2
  • Google Chrome 50.0.2661.102

3. Añadir leaflet a nuestro proyecto

Para añadir leaflet a nuestro proyecto basta con incluir la librería javascript y el css que nos proporciona leaflet. Por comodidad, añadiremos ambas desde el CDN.

<html>
	<head>
	    <title>Tutorial leaflet.js</title>
	    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
	</head>
	<body>
		<h1>Leaflet.js Demo</h1>
	    <div id="map" style="height: 100%"></div>
	    <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
	</body>
</html>

4. Carga del del mapa

Para poder cargar el mapa, necesitamos indicarle a leaflet el identificador un elemento div cargado en el DOM de nuestra página. Le indicaremos a leaflet que utilice el elemento con id “map”.

var demoMap = L.map('map').setView([40.453191, -3.509236], 6);

La llamada a la función “setView” nos centrará el mapa en las coordenadas que le indiquemos (lat, lng) y con el nivel de zoom que deseemos.

A continuación, tenemos que añadirle un “tileLayer” a nuestro mapa. Un “tile” es una imagen que representa un área determinada. Cada nivel de zoom cargará los “tiles” asociados a ese nivel de zoom, los cuales tendrán más o menos detalle en función de la escala del nivel de zoom.

Para cargar los tiles de OpenStreetMap, utilizaremos el siguiente código

var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
	var osmAttrib = 'Map data © OpenStreetMap contributors';
	var osm = new L.TileLayer(osmUrl, {
	    minZoom: 5,
	    maxZoom: 16,
	    attribution: osmAttrib
	});

En cuanto a este codigo comentar un par de detalles:

  • En cuanto a la URL, el párametro “s” representa subdominios (para cargar de distintos subdominios si el servicio dispone de ellos y aligerar la carga), el parámetro “z” el nivel de zoom, y los parámetros “x” e “y” las coordenadas del “tile”.
  • La atribución añadirá una nota al pie del mapa indicando el origen de los mismos.
  • Para crear la capa, se debe indicar la URL del servicio de mapas, y entre las opciones podemos indicar los niveles de zoom máximo y mínimo que queremos permitir.

Con esto ya tendríamos nuestro mapa centrado en el punto que deseemos

Leaflet vacio

5. Añadir marcadores a nuestro mapa

Leaflet nos permite añadir marcadores de forma sencilla:

L.marker([39.190878, -5.7806939999999996]).addTo(demoMap);

Podemos añadir tantos marcadores como queramos a nuestro mapa.

Leaflet Marcadores

6. Mostrar un popup con información de un marcador

Si además de mostrar el marcador, queremos que nos muestre alguna información adicional, podemos emplear un popup para representar esta información en forma de HTML. Para ello podemos refactorizar el código anterior ligeramente

var location = [39.190878, -5.7806939999999996];
	var marker = L.marker(location)
	marker.bindPopup('

Latitud:'+location[0]+'

Longitud:'+location[1]+'

'); marker.addTo(demoMap);

Si ahora hacemos click sobre algún de los marcadores podremos ver la latitud y longitud de dicho marcador

Leaflet Popup

7. Optimización

En el caso de que necesitemos una mayor fluidez para nuestro mapa (por ejemplo, si es una aplicación web que va a ejecutarse en smartphones con algún año a sus espaldas…), podemos mejorar la experiencia de usuario desactivando las animaciones. Para ello podemos pasarle un objeto con opciones a la llamada de creación del mapa:

var demoMap = L.map('map', {
                fadeAnimation: false,
                zoomAnimation: false,
                markerZoomAnimation: false
            }).setView([40.453191, -3.509236], 6);

También podemos indicarle al “TileLayer” que reutilice tiles, de forma que no tenga que crear nuevos objetos y reservar memoria por cada tile creado, y que se actualice cuando esté en estado “idle” para evitar que se congele la pantalla mientras carga.

var osm = new L.TileLayer(osmUrl, {
        minZoom: 5, maxZoom: 16,
        attribution: osmAttrib,
        updateWhenIdle: true,
        reuseTiles: true
    });

8. Clustering

Si necesitamos representar una gran cantidad de marcadores en nuestro mapa, es posible que a un nivel de zoom bajo nuestro mapa se vea sobrecargado. Además el renderizar una gran cantidad de marcadores también es bastante costoso (en nuestro navegador de escritorio probablemente no tendremos problemas, pero en el caso de smartphones la cosa se complica…).

Para ello podemos agrupar los marcadores en clusters, de forma que los marcadores que se aglutinan en un área determinada se representarán como un único marcadores, y se separarán cuando se alcance un nivel de zoom determinado (o al hacer click, se cambiará el nivel de zoom). Para ello leaflet cuenta con un plugin llamado Leaflet.markercluster. Para instalarlo podemos utilizar npm (en este caso no existe CDN):

npm install --save leaflet.markercluster

Y lo cargaremos en nuestro html:

<html>
	<head>
	    <title>Demo leaflet.js</title>
	    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
	    <link rel="stylesheet" href="node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css" />
	</head>
	<body>
	    <h1>Leaflet.js Demo</h1>
	    <div id="map" style="height: 100%"></div>
	    <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
	    <script type="text/javascript" src="node_modules/leaflet.markercluster/dist/leaflet.markercluster-src.js"></script>
	</body>
</html>

Refactorizaremos el código para añadir nuestros marcadores a un cluster. En vez de añadir un marcador directamente al mapa, se debe añadir a una nueva capa:

var markerCluster = L.markerClusterGroup();

	var location = [39.190878, -5.7806939999999996];
	var marker = L.marker(location)
	marker.bindPopup('

Latitud:'+location[0]+'

Longitud:'+location[1]+'

'); markerCluster.addLayer(marker); demoMap.addLayer(markerCluster);
Leaflet cluster

9. Conclusiones

Leaflet.js es una librería bastante completa. Es bastante intuitiva y su API es bastante simple. Además, nos permite personalizar muchos aspectos, como el icono de los marcadores o el comportamiento de los mismos. También dispone de bastantes plugins como en el caso de los clusters.

El hecho de que podamos utilizar cualquier servicio de mapas que queramos también es un punto a favor, ya que no nos “casamos” con un servicio determinado.

También está preparada para funcionar en smartphones, reconociendo los gestos típicos como pan o pinch sin tener que programar nada adicional.

Como desventaja, en smartphones con algún año a sus espaldas, o cuyo motor javascript del webview nativo sea algo lento (cof, cof windows phone…), la experiencia de usuario puede ser no todo lo agradable que desearíamos, especialmente cuando cargamos una cantidad elevada de marcadores.


10. Referencias

http://leafletjs.com/reference.html
https://github.com/Leaflet/Leaflet.markercluster

Uso básico de Java 8: Stream y Lambdas.

$
0
0

En este tutorial vamos a ver ejemplos hechos con Programación imperativa vs. Programación funcional y qué ventajas nos aporta.

Índice de contenidos


1. Introducción

Esta es la primera parte de un tutorial dedicado a Java 8. En esta primera parte elaboraré ejemplos entre Programación imperativa y Programación funcional para en las próximas partes realizar microbenchmarking a mano y con JMH, comparando el rendimiento de cada uno de los ejemplos.

Java 8 nos abre la puerta a la programación funcional con las expresiones lambda, también llamadas funciones anónimas, y la API Stream. Este tutorial no pretende entrar en profundidad en el tema si no ser un pequeño acercamiento a estas nuevas funcionalidades comparadas con la forma de realizarlas en programación imperativa. Para entrar más en profundidad, tenéis los siguientes tutoriales:


Si quieres conocer todas las novedades de java 8, pincha en el siguiente enlace: Novedades Java8

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 17′ (3 Ghz Intel Core 2 Duo, 8GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11
  • Entorno de desarrollo: IntelliJ Idea 2016.2
  • JavaSE build 1.8.0_77-b03
  • Maven 3.3.9

3. Proyecto

Como el objetivo de este tutorial es centrarnos en el uso de Stream y Lambdas, he creado el proyecto más sencillo que se me ha ocurrido. Un carrito de la compra que nos devuelve el número de productos y el precio total de ellos. A continuación dejo el código:

CarritoDeLaCompra.java
import java.util.Collection;

public class CarritoDeLaCompra {

    private Collection precios;

    public CarritoDeLaCompra(Collection precios) {

        this.precios = precios;
    }

    public int calcularPrecioTotal() {

        int precioTotal = 0;

        for(Integer precio : precios){

            precioTotal += precio;

        }
        return precioTotal;
    }

    public int contarNumeroProductos() {

        return precios.size();
    }
}
CarritoBuilder.java
import java.util.ArrayList;

public class CarritoBuilder {

    ArrayList precios = new ArrayList();

    public CarritoBuilder(int size){

        for(int i = 0; i < size; i++){

            Double random = Math.random()*100+1;
            precios.add(random.intValue());
        }
    }

    public CarritoBuilder(int size, int value){

        for(int i = 0; i < size; i++){

            precios.add(value);
        }
    }

    public CarritoDeLaCompra build(){

        return new CarritoDeLaCompra(this.precios);
    }

    public CarritoBuilder add(Integer nuevoValor){

        precios.add(nuevoValor);
        return this;
    }
}

También, como buenos seguidores de la filosofía TDD que somos en Autentia, he creado los test que me permitan probar que lo que realizo es totalmente funcional. Os dejo también el código de los test:

CarritoDeLaCompraTest.java
import com.autentia.CarritoBuilder;
import com.autentia.CarritoDeLaCompra;
import org.junit.Assert;
import org.junit.Test;

public class CarritoDeLaCompraTest {

    @Test
    public void shouldReturnTheCountOfAllItems() throws Exception {

        CarritoBuilder builder = new CarritoBuilder(30);
        CarritoDeLaCompra carritoDeLaCompra = builder.build();
        Assert.assertEquals(30, carritoDeLaCompra.contarNumeroProductos());
    }

    @Test
    public void shouldCalculateTotalPrice() throws Exception {

        CarritoBuilder builder = new CarritoBuilder(60,5);
        CarritoDeLaCompra carritoDeLaCompra = builder.build();
        Assert.assertEquals(300, carritoDeLaCompra.calcularPrecioTotal());

    }
}

Con el objetivo de en los siguientes tutoriales realizar pruebas de rendimiento, en vez de modificar los métodos voy a crear nuevos. AVISO: los tiempos de ejecución de los test que aparecen en esta primera parte no son representativos.


4. Lambdas y Stream

Empecemos con nuestras primeras líneas de programación funcional. Para empezar, creamos un nuevo método, en mi caso el método calcularPrecioTotalLambda() cuyo código es el siguiente:

calcularPrecioTotalLambda()
public int calcularPrecioTotalLambda() {
    int precioTotal = this.precios.stream().mapToInt(precio -> precio.intValue()).sum();
    return precioTotal;
}

Como podéis observar, hacemos uso del stream, el cual nos recogerá cada objeto de la Collection precios (this.precios.stream()) y lo mapeará a Integers (mapToInt()). La lambda que pasamos por parámetro a mapToInt nos dará un warning.

Captura de pantalla 2016-05-10 a las 15.26.05

Si pulsamos el atajo [Alt + Enter] la solución será sustituir la lambda por una referencia a método. De tal manera que el código quedaría así:

calcularPrecioTotalRefMethod()
public int calcularPrecioTotalLambda() {
    int precioTotal = this.precios.stream()
                          .mapToInt(Integer::intValue)
                          .sum();
    return precioTotal;
}

Las dos formas hacen lo mismo: sacar de cada Integer su valor. La diferencia es la llamada que hacemos. En el primer caso (i -> i.intValue()) llamamos al método intValue de cada Integer. En el segundo caso (Integer::intValue) hacemos uso del método a través de una referencia (incluidas en Java 8). Si tuviera que decantarme por una forma, quizás la más clara sea i -> i.intValue(), pero sin duda la más correcta es Integer::intValue.

Ahora creamos los test de estos dos nuevos métodos y vemos que todo funciona:

CarritoDeLaCompraTest.java
@Test
public void shouldCalculateTotalPriceLambda() throws Exception {

   CarritoBuilder builder = new CarritoBuilder(60, 5);
   CarritoDeLaCompra carritoDeLaCompra = builder.build();
   Assert.assertEquals(300, carritoDeLaCompra.calcularPrecioTotalLambda());
}

@Test
public void shouldCalculateTotalPriceRefMethod() throws Exception {

   CarritoBuilder builder = new CarritoBuilder(60,5);
   CarritoDeLaCompra carritoDeLaCompra = builder.build();
   Assert.assertEquals(300, carritoDeLaCompra.calcularPrecioTotalRefMethod());
}
Captura de pantalla 2016-05-13 a las 15.56.07

5. Filter

Hasta aquí ha sido un primer contacto muy básico con los Streams, las Lambdas y las Referencias a métodos. Pretendía dar a entender las similitudes entre un bucle for y un stream para ahora entrar más en detalle en las posibilidades que nos ofrece Java 8.

Obviamente con las opciones que tenemos ahora en CarritoDeLaCompra poco podríamos hacer, por lo que, debemos extender funcionalidades. La primera va a ser un detector de descuentos que comprueba si algún precio es mayor o igual que el que pasamos por parámetro y, por cada coincidencia, descuenta un 5% :

calcularDescuentoTotal()
public long calcularDescuentoTotal(int cantidadConDescuento){

    long descuentoTotal = 0;

    for(Integer precio : precios){
        if(precio >= cantidadConDescuento){
            descuentoTotal += (cantidadConDescuento*5)/100;
        }
    }
    return descuentoTotal;
}

El método de testeo para este nuevo método:

Test
@Test
public void shouldCalculateTotalDiscount() throws Exception {

		CarritoBuilder builder = new CarritoBuilder(20,100);
		CarritoDeLaCompra carritoDeLaCompra = builder.build();
		Assert.assertEquals(100, carritoDeLaCompra.calcularDescuentoTotal(100));

}
Captura de pantalla 2016-05-12 a las 19.17.38

Ahora, vamos a hacer un nuevo método que use Stream y Lambda:

calcularDescuentoTotalLambda()
public long calcularDescuentoTotalLambda(int cantidadConDescuento){

    long descuentoTotal = 0;

    Long numeroDeDescuentos  = this.precios.stream()
                                           .filter(precio -> precio.intValue() >= cantidadConDescuento)
                                           .count();

    descuentoTotal = (cantidadConDescuento*5/100)*numeroDeDescuentos;

    return descuentoTotal;
}

Volvemos a tener un ejemplo para comparar entre prog. imperativa y prog. funcional, ahora tratemos de entenderlo. Como podéis observar, he añadido un nuevo método llamado filter(). Dicho método recibe como parámetro un predicado. Para entenderlo, no hay más que traducirlo al español: filtro.

Nos va a servir de filtro con la condición que nosotros le pongamos, aplicando el siguiente método únicamente sobre los valores que la cumplan. En mi caso, la condición es que cada valor debe ser mayor o igual a la cantidad pasada por parámetro para que se cuente. Al final, tendremos la cuenta de todos los precios que tienen descuento. Una vez contados aplicamos unas matemáticas básicas para sacar el descuento total.

Ahora creamos el test y lo ejecutamos para comprobar si es correcto.

shouldCalculateTotalDiscountLambda()
@Test
public void shouldCalculateTotalDiscountLambda() throws Exception {

    CarritoBuilder builder = new CarritoBuilder(20,100);
    CarritoDeLaCompra carritoDeLaCompra = builder.build();
    Assert.assertEquals(100, carritoDeLaCompra.calcularDescuentoTotalLambda(100));

}
Captura de pantalla 2016-05-12 a las 19.38.17

6. anyMatch()

Para no repetir números muy extensos, he creado dos constantes que usaré a lo largo de los próximos test.

private final Long TOTAL_SIZE = 20000000L;
    private final Long NUMBER_ADD = 1000000L;

Tambien he añadido un método a CarritoBuilder llamado addMultiple() que me permita añadir mucho valores para dejar el número negativo en mitad del array completo.

addMultiple()
public CarritoBuilder addMultiple(int size, int value){

    for(int i = 0; i < size; i++){

        precios.add(value);
    }

    return this;
}

Vamos a seguir añadiendo funcionalidades a nuestro carrito. El nuevo método que he creado si detecta un valor erróneo devuelve true. Los valores erróneos serán todos aquellos valores que lleguen como un número negativo. A continuación muestro el código del nuevo método:

detectarError()
public boolean detectarError() {

  boolean negativeFind = false;

  for (Long precio : precios) {

     if (precio < 0) {

         negativeFind = true;
     }
  }

  return negativeFind;
}

Como es evidente, ahora necesitamos la clase de test para este método:

shouldDetectErrorAndReturnTrueWhenAPriceIsNegative()
@Test
public void shouldDetectErrorAnThrowRuntimeExceptionWhenAPriceIsNegative(){

	CarritoBuilder builder = new CarritoBuilder(TOTAL_SIZE,NUMBER_ADD);
	builder.add(-1L);
        builder.addMultiple(TOTAL_SIZE,NUMBER_ADD);
	CarritoDeLaCompra carritoDeLaCompra = builder.build();
	Assert.assertTrue(carritoDeLaCompra.detectarError());

}
Captura de pantalla 2016-05-24 a las 9.48.40

A continuación dejo el código del ejemplo usando un nuevo método: anyMatch()

detectarErrorAnyMatch()
public boolean detectarErrorAnyMatch() {

    return this.precios.stream().anyMatch(precio -> precio.intValue() < 0);
}

anyMatch(), al igual que filter(), recibe un predicado. En este caso devuelve true si encuentra algún precio negativo.

Creamos el test y lo ejecutamos para comprobar que funciona correctamente:

shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeAnyMatch()
@Test
public void shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeAnyMatch(){

     CarritoBuilder builder = new CarritoBuilder(TOTAL_SIZE,NUMBER_ADD);
     builder.add(-1L);
     builder.addMultiple(TOTAL_SIZE,NUMBER_ADD);
     CarritoDeLaCompra carritoDeLaCompra = builder.build();
     Assert.assertTrue(carritoDeLaCompra.detectarErrorAnyMatch());

}
Captura de pantalla 2016-05-24 a las 9.52.51

7. findAny() e isPresent()

Para estos nuevos métodos voy a usar el mismo ejemplo de antes. Más adelante veremos si realmente existen diferencias más allá de la sintaxis de cada forma. Tomamos como referencia el detectarError() y realizamos el siguiente método:

public boolean detectarErrorFindAny() {

   return this.precios.stream().filter(precio -> precio.intValue() < 0)
                               .findAny()
                               .isPresent();
}

En este ejemplo hacemos uso de los métodos findAny() e isPresent(). Si traducimos al español quedaría como “…y encuentra alguno. ¿Está presente?”. Es decir, findAny() nos devuelve un Optional cuando se cumple la condición de filter(). Este Optional tiene como método isPresent() el cual si encuentra una coincidencia devolverá true. Hay que aclarar que esta forma recorre todo el stream.

Habría una forma de evitar que recorra todo el stream y es usando el método findFirst(), es decir, “encuentra el primero”.

shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeNumberFindAny()
@Test
public void shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeNumberFindAny(){

    CarritoBuilder builder = new CarritoBuilder(TOTAL_SIZE,NUMBER_ADD);
    builder.add(-1L);
    builder.addMultiple(TOTAL_SIZE,NUMBER_ADD);
    CarritoDeLaCompra carritoDeLaCompra = builder.build();
    Assert.assertTrue(carritoDeLaCompra.detectarErrorFindAny());

}
shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeNumberFindFirst()
@Test
public void shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeNumberFindFirst(){

    CarritoBuilder builder = new CarritoBuilder(TOTAL_SIZE,NUMBER_ADD);
    builder.add(-1L);
    builder.addMultiple(TOTAL_SIZE,NUMBER_ADD);
    CarritoDeLaCompra carritoDeLaCompra = builder.build();
    Assert.assertTrue(carritoDeLaCompra.detectarErrorFindFirst());

}

Ejecutamos los test para comprobar su correcto funcionamiento.

Captura de pantalla 2016-05-24 a las 10.51.25

Existen muchas otras funcionalidades pero el objetivo de este post no es explicarlas todas.


8. parallelStreams()

En este último punto, voy a hacer uso de parallelStreams. Para conocer los parallelStream os recomiendo dos enlaces:

Cogiendo los tres ejemplos de detectar precios nulos, he creado los siguientes métodos usando un parallelStream:

detectarErrorXXXParallel()
public boolean detectarErrorAnyMatchParallel() {
    return this.precios.parallelStream().anyMatch(precio -> precio.intValue() < 0);
}

public boolean detectarErrorFindAnyParallel() {
    return this.precios.parallelStream().filter(precio -> precio.intValue() < 0)
                                        .findAny()
                                        .isPresent();
}

public boolean detectarErrorFindFirstParallel() {

    return this.precios.parallelStream().filter(precio -> precio.intValue() < 0)
                                        .findFirst()
                                        .isPresent();
}
shouldDetectErrorAnThrowRuntimeExceptionWhenAPriceIsNegativeXXXParallel()
@Test
public void shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeAnyMatchParallel(){

    CarritoBuilder builder = new CarritoBuilder(TOTAL_SIZE,NUMBER_ADD);
    builder.add(-1L);
    builder.addMultiple(TOTAL_SIZE,NUMBER_ADD);
    CarritoDeLaCompra carritoDeLaCompra = builder.build();
    carritoDeLaCompra.detectarErrorAnyMatchParallel();

}

@Test
public void shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeNumberFindAnyParallel(){

    CarritoBuilder builder = new CarritoBuilder(TOTAL_SIZE,NUMBER_ADD);
    builder.add(-1L);
    builder.addMultiple(TOTAL_SIZE,NUMBER_ADD);
    CarritoDeLaCompra carritoDeLaCompra = builder.build();
    Assert.assertTrue(carritoDeLaCompra.detectarErrorFindAnyParallel());

}

@Test
public void shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeNumberFindFirstParallel(){

    CarritoBuilder builder = new CarritoBuilder(TOTAL_SIZE,NUMBER_ADD);
    builder.add(-1L);
    builder.addMultiple(TOTAL_SIZE,NUMBER_ADD);
    CarritoDeLaCompra carritoDeLaCompra = builder.build();
    Assert.assertTrue(carritoDeLaCompra.detectarErrorFindFirstParallel());

}

Lanzamos los test por separado:

Captura de pantalla 2016-05-24 a las 10.56.15 Captura de pantalla 2016-05-24 a las 10.56.48 Captura de pantalla 2016-05-24 a las 10.57.15

9. Conclusión

Este tutorial no lo he enfocado en aprender con exactitud todos los entresijos de Java 8, sino más bien en servir de primer contacto a todos aquellos que quieran empezar a usar la programación funcional. Lo cierto es que hay otros lenguajes como Scala que ofrecen una programación funcional bastante más avanzada, pero como dije al principio, Java8 solo ha sido la primera puerta hacia la programación funcional.

En la segunda parte de este tutorial realizaré las pruebas de Benchmarking comparando el rendimiento de cada uno de los métodos que he creado para comprobar en diferentes situaciones cuales son las ventajas y desventajas de la Programación imperativa vs Programación funcional.

Benchmarking Java 8: ¿Qué es más rápido?

$
0
0

En este tutorial realizaremos un benchmarking de todos los ejemplos de la primera parte, descubriendo las ventajas y desventajas que las Lambdas y Streams nos proporcionan.

Índice de contenidos


1. Introducción

Esta es la segunda parte del tutorial sobre Java 8 que he realizado. En esta segunda parte me centraré en comprobar cuales son las ventajas y desventajas de usar Streams y Lambdas, sacando en conclusión cuándo y cómo usar cada una de las herramientas que nos aportan. Para este tutorial he contado con David Gómez para ayudarme a realizar benchmarking lo más fiable posible.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 17′ (3 Ghz Intel Core 2 Duo, 8GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11
  • Entorno de desarrollo: IntelliJ Idea 2016.2
  • JavaSE build 1.8.0_77-b03
  • Apache Maven 3.1.1

3. Microbenchmarking a mano

Las modificaciones que van a aparecer respecto al ejemplo anterior son las siguientes:

1ª setUp() y testWith()
public static final long LOAD_LEVELX2 = 1_000L;
private static CarritoDeLaCompra carritoCompra;

@BeforeClass
public static void setUp() {
    CarritoBuilder builder = new CarritoBuilder(LOAD_LEVELX2,1000000L);
    builder.add(-1L);
    builder.addMultiple(LOAD_LEVELX2,1000000L);
    carritoCompra = builder.build();
}

private long testWith(Supplier method) {
    System.gc();
    System.out.println("Start----------------------");

    long start  = System.currentTimeMillis();

    method.get();

    long end = System.currentTimeMillis();

    System.out.println("End----------------------");
    System.out.println(errors);
    return end - start;
}

El método testWith nos permitirá pasar por parámetro el método en forma de Lambda (Proveedor) que queremos realizar para aplicar una serie de acciones antes y después de ejecutar el test, entre ellas la línea System.gc();. Esta se utiliza para ejecutar el Garbage Collector antes del test para evitar que cualquier GC se ejecute en medio de un test y pueda modificar nuestro benchmark.

Para monitorizar el GC y asegurarnos de que no se ejecuta entre las marcas Start – End editamos la Run/Debug configuration y en VM options debemos usar el parámetro -XX:+PrintGC. Además, configuraremos el tamaño inicial del heap para que tenga 4GB con el parámetro -Xms4092m. Para saber más acerca del GC, podéis ver este tutorial de adictos que habla de ello.

Captura de pantalla 2016-05-24 a las 15.46.09

El setUp creará el mismo carrito antes de cada test y así probar la misma carga en todos ellos.

2ª Contador
private AtomicLong counter = new AtomicLong();

public long getCounter() {
    return counter.get();
}

public void resetCounter() {
    counter.set(0L);
}

Este contador nos permitirá saber el número de iteraciones que hacen cada uno de los diferentes tipos de recorrer un stream

Lo siguiente que debemos hacer, es crear unos nuevos test para cada método. El código es el siguiente:

Nuevos test
@Test
public void testAnyMatch() {

    System.out.println("anyMatch = "
            + testWith(() -> carritoCompra.detectarErrorAnyMatch()));
}

@Test
public void testAnyMatchParallel() {

    System.out.println("anyMatchParallel = "
            + testWith(()-> carritoCompra.detectarErrorAnyMatchParallel()));

}

@Test
public void testFindAny() {

    System.out.println("findAny = " +
            + testWith(() -> carritoCompra.detectarErrorFindAny()));

}

@Test
public void testFindAnyParallel() {

    System.out.println("findAnyParallel = "
            + testWith(() -> carritoCompra.detectarErrorFindAnyParallel()));

}

@Test
public void testFindFirst() {

    System.out.println("findFirst = "
            + testWith(() -> carritoCompra.detectarErrorFindFirst()) );

}

@Test
public void testFindFirstParallel() {

    System.out.println("findFirstParallel = "
            + testWith(() -> carritoCompra.detectarErrorFindFirstParallel()));

}

@Test
public void testImperative(){

    System.out.println("imperative = "
            + testWith(() -> carritoCompra.detectarError()));
}

3.1. Resultados individuales

Una vez creados, empezamos el primer benchmark. A continuación dejaré varios logs con los resultados que he recibido, empezando por el LOAD_LEVEL que había en cada log.

Nota: LOAD_LEVEL se llama en el código LOAD_LEVELX2 ya que el valor negativo lo incluyo justo en la mitad, por lo que añado un LOAD_LEVEL al principio y otro al final.

LOAD_LEVEL = 2_000L;

anyMatch = 3ms
anyMatchParallel = 22ms

findAny = 25ms
findAnyParallel = 7ms

findFirst = 8ms
findFirstParallel = 3ms

imperative = 1ms
LOAD_LEVEL = 20_000L;

anyMatch = 6ms
anyMatchParallel = 103ms

findAny = 7ms
findAnyParallel = 8ms

findFirst = 5ms
findFirstParallel = 17ms

imperative = 5ms
LOAD_LEVEL = 200_000L;

anyMatch = 24ms
anyMatchParallel = 43ms

findAny = 21ms
findAnyParallel = 22ms

findFirst = 10ms
findFirstParallel = 21ms

imperative = 26ms
LOAD_LEVEL = 2_000_000L;

anyMatch = 43ms
anyMatchParallel = 72ms

findAny = 54ms
findAnyParallel = 36ms

findFirst = 50ms
findFirstParallel = 54ms

imperative = 57ms
LOAD_LEVEL = 20_000_000L;

anyMatch = 331ms
anyMatchParallel = 100ms

findAny = 252ms
findAnyParallel = 137ms

findFirst = 449ms
findFirstParallel = 243ms

imperative = 375ms
Captura de pantalla 2016-06-01 a las 9.59.51

Como podéis observar, entre las marcas Start – End no se ejecuta ningún Garbage Collector, evitando así falsear los tiempos de ejecución.

A continuación realizaré las pruebas con más LOAD_LEVEL con un ordenador más potente para ver los resultados. El hardware utilizado para los siguientes benchmarks es:

  • Macbook Pro 15′ Intel core i5 2’4GHz, 8GB DDR3
LOAD_LEVEL = 2_000_000L;

anyMatch = 36ms
anyMatchParallel = 87ms

findAny = 33ms
findAnyParallel = 47ms

findFirst = 18ms
findFirstParallel = 95ms

imperative = 27ms
LOAD_LEVEL = 20_000_000L;

anyMatch = 70ms
anyMatchParallel = 114ms

findAny = 69ms
findAnyParallel = 105ms

findFirst = 121ms
findFirstParallel = 116ms

imperative = 62ms
LOAD_LEVEL = 200_000_000L;

anyMatch = 541ms
anyMatchParallel = 379ms

findAny = 417ms
findAnyParallel = 795ms

findFirst = 1139ms
findFirstParallel = 591ms

imperative = 412ms

3.2. Resultados por iteración

Las cinco iteraciones mostradas en los gráficos son con LOAD_LEVEL= 2000L y LOAD_LEVEL= 20_000_000L

LOAD_LEVEL= 2000L
Captura de pantalla 2016-06-01 a las 11
LOAD_LEVEL= 20_000_000L
Captura de pantalla 2016-06-01 a las 11

3.3. Media y Desviación típica

La media está realizada con la siguiente fórmula en cada LOAD_LEVEL:

Captura de pantalla 2016-06-01 a las 10.57.36 Captura de pantalla 2016-06-01 a las 11.25.35 Captura de pantalla 2016-06-01 a las 11.26.20

3.4. Curva de respuesta

Captura de pantalla 2016-06-01 a las 10.18.35

Gracias a los gráficos se puede comprobar que hay dos métodos cuya curva de respuesta y resultados son mucho mejores que los demás: anyMatchParallel y findAnyParallel. Si bien se puede observar que en valores bajos es incluso más pesado el trabajo que realizan las lambdas y streams que un bucle for.

Hay un resultado que me gustaría remarcar también y es el de findAny, cuya Desviación típica es la más grande, lo que viene a traducirse que sus resultados son menos uniformes y por lo tanto más inestables.


4. ¿Por qué?

Una vez obtenidos los resultados, vamos a averiguar el por qué de estos contando el número de iteraciones que realizan. Para ello modificamos los métodos y test para que usen el counter que ya teníamos creado.

¡OJO! El código que a continuación pongo solo debe usarse para el contador, no para el benchmark. Esto podría falsear mucho los resultados.

Añadimos el método peek() el cual recibe un consumidor. El método peek realiza un método pasado como una lambda pero no afecta a ninguna otra parte del stream. Es decir, es una operación intermedia cuya acción es invisible al resto de métodos. En este caso lo usamos para lanzar el método incrementAndGet() de AtomicLong, con el cual evitaremos una condición de carrera al contar iteraciones con multihilo

public boolean detectarErrorAnyMatch() {

    return this.precios.stream()
                       .peek(precio -> counter.incrementAndGet())
                       .anyMatch(precio -> precio.intValue() < 0);
}


public boolean detectarErrorFindAny() {

    return this.precios.stream().peek(precio -> counter.incrementAndGet())
                                .filter(precio -> precio.intValue() < 0)
                                .findAny()
                                .isPresent();
}

public boolean detectarErrorFindFirst() {

    return this.precios.stream().peek(precio -> counter.incrementAndGet())
                                .filter(precio -> precio.intValue() < 0)
                                .findFirst()
                                .isPresent();
}

public boolean detectarErrorAnyMatchParallel() {

    return this.precios.parallelStream().peek(precio -> counter.incrementAndGet())
                                        .anyMatch(precio -> precio.intValue() < 0);
}

public boolean detectarErrorFindAnyParallel() {
    return this.precios.parallelStream().peek(precio -> counter.incrementAndGet())
                                        .filter(precio -> precio.intValue() < 0)
                                        .findAny()
                                        .isPresent();
}

public boolean detectarErrorFindFirstParallel() {

    return this.precios.parallelStream().peek(precio -> counter.incrementAndGet())
                                        .filter(precio -> precio.intValue() < 0)
                                        .findFirst()
                                        .isPresent();
}
@Test
public void testXXXX() {

    carritoCompra.resetCounter();

    testWith(()-> carritoCompra.detectarErrorXXXX());

    long counter = carritoCompra.getCounter();

    System.out.println("Iteraciones XXXX = " + counter);
}

El resultado:

LOAD_LEVEL = 200_000L

Iteraciones anyMatch = 100_001
Iteraciones anyMatchParallel = 50_001

Iteraciones findAny = 100_001
Iteraciones findAnyParallel = 50_001

Iteraciones findFirst = 100_001
Iteraciones findFirstParallel = 100_001

Iteraciones imperative = 200_001

Solamente el ejemplo de Programación imperativa recorre todos los datos. Para dicho ejemplo necesitaríamos poner una comprobación más con un break y dicha práctica no es muy recomendable. Los métodos anyMatch, findAny, findFirst y findFirstParallel recorren hasta la primera coincidencia. anyMatchParallel y findAnyParallel recorren 1/4 del total ya que divide la carga por hilos, en mi caso 2 hilos. Cuando uno de esos hilos encuentra alguna (any) coincidencia se paran los demás.

Tanto findAnyParallel como anyMatchParallel hay veces que muestran los siguientes resultados:

LOAD_LEVEL = 200_000L

Iteraciones anyMatchParallel = 50_001
Iteraciones anyMatchParallel = 150_002
Iteraciones anyMatchParallel = 100_001

Iteraciones findAnyParallel = 1
Iteraciones findAnyParallel = 125_002

Como ya dije, estos métodos se dividen por hilos y dependiendo de en qué momento el hilo esté más o menos ocupado, así se repartirán la carga recogiendo unos hilos más carga que otros.

A continuación dejo los resultados que he obtenido en otro ordenador

  • Macbook Pro 15′ Intel core i5 2’4GHz, 8GB DDR3
LOAD_LEVEL = 200_000L

Iteraciones anyMatch = 100_001
Iteraciones anyMatchParallel = 175_002

Iteraciones findAny = 100_001
Iteraciones findAnyParallel = 125_001

Iteraciones findFirst = 100_001
Iteraciones findFirstParallel = 175_002

Iteraciones imperative = 200_001
LOAD_LEVEL = 200_000L

Iteraciones anyMatchParallel = 162_502
Iteraciones anyMatchParallel = 187_502

Iteraciones findAnyParallel = 162_502
Iteraciones findAnyParallel = 162_501

5. Conclusiones

Una vez hechas todas las pruebas incluso en difrentes hardwares queda claro que, si se trata de grandes volúmenes de información, anyMatchParallel y findAnyParallel suponen una ventaja absoluta en cuanto a claridad y sencillez del código y rapidez de ejecución. Hemos visto que incluso en volúmenes muy pequeños sería hasta contraproducente usar streams y lambdas. Cuándo usarlos depende de muchos factores que cada desarrollador debe valorar por sí mismo.


6. Enlaces

Repositorio de github
Documento pdf con los resultados

Introducción a SAFe

$
0
0

En este tutorial hablaremos de Scaled Agile Framework (SAFe), enfocado a las empresas que aplican la agilidad en sus procesos.

Índice de contenidos

1. Introducción

La mayoría de las organizaciones de IT en todo el mundo se han dado cuenta de la eficacia del agilismo en el desarrollo de software. Un equipo que desarrolla software con metodologías ágiles genera valor en interacciones frecuentes, que no sólo aumenta la confianza del cliente, sino que también reduce el riesgo e incrementa considerablemente el valor del negocio. Ahora bien, es cierto que la agilidad funciona para gestionar las fases de desarrollo de un proyecto, pero aún está lejos de ofrecer una solución completa. Para desarrollar software bajo metodologías ágiles se necesita un gran cambio en la mentalidad de toda la organización. En organizaciones pequeñas donde el negocio e IT trabajan muy de cerca puede que no sea un reto, pero en grandes organizaciones donde las estrategias de negocio no están directamente vinculadas con IT la agilidad puede que se pierda y se convierta en un fracaso.

Dadas estas necesidades, Dean Leffingwell propuso un modelo muy interesante llamado Scaled Agile Framework (SAFe) orientado a grandes empresas que quieran aplicar la agilidad en todos sus niveles.

2. ¿Que es SAFe?

Como su propio creador lo define, SAFe es una base de conocimientos libre para la aplicación de patrones probados para desarrollar software implementando Lean-Agile en proyectos a escala empresarial. Su objetivo principal es proporcionarle a las organizaciones una guía general para aumentar la productividad en el desarrollo de software a todos los niveles de una organización.

En la Big Picture de SAFe se describe un modelo de tres niveles para escalado de las metodologías ágiles en grandes organizaciones:

  • Nivel dePortafolio (Portfolio), donde los principios Lean-Agile se aplican para equilibrar la carga de trabajo con capacidad de entrega y optimizar el valor entregado alineando los esfuerzos arquitectónicos.
  • Nivel de Programa (Program), aquí las características del producto se integran y se prueban.
  • Nivel de Equipo (Team), en el cual se desarrollan y prueban pequeñas características dentro de un solo componente de dominio de negocio. El propósito en este nivel es ​entregar en funcionamiento características probadas (historias de usuario) en ciclos que por lo general duran 2 semanas. SAFe establece fechas fijas para la entrega de productos usando la metáfora del tren de liberación; si una característica pierde el tren tiene que esperar al siguiente.
En la versiones más recientes de SAFe (4.0), se introduce un nivel denominado cadena de valor que da un nuevo enfoque opcional pensado para sistemas grandes y complejos con varios trenes de liberación ágil. La identificación y optimización de los flujos de valor es una habilidad crítica de las empresa Lean-Agile.

SAFe

3. ¿Cuáles son los principios sobre los que se fundamenta?

Las prácticas de SAFe se basan en nueve principios fundamentales que han evolucionado a partir de métodos ágiles de desarrollo de productos como Lean, el pensamiento sistémico y la observación de empresas exitosas. Estos nueve principios son:

  • Adoptar una visión económica.
  • Aplicar el pensamiento sistémico.
  • Asumir la variabilidad; preservar opciones.
  • Construir gradualmente y con velocidad en ciclos integrados de aprendizaje.
  • Hitos basados en la evaluación objetiva de los sistemas de trabajo.
  • Visualizar y limitar el WIP, reducir el tamaño de los lotes y gestionar longitudes de cola.
  • Aplicar la cadencia y la sincronización con la planificación de varios dominios.
  • Desbloquear la motivación intrínseca de los trabajadores del conocimiento.
  • Descentralizar la toma de decisiones.

4. Conclusiones

El objetivo de SAFe está en la síntesis de un conjunto de conocimientos y las lecciones aprendidas por cientos de implementaciones bajo un marco de desarrollo ágil. Con este framework se pretende promover un sistema de prácticas integradas y probadas que ha demostrado traer mejoras sustanciales en el compromiso de los empleados, el tiempo de comercialización, la calidad de la solución y la productividad de los equipos.

Como referencia adicional, les recomiendo el siguiente video que en menos de nueve minutos hace un resumen muy interesante lo que es SAFe en la práctica.



5. Referencias

Viewing all 989 articles
Browse latest View live