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

Tutorial ‘Simple Docker UI’

$
0
0

En este tutorial vamos a ver como podemos gestionar nuestras imágenes y contenedores a través de la herramienta Simple Docker UI

0. Índice de contenidos


1. Introducción

Siguiendo con la serie de tutoriales dedicados a Docker:

, vamos a continuar con una herramienta que nos va a permitir gestionar nuestras imágenes/contenedores Docker, Simple Docker UI

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.9.1, Docker Machine 0.5.2
  • Simple Docker UI 0.4.1

3. Simple Docker UI

Simple Docker UI es una aplicación para Chrome escrita con el siguiente Stack:

  • Scala.js
  • React on Scala.js
  • Bootstrap
  • Bower
  • Momentjs
, que nos permite interactuar con la API remota de Docker.

Veremos como podemos ejecutar los comandos típicos de Docker a través de una interfaz visual, de una manera muy cómoda (se acaban las excusas para no usar docker ;))


4. Instalación

Al tratarse de una aplicación de chrome basta con acceder al link

y pulsar al botón ADD TO CHROME, posteriormente podremos acceder a ella poniendo en el navegador chrome://apps/

Pulsamos en el icono, se abre la aplicación desde la pestaña ‘setting’ y nos muestra un mensaje advirtiéndonos que no hemos podido conectarnos.

Como hemos comentado en anteriores tutoriales, OSX no ofrece soporte nativo para docker, por lo que tenemos que usar Boot2Docker o lo que recomiendan actualmente Docker Machine. En este tutorial partimos de la base que tenemos instalado Docker Machine (a través de Docker ToolBox), esto hace que tengamos que realizar unos pasos extra en la configuración.

Por defecto Docker ejecuta con TLS habilitado, por lo que debemos instalar las credenciales que genera docker-machine y permitir que chrome las use. Para ello, ejecutamos desde la terminal, los siguientes comandos

security add-trusted-cert -k ~/Library/Keychains/login.keychain ~/.docker/machine/certs/ca.pem
	security import ~/.docker/machine/certs/key.pem -k ~/Library/Keychains/login.keychain
	security import ~/.docker/machine/certs/cert.pem -k ~/Library/Keychains/login.keychain

Ahora podemos comprobar a través del navegador https://192.168.99.100:2376/_ping que accedemos al DOCKER_HOST

Accedemos de nuevo a la pestaña de settings, pulsamos en el botón de ‘Verify’ y comprobamos que conexión es válida.

Ya tenemos todo listo.


5. imágenes

Una vez instalado Docker Simple UI, vamos a empezar a jugar con él, en primer lugar accedemos a la pestaña ‘Images’

Aún no tenemos ninguna imagen disponible, vamos a buscar una en Docker Hub por medio de la aplicación, rellenamos el cuadro de búsqueda (en este ejemplo buscamos la imagen oficial de Ubuntu)

Seleccionamos la imagen oficial de Ubuntu

Pulsamos en el botón ‘Pull Image’ y vamos comprobando como se va descargando

Una vez haya acabado la descarga nos aparecerá en el listado de imágenes

Pulsamos en el link del ID de la imagen y accedemos a sus detalles


6. Contenedores

A partir de la opción anterior podemos observar el historial de la imagen y realizar un borrado de la imagen con el botón ‘Remove’ (docker rmi …) o arrancar un contenedor a partir de esta imagen, botón ‘Start’ (docker run ….). Vamos a realizar esta operación :

Podemos ver que tenemos diferentes opciones para arrancar el contenedor:

  • Nombre del contenedor
  • Comando de inicio del contenedor
  • Puertos
  • Volúmenes
  • Variables de entorno

Podemos observar como en la parte inferior de la ventana modal, se va componiendo el comando docker run … . En este ejemplo simplemente vamos a añadir un nombre al contenedor

docker run -i -t -P --name miUbuntu ubuntu:latest /bin/bash

Pulsamos al botón ‘Run’ y dentro de la pestaña ‘Containers’ podemos ver nuestro contenedor ‘miUbuntu’

Accedemos a los detalles del contenedor pulsando el link de su ID

Esta pantalla nos da las opciones de parar el contenedor ‘Stop’ o borrarlo ‘Remove’ y unas opciones bastante interesantes como son el acceso al terminal (si hemos arrancado el contenedor con la opción -i -t )

Los procesos que se ejecutan dentro de nuestro contenedor, a través de la pestaña ‘Top’

O los cambios que se han producido en el filesystem, accediendo a la pestaña ‘File system changes’ (en este ejemplo hemos creado el directorio ‘prueba’)


7. Conclusiones

Hasta los ‘alérgicos’ a la consola tienen la posibilidad de adentrarse el mundo ‘Docker’ 😉 . Como estamos viendo en esta serie de tutoriales, el ecosistema Docker y todas las herramientas que surgen a su alrededor, están creciendo a un ritmo imparable .. apenas hemos rascado en las posibilidades que nos ofrece esta tecnología, seguiremos en ello…

Un saludo.


8. Referencias


Comentando el libro ‘Mi visión de lo posible’, de Luis Miguel Fernández Montañez

$
0
0

Comentando el libro: Mi visión de lo posible de Luis Miguel Fernández Montañez.

Un amigo me dejó este libro y, como (casi) siempre que hago un esfuerzo, trato de dejar un tangible tratando de comentar lo que he leído y/o me ha sugerido la lectura.

He de decir que me ha costado un poquito leerlo, y casi lo abandono al principio, porque me da la sensación de que (como dice un amigo, y algo de lo que yo también peco) “se puede contar lo mismo con menos palabras” pero avanzando en él veo ideas muy interesantes.

Comenta unas reflexiones sobre la gente de OnStrategy: “Ellos, enamorados del pasado no imaginan el futuro. Mezclan la estrategia con la operación. La presión del día a día les consume ¿habrá para ellos un buen mañana?” Con esta frase me vienen a la mente muchos responsables de tecnología que están más preocupados porque los sistemas no fallen que por actualizarse y dar valor en la transformación digital que estamos viviendo. Negocio les ve como un impedimento más que como una influencia positiva en el crecimiento.

Otra reflexión en la misma línea: “Tienen estrellas pero falta equipo. Trabajan muchos pero la mayoría no saben a dónde van. Capacitan a todos y no a los que más lo necesitan. Hay recompensas, faltan resultados. ¿Lograrán transformar su organización?”. Me recuerda el comentario de cafetería de una jefe de proyecto en el descanso de un curso más, en años, de dirección clásica de proyectos. Adicionalmente es fácil encontrar que las direcciones de las empresas se han convertido en gestores de millonarios presupuestos delegados en proveedores donde sus propios jefes de proyecto ni tienen el poder para influir con su conocimiento ni tienen ya el conocimiento para poder contribuir en la ejecución. Lástima disponer de gente tan competente perdidos en Excel y PowerPoints.

Hace referencia a una muy buena reflexión: “y si formamos a la gente y luego se marchan”, con otra: “y si no los formamos y encima se quedan”. Muchas organizaciones se han convertido en cementerios de elefantes donde los que quieren más dinamismo se van a emprender. En contrapartida, las empresas, para ganar dinamismo, contratan a grandes consultoras sin darse cuenta que ellas tienen el mismo problema. Retener el talento en estructuras pesadas, jerárquicas y con la misma gente dando muchos años servicio es harto complicado.

Dice “no se ama lo que no se conoce. Y no se sirve bien lo que no se ama. Equipos descontentos que no aman a su empresa no la servirán con pasión nunca. Harán su trabajo jurídicamente perfecto, pero nada más”. Como empresario he aquí algo sobre lo que pensar. Aunque cada vez estoy más convencido de que hay que hacer partícipe a la gente que trabaja contigo (a los más comprometidos se entiende) de la propiedad y ya llevo tiempo pensando en cómo hacerlo.

Cuenta cómo en una orquesta sinfónica el director dirige: “Aunque el director tiene la visión global, cada músico es responsable de que su instrumento brille con todo, que es lo que se define como armonía estética”. Es una buena similitud a un equipo brillante. Esto tendría que dar que pensar a las organizaciones donde la gente se pasa más tiempo luchando entre ellos que contribuyendo a obtener el mayor partido del esfuerzo.

Dice cómo “había leído un breve resumen de una encuesta de la consultora Gallup (2014) donde se afirma que el 60% de los trabajadores tienen en talento dormido, no están comprometidos con su empleo y no desarrollan las capacidades para dar lo mejor de sí mismos. Y el 50% no pone el suficiente entusiasmo”. Aquí diría que cada persona tiene unos objetivos personales en la vida y que pocos nos dedicamos a lo que realmente nos gusta o convertimos en un reto lo que nos encargan. La monotonía claramente es un enemigo que nos ataca a todos.

Referencia a Tom Peters “Si tú cuidas a la gente, la gente cuidará del servicio, el servicio cuidará del cliente, el cliente cuidará los beneficios, los beneficios cuidarán de la reinversión, la reinversión cuidará de la reinvención y la reinvención cuidará del futuro”. Vaya frase más buena. Comenta el mensaje subyacente que las personas encargadas de la externalización es muy posible que rompan un poco este ciclo ;-), y más cuando los tratas como eso: externos. No os podéis imaginar cómo muchas personas en grandes organizaciones aprovechan la asimétrica cliente-proveedor no dándose muchas veces cuenta de que los que tienen un sueldo fijo y un puesto fijo están dormidos en las redes sociales, no desarrollan contactos ni conocimientos de mercado, son ellos 😉

Cita a Deborah Hopkins “la cultura se come a la estrategia para desayunar” hablando de la burocracia de las organizaciones que actúa como eficaz anestesia. Comenta también lo poco bien visto que está preguntar en estos tiempo “y por qué?”.

Habla muy acertadamente (citando al Dr. Mario Alonso Puig) sobre la inteligencia especulativa y la práctica. No se conoce para saber sino para hacer. La burocracia habla de lo razonable y no de “mi visión de lo posible”. Me viene también a la mente frases del estilo “lo conseguí porque no sabía que era imposible”. Una de las cosas que prohibo nada más empezar un curso es que alguien diga “en esta empresa eso no se puede hacer”. Normalmente es más fácil pensar eso que “no soy suficientemente inteligente para encontrar el camino” o “no tengo ganas de hacer el intento”.

Muchas gracias al autor @lfernandezmo porque todos los que hemos escrito algo sabemos lo difícil que es y lo poco agradecidos que somos los lectores ;-).


Cómpralo en Casa del Libro

Automatización de copias de seguridad con Dropbox y Ansible

$
0
0

En este tutorial veremos como automatizar la creación de una copia de seguridad a la hora de instalar nuestras aplicaciones en una máquina remota gracias a Ansible y Dropbox.

Índice de contenidos


1. Introducción

Tarde o temprano a la hora de desarrollar una aplicación necesitamos tener una copia de respaldo que sea capaz de guardar el estado previo que tenían las aplicaciones ya sea almacenando, por ejemplo, periódicamete los datos de la base de datos. Este tutorial pretende automatizar todo el proceso de creación de una copia de seguridad que estará almacenada en Dropbox. Para ello utilizaremos Ansible, herramienta de automatización que ya he mencionado en otros tutoriales como éste.


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 Capitan 10.11
  • Ansible 1.9.3
  • Vagrant 1.7.2
  • Virtual Box

3. ¿Qué es dropbox uploader?

Dropbox Uploader es un script BASH que puede ser utilizado para subir y descargar archivos de Dropbox (entre muchas otras cosas), así como compartir, sincronizar o hacer copias de seguridad de estos archivos. ¿Por qué Dropbox Uploader? porque al ser bash nos va a permitir utilizarlo con Ansible, de forma que al crear la máquina en la que estará alojada nuestra aplicación, se hará la copia de seguridad de los ficheros que queramos de forma completamente automática.

Tras esta breve introducción vamos al lío :)


4. Automatización de copia de seguridad

Para automatizar las copias de seguridad en primera instancia necesitamos:

  • Instalar Virtualbox, Vagrant y Ansible.
  • Crear una aplicación de Dropbox (con la que nos comunicaremos gracias al script de Dropbox Uploader).
  • Ejecutar los playbook de Ansible y disfrutar de la “magia”. 😀

4.1. Instalación previa

Para este tutorial partimos de un entorno con VirtualBox, Vagrant y Ansible instalados. Si queréis saber como se instalan estas herramientas así como una breve descripción de las mismas, se pueden ver desde este tutorial.


4.2. Preparación del entorno

Antes de meternos en el meollo de crear máquinas virtuales y trastear con ellas, vamos a crear la aplicación de Dropbox. Para ello necesitamos crearnos una cuenta de Dropbox si no la tenemos todavía. Por defecto las cuentas de Dropbox vienen con 2GB de memoria, más que suficiente para el backup de nuestros datos de prueba.


4.2.1. Creación de la aplicación de Dropbox

Si no tenemos una cuenta de Dropbox creada puedes crearla de forma completamente gratuita desde aquí

.

Una vez registrados con nuestra cuenta de Dropbox accedemos a https://www.dropbox.com/developers/apps dónde comenzaremos con la creación de la aplicación.

En primer lugar, pinchamos en create app.

A continuación configuramos las características de la aplicación. En nuestro caso elegiremos el API de Dropbox, el tipo de acceso solo al directorio de la aplicación y nombraremos a la copia de seguridad con el nombre tutorial backup.

Al volver a pinchar en el botón create app nos llevará a una pantalla de configuración donde veremos la key y el secret de la aplicacion, que son los datos que a nosotros nos interesan para poder acceder a Dropbox desde la terminal con Dropbox Uploader.


4.2.2. Creación y configuración previa de la máquina virtual

Una vez creada la aplicación de Dropbox vamos a crear la máquina virtual en la que realizaremos la copia de seguridad, y vamos a configurarla de forma sencilla para que tenga un PostgreSQL y una base de datos de prueba de la que haremos el backup.

Para ello selecionamos un directorio en el cual pondremos la configuración de Vagrant (en mi caso está dentro de maquinas-virtuales/tutorial-backup) y ejecutamos el comando

vagrant init
mkdir maquinas-virtuales/tutorial-backup
cd maquinas-virtuales/tutorial-backup
vagrant init

Deberían de crearse un fichero llamado Vagrantfile en el que configuraremos nuestra máquina virtual. Para ello lo abrimos con nuestro editor preferido y lo dejamos con las siguientes líneas:

Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.network "private_network", ip: "192.168.33.10"
end

En estas líneas le estamos indicando a Vagrant con que “box” queremos iniciar el sistema y la IP específica que va tener nuestra máquina virtual.

Una vez configurada la máquina, con el comando vagrant up levantaremos la máquina virtual.

Accedemos a la máquina con el comando vagrant ssh y procederemos a configurar manualmente la configuración de acceso a Dropbox. Espera un momento… ¿Manualmente? ¿No decíamos que todo era automático? Efectivamente así es, pero para poder automatizar todo el proceso y que este sea replicable para la misma aplicación de Dropbox debe configurarse previamente un fichero de configuración de forma manual, que luego exportaremos via Ansible de forma automática.

Para ello cogeremos manualmente el script de dropbox_uploader con el comando:

curl "https://raw.githubusercontent.com/andreafabrizi/Dropbox-Uploader/master/dropbox_uploader.sh" -o dropbox_uploader.sh

Cambiamos los permisos del fichero para que sea ejecutable y ejecutamos el fichero. Al hacerlo por primera vez nos pedirá datos relativos a la aplicación de Dropbox, y este es el motivo por el cual es necesario hacer la primera vez el proceso manual. Introducimos los datos que nos pide sobre la aplicación (app key, app secret, y los permisos sobre la aplicación o sobre todo el dropbox) y verificamos la url que nos proporciona.

chmod 777 dropbox_uploader.sh
./dropbox_uploader.sh

Tras hacer esta configuración debería de crearse en el HOME del usuario que estamos utilizando un fichero llamado .dropbox_uploader que contiene toda la información relevante a nuestro acceso a Dropbox por el script. De hecho, al ejecutarse el script sin parámentros comprueba si este fichero se encuentra en el HOME del usuario con el que se ejecuta, y si no lo crea (que es el caso que acabamos de comprobar). A continuación copiaremos este fichero al directorio /vagrant, que es un directorio compartido entre la máquina virtual y la real donde nos llevaremos la configuración que acabamos de realizar para automatizar el proceso con Ansible.

cp /home/vagrant/.dropbox_uploader /vagrant/

Ahora es cuando entra en juego ansible 😀


4.2.3. Automatización del backup con Ansible

Por ahora nuestra máquina no tiene ni base de datos ni copia de seguridad. Para crear la base de datos y automatizar las copias de seguridad utilizaremos Ansible. Dentro de la carpeta tutorial-backup crearemos el directorio ansible, donde se pondrán todos los ficheros relacionados con la automatización.

Comprobamos como no solo está el fichero Vagrantfile que usamos para crear la máquina virtual si no que la primera vez que levantas la máquina virtual se crea un directorio .vagrant. Además también vemos el fichero .dropbox_uploader que hemos movido previamente. ahora dentro de ansible vamos a crear la siguente estructura (los que acaban con / son directorios):

Estructura ansible
ansible
	|_environment/
	|    |_development/
	|       |_inventory
	|       |
	|       |_group_vars/
	|           |_development
	|
	|_roles/
	|   |_database/
	|   | 	|_files/
	|   | 	|   |_booktown.sql
	|   | 	|
	|   | 	|_tasks/
	|   |    	|_main.yml
	|   |
	|   |_dropbox_backup/
	|   |   |_files/
	|   |   |   |_.dropbox_uploader
	|   |   |
	|   |   |_tasks/
	|   |   |   |_backup_setup.yml
	|   |   |   |_main.yml
	|   |   |   |_restore_backup.yml
	|   |   |
	|   |   |_templates/
	|   |	      |_backup.sh
	|   |
	|   |_postgres/
	|   |_tasks/
	|       |_common_os_setup
	|       |_main.yml
	|       |_postgres_install.yml
	|
	|_playbook.yml

Como vemos en la estructura nuestro fichero .dropbox_uploader lo situaremos dentro de ansible/roles/dropbox_backup/files/

A continuación creamos los ficheros necesarios para el funcionamiento de ansible.

environment/development/inventory
[development]
192.168.33.10

En el inventory se especifican los grupos de máquinas del entorno (en nuestro caso solo tenemos un grupo de máquinas con una máquina asociada) con la IP de las máquinas asociadas a ese grupo. Precisamente por este motivo especificamos la IP de la máquina virtual en el Vagrantfile anteriormente.

environment/development/group_vars/development
---

postgres:
    version: 9.3

datasource:
    dbname: booktown
    dbuser: booktown
    dbpassword: booktownpass

dropbox_backup:
    days_to_keep: 30
    directory: /home/backup
    uploader_script_folder: /opt/dropbox_uploader
    enable_restore: false
    backup_filename: no-file
    configuration_folder: /opt/dropbox_uploader/config

Este es el fichero de variables que utilizaremos en los playbooks. Ya veremos para que sirve cada una luego. 😀

A continuación antes de meternos en los roles vamos a ver como está estructurado el playbook, que es el orquestador que indica en que orden (y en que condiciones) se ejecuta cada rol.

playbook.yml
---

# file: playbook.yml

- hosts: development
  sudo: yes
  gather_facts: no
  roles:
      - postgres

- hosts: development
  sudo: yes
  sudo_user: postgres
  gather_facts: no
  roles:
      - database

- hosts: development
  sudo: yes
  gather_facts: no
  roles:
      - dropbox_backup

Como vemos primero se ejecutará el rol encargado de instalar PostgresSQL, luego se creará y se inicializará la base de datos y por último se automatizará la copia de seguridad. Echemos un vistazo rápido a cada uno de estos roles, hasta llegar al de Dropbox que es el que nos interesa.

roles/postgres/tasks/main.yml
---

# file: roles/postgres/tasks/main.yml

- include: common_os_setup.yml
- include: postgres_install.yml

En este fichero se indica el orden de ejecución de las demás tareas mediante include.

roles/postgres/tasks/common_os_setup.yml
---

# file: roles/postgres/tasks/common_os_setup.yml

- name: ensure apt cache is up to date
  apt: update_cache=yes

- name: ensure the language packs are installed
  apt: name={{item}}
  with_items:
  - language-pack-en
  - language-pack-es

- name: reconfigure locales
  command: sudo update-locale LANG=en_US.UTF-8 LC_ADDRESS=es_ES.UTF-8 LC_COLLATE=es_ES.UTF-8 LC_CTYPE=es_ES.UTF-8 LC_MONETARY=es_ES.UTF-8 LC_MEASUREMENT=es_ES.UTF-8 LC_NUMERIC=es_ES.UTF-8 LC_PAPER=es_ES.UTF-8 LC_TELEPHONE=es_ES.UTF-8 LC_TIME=es_ES.UTF-8

- name: ensure apt packages are upgraded
  apt: upgrade=yes

Aquí nos encargamos de realizar las tareas básicas de asegurarnos de que el sistema está actualizado correctamente y añadimos los paquetes de español e inglés.

roles/postgres/tasks/postgres_install.yml
---
# file: /roles/postgres/tasks/postgres_install.yml

- name: ensure PostgreSQL are installed
  apt: name={{item}}
  with_items:
      - postgresql-{{postgres.version}}
      - postgresql-contrib-{{postgres.version}}
      - python-psycopg2

- name: ensure client's encoding is UTF-8
  lineinfile:
      dest: /etc/postgresql/{{postgres.version}}/main/postgresql.conf
      backup: yes
      insertafter: "#client_encoding = sql_ascii"
      line: "client_encoding = utf8"

- name: ensure PostgreSQL is running
  service: name=postgresql state=restarted enabled=yes

En este rol otorgamos acceso a PostgresSQL desde otros host.

roles/database/tasks/main.yml
---
# file: /roles/database/tasks/main.yml

- name: ensure database is created
  postgresql_db:
    name: "{{datasource.dbname}}"
    encoding: UTF-8
    lc_collate: es_ES.UTF-8
    lc_ctype: es_ES.UTF-8
  tags: database

- name: ensure user has access to database
  postgresql_user:
    db: "{{datasource.dbname}}"
    name: "{{datasource.dbuser}}"
    password: "{{datasource.dbpassword}}"
    priv: ALL
  tags: database

- name: Copy database default script
  copy: src="booktown.sql" dest=/tmp mode=644
  tags: database

- name: Add sample data to database
  shell: psql {{datasource.dbname}} < /tmp/booktown.sql
  when: dropbox_backup.enable_restore == false
  tags: database

- name: Delete database default script
  file: path=/tmp/booktown.sql state=absent
  tags: database

En este rol creamos la base de datos y la inicializamos con una base de datos de prueba (de la que luego haremos el backup).

En el directorio ansible/roles/dropbox_backup/files/ se encuentra nuestro fichero .dropbox_uploader que tiene la configuración necesaria para acceder a Dropbox.

roles/dropbox_backup/tasks/main.yml
---

# file: roles/dropbox_backup/tasks/main.yml

- include: backup_setup.yml
- include: restore_backup.yml
  when: dropbox_backup.enable_restore

Igual que el main del rol de postgres, es encargado de orquestar el orden de las tareas del rol por ficheros para ordenar su contenido.

Es interesante ver ahora el módulo when, ya que quiere decir que el contenido de esa tarea (en este caso hacer el backup), solo se va a realizar si la expresión es resuelta a true. De modo que como ahora solo queremos hacer una copia de seguridad, nos aseguramos de que la propiedad enable_restore dentro del grupo de propiedades de dropbox_backup está puesta a false dentro de las variables que se utilizan a lo largo del playbook. Si nos fijamos en el fichero development en el que establecíamos las variables anteriormente esta propiedad tiene el valor false.

Es precisamente por que queremos hacer una copia de seguridad que también hemos usado el módulo when a la hora de inicializar la base de datos ya que cuando hagamos el restore de la base de datos después no queremos que se inicialize la base de datos.

roles/dropbox_backup/tasks/backup_setup.yml
---

# file: roles/dropbox_backup/tasks/backup_setup.yml

- name: Create backups directories
  file: path={{item}} state=directory mode=0755
  with_items:
    - "{{dropbox_backup.uploader_script_folder}}"
    - "{{dropbox_backup.directory}}"
    - "{{dropbox_backup.configuration_folder}}"
  tags:
    - backup

- name: Download dropbox_uploader
  get_url: url=https://raw.githubusercontent.com/andreafabrizi/Dropbox-Uploader/master/dropbox_uploader.sh dest={{dropbox_backup.uploader_script_folder}}/dropbox_uploader.sh mode=0755
  tags:
    - backup

- name: Copy dropbox configuration file
  copy: src=.dropbox_uploader dest="{{dropbox_backup.configuration_folder}}/.dropbox_uploader" mode=0600
  tags:
    - backup

- name: Copy dropbox script
  template: src=backup.sh dest="{{dropbox_backup.configuration_folder}}/backup.sh" mode=0600
  tags:
    - backup

- name: Create cron job to perform five minute backups
  cron: name="Dropbox backups" minute="*/5" hour="*" job="sh "{{dropbox_backup.configuration_folder}}"/backup.sh >> /var/log/backup.log 2>&1"
  tags:
    - backup

Este es el rol que nos interesa, vamos a entrar en profundidad en como se realiza:

  • En primer lugar crearemos el directorio de backup, que será el directorio en el que copiaremos el sql que queremos subir a dropbox.
  • Después nos descargamos el dropbox_uploader en el destino que elijamos por parámetro. Esto es exactamente la misma acción que hemos realizado antes a mano con el comando curl ya que cuando hagamos un restore de la máquina necesitaremos del script para bajarnos nuestra copia de seguridad de dropbox.
  • Copiamos después la configuración que habíamos realizado manualmente donde se lo hayamos especificado en nuestras variables de configuración (en nuestro caso /opt/dropbox_uploader/config).
  • Copiamos el script (del que hablaremos en breves) encargado de hacer el dump de la base de datos, mover todos los archivos a la carpeta de backup, subirlo y borrar los backups más antiguos.
  • Por ultimo usamos el módulo cron de ansible para programar tareas y programamos nuestro job para que haga una copia de seguridad cada cinco minutos (para que veamos de forma temprana la salida).
roles/database/tasks/backup.sh
#!/bin/bash

hostname=`hostname | awk -F. '{print $1}'`
date=$(date +"%d-%m-%Y")
database="{{datasource.dbname}}"
uncompressed_backup_db_file="{{dropbox_backup.directory}}/${database}.sql"
backup_db="/tmp/${hostname}-${date}.sql.tar.gz"
backup_dirs="{{dropbox_backup.directory}}"
backup_filename="/tmp/${hostname}-${date}.tar.gz"
dropbox_uploader="{{dropbox_backup.uploader_script_folder}}/dropbox_uploader.sh -f "{{dropbox_backup.configuration_folder}}"/.dropbox_uploader"

echo "[$(date +"%d-%m-%Y-%T")] Database dump..."
sudo -u postgres pg_dump $database > $uncompressed_backup_db_file

echo "\n[$(date +"%d-%m-%Y-%T")] Compress data..."
tar -czvf $backup_filename $backup_dirs $uncompressed_backup_db_file

# Send to Dropbox
echo "\n[$(date +"%d-%m-%Y-%T")] Upload to dropbox..."
$dropbox_uploader upload $backup_filename /

# Delete local backup
sudo rm $uncompressed_backup_db_file
sudo rm -rf $backup_filename
sudo rm -rf $backup_db

# Delete old remote backups
echo "\n[$(date +"%d-%m-%Y-%T")] Delete old remote backups..."
delete_date=`date --date="-{{dropbox_backup.days_to_keep}} day" +%d-%m-%Y`
$dropbox_uploader delete "${hostname}-${delete_date}.tar.gz"

Es un script sencillo que indica el proceso de realización del backup. En primera instancia se puede ver como se hace un dump de la base de datos, luego comprime los datos y los envía a Dropbox. Una vez subido elimina todos los ficheros y como todos los ficheros que se suben tienen una nomenclatura busca el fichero de hace 30 días para borrarlo de dropbox y que así no nos quedemos nunca sin espacio

El último fichero que nos queda es el de realizar una restauración de la copia de seguridad. Cuando queramos restaurar una copia de seguridad en una máquina nueva simplemente tenemos que cambiar el estado de la propiedad enable_restore a true, y decidir qué copia de seguridad queremos utilizar para levantar la base de datos y ponerla en la variable backup_filename. El conjunto de tareas encargado de hacer el restore son:

roles/database/tasks/main.yml
---

# file: roles/dropbox_backup/tasks/restore_backup.yml

- name: Get backup from dropbox
  command: "{{dropbox_backup.uploader_script_folder}}/dropbox_uploader.sh -f {{dropbox_backup.configuration_folder}}/.dropbox_uploader download {{dropbox_backup.backup_filename}} /tmp"
  tags:
    - restore

- name: Change backup permission
  file: path=/tmp/{{dropbox_backup.backup_filename}} mode=666
  tags:
    - restore

- name: Decompress backup file
  command: tar -xzvf /tmp/{{dropbox_backup.backup_filename}} chdir=/tmp
  tags:
    - restore

- name: Restore database
  shell: sudo -u postgres psql {{datasource.dbname}} < /tmp/home/backup/{{datasource.dbname}}.sql
  tags:
    - restore

- name: Delete backups files
  file: path={{item}} state=absent
  with_items:
    - /tmp/home
    - "/tmp/{{dropbox_backup.backup_filename}}"
  tags:
    - restore
  • En primer lugar obtenemos el backup de Dropbox y lo mantenemos en el directorio /tmp.
  • Descomprimimos el backup y restauramos la base de datos.
  • Por último eliminamos estos ficheros temporales y ya tendremos el mismo estado que teníamos en la base de datos.

4.3. Comprobación

Ahora vamos a comprobar como se crea la base de datos y como se genera una copia de seguridad a lo largo del tiempo.

Para comprobar que se ejecuta desde la creación de la máquina vamos a cargarnos la máquina que hemos creado antes y volver a crearla desde cero:

cd maquinas-virtuales/tutorial-backup
vagrant destroy
(y)
vagrant up

Ejecutamos el playbook de Ansible para que ejecute el playbook en la máquina virtual. Para ello ejecutaremos el siguiente comando desde dentro de la carpeta ansible:

cd /Users/aortiz/maquinas-virtuales/tutorial-backup/ansible
ansible-playbook -i environment/development/inventory --private-key=/Users/aortiz/maquinas-virtuales/tutorial-backup/.vagrant/machines/default/virtualbox/private_key playbook.yml -vv -u vagrant

NOTA: Para que no te de un problema de autenticación tienes que quitar la línea correspondiente a la IP del fichero localizado en /home/{usuario}/.ssh/known_hosts antes de ejecutar el playbook otra vez.

Justo al comenzar os pedirá verificación por ser un host desconocido. Aceptamos y dejamos a Ansible realizar las tareas. Una vez finalizado deberíamos ver algo así:

Es importante notar como las últimas tareas están en color azul. Esto quiere decir que estas tareas se han omitido gracias al módulo when del que hablábamos antes. Queremos hacer una copia de seguridad, no una restauración del sistema.

Ahora mismo ya tenemos nuestras copias de seguridad funcionando y generándose automáticamente. Para comprobarlo vamos a ver los log del proceso de backup que están localizados en el directorio /var/log/backup.log

Vemos como en un principio comprime los datos (solo el .sql) y lo sube a Dropbox:

[03-12-2015-12:00:01] Upload to dropbox...
 > Uploading "/tmp/vagrant-ubuntu-trusty-64-03-12-2015.tar.gz" to "/vagrant-ubuntu-trusty-64-03-12-2015.tar.gz"... DONE

Destacar como al final elimina los backups remotos:

[03-12-2015-12:05:09] Delete old remote backups...
 > Deleting "/vagrant-ubuntu-trusty-64-03-11-2015.tar.gz"... FAILED

Este último falla ya que no tenemos ninguna copia de seguridad del día 3/11/2015.

NOTA: Hay que destacar que solo se genera una copia de seguridad por día ya que se va sutituyendo la previa cada 5 minutos. Lo suyo es hacer una copia de seguridad a media noche, pero aquí hemos acelerado el periodo del cron para ver el funcionamiento.

Comprobamos que el archivo está subido a Dropbox:

Por último vamos a hacer una restauración del sistema, de modo que al instalar nuestros datos en la nueva máquina, queremos conservar el estado de la base de datos de la copia de seguridad que queramos. Para ello, eliminaremos la máquina virtual de nuevo, y utilizaremos el mismo playbook para que se quede todo tal y como estaba cambiando en las propiedades de Ansible el enable_restore a true y en backup_filename ponemos el nombre de nuestra copia de seguridad.

environment/development/group_vars/development
---

postgres:
    version: 9.3

datasource:
    dbname: booktown
    dbuser: booktown
    dbpassword: booktownpass

dropbox_backup:
    days_to_keep: 30
    directory: /home/backup
    uploader_script_folder: /opt/dropbox_uploader
    configuration_folder: /opt/dropbox_uploader/config
    enable_restore: true
    backup_filename: vagrant-ubuntu-trusty-64-03-12-2015.tar.gz

Volvemos a ejecutar el playbook que habíamos hecho antes.

NOTA: Repito que para que no te de un problema de autenticación tienes que quitar la línea correspondiente a la IP del fichero localizado en /home/{usuario}/.ssh/known_hosts antes de ejecutar el playbook otra vez.

Comprobamos dos cosas:

  • No se ha ejecutado la tarea que introducía los datos de la base de datos previamente.
  • Al meternos en la base de datos, los datos del .sql están presentes.

5. Conclusiones

Esta es una forma sencilla de tener una copia de seguridad de nuestras aplicaciones, o simplemente de nuestros archivos (cambiando un poco el script) en Dropbox de una manera automática y sencilla en Linux. Sobretodo lo importante es coger el concepto de que si es algo que vas a repetir en varios sistemas, lo mejor es tener un playbook automatizado que te ayude a replicar exactamente el mismo comportamiento en otras máquinas. 😀

Puedes ver la configuración de Vagrant y Ansible desde el repositorio GitHub https://github.com/JuananOc/tutorial-backup.


6. Referencias

PlantUML – Dibuja diagramas UML de forma sencilla

$
0
0

En este tutorial aprenderemos qué es PlantUML y cómo se puede utilizar. Detallaremos mediante ejemplos los tipos de diagramas que se pueden hacer con esta herramienta.

Índice de contenidos


1. Introducción

PlantUML es una herramienta de dibujo de diagramas. Soporta varios de los diagramas más usados. Entre ellos destacan:

  • Diagramas de Secuencia
  • Diagramas de Clases
  • Diagramas de Estados
  • Diagramas de Actividad

Una de sus ventajas es que se puede integrar con una gran cantidad de aplicaciones. Nosotros lo hemos probado con Atom, en el que dispone de un paquete que traduce al vuelo el código que se va escribiendo (https://atom.io/packages/plantuml-viewer), de forma que dibuja en el momento en el que escribes. También se puede integrar en IDEs, como eclipse, en los que por medio de anotaciones se pueden acompañar diagramas dentro de una clase java.

Otra de sus ventajas es la simplicidad e intuitividad de su sintaxis, por ejemplo, para hacer una flecha en un diagrama de secuencia se usa “->”; si se necesita una flecha discontinua se usaría “–>”; una relación de herencia en un diagrama de clases “<|--”.

Es importante recordar que es una herramienta de dibujo. Esto quiere decir que es posible dibujar diagramas inconsistentes si no se tiene algo de cuidado.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: 2 Ghz Intel Core I7, 8GB DDR3
  • Sistema Operativo: OS X El Capitan
  • Entorno de desarrollo: Atom
  • Entorno de desarrollo: Eclipse Mars

3. Instalación

Para el correcto funcionamiento de PlantUML deberemos tener instalado software adicional: Graphviz. Es necesario para poder generar la mayoría de los diagramas.

Finalmente, descargamos el jar correspondiente a PlantUML para ponernos manos a la obra.


4. Diagramas de secuencia

Comencemos, pues, a dibujar. Lo principal es empezar cada uno de nuestros diagramas con @startuml y terminarlos con @enduml. Veremos mediante ejemplos qué cosas se pueden hacer con esta herramienta, desde algo sencillo a algo más complejo. La complejidad y el tamaño de los diagramas puede ser atroz. Por si os queréis hacer una idea, en la documentación se habla de un problema de memoria para diagramas de mas de 20000 x 10000px, pudiéndose arreglar aumentando la memoria que usa la JVM. Nosotros no vamos a llegar a tanto, empezamos con un diagrama sencillo tal que así:

@startuml

      Juan -> Paco: saluda
      Juan <-- Paco: responde
      Juan -> Jesús: saluda
      Juan <-- Jesús: responde

  @enduml

Que genera el siguiente diagrama:
Diagrama1

También podemos declarar distintos participantes en el diagrama, como un actor (el monigote de los diagramas) o una base de datos, entre otros. Incluso añadir notas, o cambiar la apariencia de todo. Completando el ejemplo anterior… (lo siento Jesús, te he convertido en baseDeDatos)

@startuml

    actor Juan
    database baseDeDatos #green

    Juan -> baseDeDatos: saludas
    note right: cortesía
    Juan <-- baseDeDatos: responde

    Juan -> Paco: saluda
    Paco -> Paco: ¿cómo se \nllamaba \neste tío?
    Juan <-- Paco: responde

  @enduml

Diagrama2

Como podemos ver, las posibilidades se hacen enormes según descubrimos nuevos elementos que añadir en nuestros diagramas, así como formas de combinarlos. En el siguiente código podemos ver una muestra de las posibilidades:

@startuml

    == Comienzo==

    Elemento1 -X Elemento2
    Elemento2 -[#0000FF]-\ Elemento1
    Elemento2 <-> Elemento1

    == Final ==

    Elemento1 ->o Elemento2

  @enduml

Diagrama3

Con esto terminamos con los diagramas de secuencia, podemos hacernos una idea de las posibilidades.


5. Diagramas de clases

Lo siguientes diagramas que tenemos en la lista son los diagramas de clases, vamos a analizarlos. A continuación exponemos las principales operaciones entre clases: Herencia, composición y agregación.

@startuml

    Clase1 <|-- Clase2
    Clase1 <|-- Clase3
    Clase4 *-- Clase5
    Clase6 o-- Clase7

  @enduml

Diagrama4

Como hemos hecho antes, ahora completamos el diagrama con más elementos para demostrar la potencia: nombres a las relaciones, métodos, etc.

@startuml

    class Clase1{
      toString()
    }

    class Clase2 {
      String[] elementos
      mostrarElementos()
    }

    Clase1 <|-- Clase2
    Clase1 <|-- Clase3

    Clase4 "1" *-- "n" Clase5 :agregación
    Clase6 o-- Clase7

  @enduml

Diagrama5

Tenemos muchas más opciones para relacionar clases entre sí, más ejemplos:

@startuml

    Clase1 .. Clase2
    Clase1 -- Clase3
    Clase3 --> Clase4
    Clase4 <|.. Clase5
    Clase6 ..> Clase7
    Clase7 ..|> Clase8
    Clase8 <--* Clase9

  @enduml

Diagrama6

Para acabar con este tipo de diagramas, vamos a hablar de la visibilidad de métodos y atributos, así como la posibilidad de trabajar con interfaces o clases abstractas, dejando muchas cosas en el tintero para que podáis investigar (y no os de tanta pereza leer un artículo tan largo).

@startuml

    abstract Abstracta{
      -privado
      #protected
      ~package_private()
      +public()
    }

    interface Interfaz {
      metodo()
    }

    enum TimeUnit {
      DIAS
      HORAS
      MINUTOS
    }

    class Clase2 #red

    Abstracta <|-- Clase2
    Abstracta <|-- Clase3

    Interfaz <|.. Clase4
    Interfaz <|.. Clase5

  @enduml

Diagrama7

6. Diagramas de estados

Seguimos con los diagramas de estados de toda la vida, que simbolizan un esquema de transiciones entre diversos estados.

@startuml

    [*] --> Nacimiento
    Nacimiento : sin preocupaciones
    Tropiezo: se lió parda

    Nacimiento --> Tropiezo : torpeza
    Tropiezo -> Muerte : SAMUR no llega a tiempo
    Muerte --> [*]

  @enduml

Diagrama8

Se pueden tener además estados compuestos. Completando el ejemplo anterior con un estado compuesto nos quedaría algo como lo siguiente:

@startuml

    [*] --> Nacimiento
    Nacimiento : sin preocupaciones


    state Tropiezo {
     Agilidad: lo esquiva
     Torpeza: se lio parda
    }

    Nacimiento --> Tropiezo : torpeza
    Agilidad -> Vida : felicidad
    Torpeza -> Muerte : SAMUR no llega a tiempo
    Torpeza -> Vida : SAMUR llega a tiempo
    Vida --> Muerte
    Muerte --> [*]

  @enduml

Diagrama9

A grandes rasgos es todo para este tipo de diagramas. Señalar que las flechas de transiciones entre estados se adaptan bastante bien a los elementos con los que puedan colisionar.


7. Diagramas de actividad

Para ilustrar este tipo de diagramas os mostraré un ejemplo de la documentación de PlantUML que muestra la actividad de un servlet container haciendo uso de la mayoría de opciones que podemos usar en este tipo de diagramas. Se puede observar que el código es bastante legible, en consonancia con lo visto en apartados anteriores. Además, se puede comprobar que aunque los diagramas crezcan en tamaño, siguen dibujandose de forma que nada se “pisa”.

@startuml
    title Servlet Container

    (*) --> "ClickServlet.handleRequest()"
    --> "new Page"

    if "Page.onSecurityCheck" then
      ->[true] "Page.onInit()"

      if "isForward?" then
       ->[no] "Process controls"

       if "continue processing?" then
         -->[yes] ===RENDERING===
       else
         -->[no] ===REDIRECT_CHECK===
       endif

      else
       -->[yes] ===RENDERING===
      endif

      if "is Post?" then
        -->[yes] "Page.onPost()"
        --> "Page.onRender()" as render
        --> ===REDIRECT_CHECK===
      else
        -->[no] "Page.onGet()"
        --> render
      endif

    else
      -->[false] ===REDIRECT_CHECK===
    endif

    if "Do redirect?" then
     ->[yes] "redirect request"
     --> ==BEFORE_DESTROY===
    else
     if "Do Forward?" then
      -left->[yes] "Forward request"
      --> ==BEFORE_DESTROY===
     else
      -right->[no] "Render page template"
      --> ==BEFORE_DESTROY===
     endif
    endif

    --> "Page.onDestroy()"
    -->(*)
  @enduml

Diagrama10

8. Otros diagramas

Finalizaremos aquí el repaso más detallado a los distintos diagramas que se pueden crear, puesto que ya os podéis hacer una idea de la potencia de esta herramienta. Sin embargo, vale la pena mencionar algunos de los diagramas que nos dejamos por el camino, junto con una imagen creada con PlantUML:

  • Diagrama de casos de uso

  • Diagrama11
  • Diagrama de componentes

  • Diagrama12
  • Diagrama de despliegue

  • Diagrama13

9. Otros diagramas

Como hemos introducido al principio, PlantUML se puede integrar con multitud de IDEs y editores de texto: Eclipse, Netbeans, Intellij, Atom, Sublime, Emacs, etc.

Nosotros lo hemos probado con Atom, en el que dispone de un paquete que traduce al vuelo el código que se va escribiendo (plantuml-viewer), de forma que dibuja en el momento en el que escribes. Se instala desde el gestor de paquetes de Atom (la pestaña de Install en las opciones).


Captura de pantalla atom

Como vemos arriba, buscamos plantuml e instalamos 2 paquetes. Primero plantuml-viewer para la traducción al vuelo de nuestro código así como la posibilidad de guardar el diagrama generado. Por último language-plantuml para que Atom reconozca la sintaxis de PlantUML.

Una vez hecho todo esto, ya se puede trabajar con él:


Captura de pantalla atom paquetes

También se puede integrar en IDEs, como eclipse. Para instalarlo en eclipse se necesita instalar mediante la opción Install New Software, según se indica en la web de PlantUML.

Por medio de anotaciones se pueden crear diagramas dentro de una clase java de la misma forma que hemos creado con anterioridad.


Captura de pantalla eclipse anotaciones

Sin embargo, lo más interesante de este plugin es que también crea de forma automática diagramas del contexto en el que te encuentres, por ejemplo, creando una clase que hereda de otra con sus métodos y sus atributos:


Captura de pantalla eclipse automático

10. Conclusiones

Como hemos podido comprobar, nos encontramos ante una forma muy potente de dibujar diagramas, que se puede integrar con muchas de las herramientas de edición más utlizadas. Además, la traducción al vuelo es una feature muy interesante, el permitir ver instantáneamente reflejados los cambios en el código se agradece enormemente. Os animo a probarlo y comentar la entrada con vuestras conclusiones.


11. Referencias

Revisión de ‘Test-Driven Development by Example’, de Kent Beck

$
0
0

En esta ocasión revisamos uno de los libros fundamentales sobre una técnica de programación que debería ser indispensable en las costumbres de todo buen programado: el desarrollo dirigido por test o TDD.

A estas alturas, si te consideras un buen programador o al menos aspiras a serlo, deberías conocer el diseño dirigido por test o Test Driven Development (TDD). Se trata de una técnica redescubierta (tal y como aclara el autor quitándole méritos) y popularizada por Kent Beck. Y este libro es precisamente el que se puede considerar como obra oficial de difusión de TDD por parte de su “redescubridor”.

Por si no lo conoces, TDD es una técnica de programación que consiste básicamente en programar en pequeñas y seguras iteraciones siguiendo este ciclo:

  • En primer lugar se comienza expresando una funcionalidad a través de un test sin que haya código que lo satisfaga. Esto dará un fallo de test en “rojo”
  • Se programa el código mínimo que hace pasar el test. Ahora tenemos un test en “verde”.
  • Una vez que tenemos el test en verde, refactorizamos el código para eliminar duplicidades y mejorarlo, pero siempre conservando el semáforo verde.

Repitiendo este ciclo, añadiendo cada vez más funcionalidad expresada con test, logramos tener un código ya testeado, orientado a la funcionalidad, probablemente de mayor calidad y viviremos más felices o al menos con un nivel de ansiedad reducido. Aunque también tiene sus partes negativas: algo más de esfuerzo a corto plazo y cierta sensación de falsa seguridad entre otras cosas, aunque esto daría para un debate.

Si no la conoces, te recomiendo que te formes sobre esta disciplina, bien con este libro que aquí comentamos, o si me lo permites, te recomiendo ‘Diseño Ágil con TDD’ de Carlos Ble, que me parece una gran introducción en castellano. Y cómo no, en Autentia nos tomamos muy en serio el TDD, así que tenemos unas cuantas entradas que tratan sobre el tema en este portal de Adictos al Trabajo: http://www.adictosaltrabajo.com/?s=TDD.

Volviendo a la revisión del libro, podemos considerar que es la obra básica a partir de la cual se expande la idea de TDD. El autor, Kent Beck, a mi juicio emplea una forma muy acertada para transmitir su mensaje: utilizando ejemplos. Si quieres convencer a un programador para que utilice una técnica mejor demuéstrale sobre su terreno: con código y explicaciones de las decisiones sobre ese código.

El libro comienza directamente con un ejemplo de código, que desarrolla a lo largo de 17 capítulos y que conforma la primera de las tres partes del libro. Este ejemplo es uno de los paradigmáticos dentro de la programación de sistemas de gestión: “el dinero”. Parte de una situación real a la que Kent Beck tuvo que enfrentarse en su vida de programador, y que le sirve como excusa para aplicar TDD: a través de pequeños cambios realizados poco a poco, test a test, refactorización a refactorización, va transformando el código hasta llegar a una solución final de altísima calidad, que a buen seguro no hubiera llegado sin emplear TDD.

Este ejemplo inicial está escrito en Java básico y no emplea ningún framework de testing tal y como los conocemos hoy en día. Como ya he hecho notar, se trata de un libro de 2002 así que no había el desarrollo que hay ahora (gracias precisamente a Kent Beck y libros como éste), ni tampoco parece pertinente ensuciar las explicaciones empleando “magia”. Seguro que si no estás familiarizado con TDD, en alguna parte te resultarán evidentes los pasos que da, pero es porque el autor es muy meticuloso con la justificación de cada test, de cada implementación y de cada cambio. Al fin y al cabo de esto va TDD y refactorización: pequeños pasos pero muy seguros.

La segunda parte tiene una extensión más reducida. Una vez ha explicado en la parte primera qué es TDD con el ejemplo del dinero, en esta segunda parte se dedica a desarrollar las necesidades que un framework de testing debería cumplir, y que surgen de toda la implementación anterior. Así, define que debería existir un método de test, otro método para preparar todo el contexto (setup); otro para eliminar el contexto y dejar todo como estaba antes del test (teardown); recogida de resultados. Los test y código de este framework lo implementa en Python (no te asustes si no lo conoces, los ejemplos se hacen muy sencillos) y “casualmente” lo llama xUnit. Imagino que entenderás que los frameworks como JUnit y derivados provienen de estas ideas.

Finalmente la tercera parte está dedicada a los patrones del TDD. Me ha parecido la parte más interesante, sobre todo porque tenía experiencia previa con la técnica y con JUnit, asi que no supusieron grandes sorpresas. Esta parte es más bien un conjunto de consejos y buenas prácticas para llevar a cabo TDD. La mayoría de ellas ya están implícitas en el modo de hacer TDD hoy en día, pero conviene ver su origen y explicación. Se tratan temas como: concentrarse en hacer primero el test, que sean aislados, cómo organizarlos…; otros más curiosos son como afrontar la técnica desde el punto de vista del programador: cuándo descansar, dejar tests fallando al acabar la jornada para forzar a recordar, triangulación; u otros patrones más técnicos y referidos a xUnit como la captura de excepciones, dónde hacer las fixtures o patrones de diseño aplicados a test como el Null Object, Template Method, Factory, Composite. Finalmente, como dentro del ciclo de TDD hay una fase de refactorización, dedica un último capítulo a esta técnica iniciada por Martin Fowler (aquí tienes una entrada con la crítica del libro: Repasando los clásicos: Refactoring, de Martin Fowler).

En general es un libro interesante, pero, como sucede con ‘Refactoring’ de Martin Fowler, hay que encuadrarlo en su época: 2002. En su tiempo fue un libro revolucionario, tal como se ha demostrado la expansión y el reconocimiento general de las ventajas del uso de TDD a día de hoy. Pero en la actualidad, si lo enmarcamos en el estado del arte a finales de 2015, la mayoría de información ya se ha incorporado al quehacer diario de los programadores expertos, y se puede ver plasmada junto con evoluciones posteriores en una extensa bibliografía de TDD.


Cómpralo en Amazon

Comentando el libro ‘Growth Hacker Marketing’ de Ryan Holliday

$
0
0

El libro ‘Growth Hacker Marketing’ de Ryan Holliday explica la nueva forma de aproximarse al mundo de las ventas desde la creatividad y las antípodas de los tradicionales grandes presupuestos.

De un tiempo a esta parte se han puesto de moda nuevas técnicas de Marketing en las que más que nunca la creatividad juega un papel importante. Con el fin de la era de la supremacía de la televisión, radio, prensa y otros medios de comunicación de masas popularizados en el siglo XX, la confirmación de la práctica hegemonía de Internet ha motivado la llegada de nuevos modelos de ventas.

No en vano, marcas como Facebook, Twitter, Airbnb o Dropbox, (casos que se mencionan en el interior del libro) obtuvieron su éxito a través de ingenio, contactos y situarse en el momento adecuado en el lugar idóneo. Se trata de aprovechar así la senda que el emprendimiento Lean y el Long Tail establecieron hace unos años.

Sin duda Ryan Holliday concuerda con las primeras menciones que registra la blogosfera sobre esta vertiente enfocada a las ventas. No hay más que visitar la entrada de Sean Ellis en 2010 en Startup Marketing o la del gurú del Marketing Andrew Chen que titula ‘Growth Hacker is the new VP Marketing’.

Desde que comenzó a hablarse de esta tendencia ya se citan cuáles son las herramientas clave para el éxito en el Growth Hacking, aquellas que miden la repercusión de un determinado producto o servicio. Dichos puntos se sintetizan en la capacidad de probar, seguir y escalar la evolución del producto y conforme a ello ir modificándolo en base a la experiencia del usuario. De esa forma se conseguiría un producto diseñado por el usuario para sí mismo en el que además se siente parte del proceso de creación del mismo. Un doble motivo para que el mismo participe de la compra de dicho producto o servicio. Si crea un producto a su gusto y además se le da crédito por ello, ¿por qué no comprarlo?

Nunca he sido muy partidaria del SEM o el pago por promoción de un servicio, aunque sus positivos resultados sean incuestionables. Sin embargo siempre he tratado de crecer profesionalmente cualitativamente y no cuantitativamente. Eso lo aplico a las redes sociales, la generación de contenidos en una web o cualquier otra herramienta que vaya encaminada al Marketing digital. Reconozco que soy más de un número de seguidores, lectores o usuarios leales, familiarizados con la empresa que de un gran número de usuarios que participan de las campañas de la empresa pero que no se fidelizan. Por este motivo me he decidido a comentar el ‘Growth Hacking Marketing’ en este post ya que creo que es una herramienta decisiva para aquellos que afrontan con creatividad y planificación un proyecto en lugar de los costes que comportan las campañas de captación.



Cómpralo en Amazon

Introducción a Kibana

$
0
0

En este tutorial veremos una pequeña introducción a Kibana, una herramienta para visualizar y explorar los datos que se encuentran indexados en ElasticSearch.

Índice de contenidos


1. Introducción

¿Qué es kibana?

Kibana es una herramienta open-source perteneciente a Elastic, que nos permite visualizar y explorar datos que se encuentran indexados en ElasticSearch, es decir, un plugin de ElasticSearch. Kibana también es conocido por el stack ELK:

  • Elasticsearch
  • Logstash
  • Kibana

Antes de seguir con el tutorial os recomendo que veáis primero los tutoriales de Elasticsearch y Logstash realizados por Daniel Díaz Suárez y Rodrigo de Blas García.


2. Motivación

El stack ELK (Elasticsearch , Logstash y Kibana) es una herramienta ideal para la agregación de logs, visualización y análisis, aunque no está diseñado específicamente para este propósito.

Individualmente, la instalación de Elasticsearch, Logstash y Kibana y configurarlos para que se comuniquen entre sí, no es una tarea fácil.

Afortunadamente, con Docker y Docker Compose podemos crear nuestros contenedores con el stack ELK de forma fácil


3. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3 SDRAM)
  • Sistema Operativo: Mac OS X Yosomite 10.10.5
  • NVIDIA GeForce GT 750M 2048 MB
  • Docker 1.9.0
  • Docker Compose 1.5.0
  • Kibana 4.1.2
  • Logstash 2.0.0
  • Elasticsearch 1.7.3
  • curl 7.43.0

4. Creación del entorno

Como se ha comentado anteriomente vamos a crear nuestro entorno con Docker, para ello vamos a crear nuestro docker-compose.yml donde iremos creando y configurando las distintas herramientas que forman el stack ELK.

Todas la imagenes que vamos usar son las oficiales que nos proporciona Docker Hub

docker-compose.yml
elasticsearch:
  image: elasticsearch:1.7.3
  command: elasticsearch  -Des.network.host=0.0.0.0
  ports:
    - "9200:9200"

logstash:
  image: logstash:2.0.0
  volumes:
    - ./logstash/conf:/conf
  links:
    - elasticsearch:elasticsearch
  command: logstash -f /conf/logspout.conf
	expose:
		- "5000"
		- "5000/udp"

kibana:
  image: kibana:4.1.2
  links:
    - elasticsearch
	command: kibana -q
  ports:
    - "5601:5601"
logspout.conf
input {
  udp {
    port  => 5000
  }
}

output {
	elasticsearch {
    hosts => "elasticsearch:9200"
    ssl => false
  }
}

Vamos analizar este docker-compose.yml para entender la configuración de las distintas herramientas (ELK)

Elasticsearch

  • Arrancamos elasticsearch con la opción de aceptar peticiones desde cualquier IP.
  • Exponemos el puerto 9200 para recibir registros desde logstash y enviar registros a kibana.

Logstash

  • Creamos una unidad para poner nuestra configuración.
  • Creamos un link para poder ver el contenedor de elasticsearch.
  • Arracamos logstash con la configuración del fichero logspout.conf.
  • Exponemos el puerto 5000 tanto tcp y udp, para recibir los registro.

kibana

  • Creamos un link para poder ver el contenedor de elasticsearch.
  • Arracamos kibana en modo logging off.
  • Exponemos el puerto 5601 para visualizar la herramienta por vía web.

La configuración de kibana es muy simple, solo indicamos donde se encuentra elasticsearch, donde tiene más configuración logstash y donde tenemos que definir los inputs y filters. Para este ejemplo vamos a escuchar todas las peticiones por el puerto 5000 por UDP.

Para poder ver las funcionalidades de Kibana tenemos que alimentar nuestro elasticsearch. Para ello vamos a usar los logs que genera Docker cuando arrancamos un contenedor, es decir, coger todo lo que se escribe en

stdout
y
stderr
y se lo enviamos a logstash.

Click en la imagen para ampliar:

Ciclo de vida ELK

Para poder enviar todo lo que se escriba por

stdout
y
stderr
de los contenedores de Docker tenemos que añadir a nuestro docker-compose el contenedor de Logspout que pertenece gliderlabs, en concreto el módulo logspout-logstash que, de forma automática, se encargará de enviar todo lo que ocurre en los contenedores a logstash.
docker-compose.yml
logspout:
  build: logspout/
  links:
    - logstash
  volumes:
   - /var/run/docker.sock:/tmp/docker.sock
  environment:
    - ROUTE_URIS=logstash://logstash:5000

Con esta configuración ya tenemos el stack ELK configurado, lo siguiente es alimentar elasticsearch para poder usar Kibana y ver las principales funcionalidades que nos ofrece.

Arrancamos nuestro stack (ELK) con el siguiente comando.

docker-compose up

Click en la imagen para ampliar:

Docker Compose ELK

Abrimos otro terminal para crear contenedores para que escriba registros en elasticsearch. Con el siguiente comando ejecutamos un contenedor que pinta por consola hello world!

docker run  --rm  --name test --hostname=test debian:jessie  bash -c 'for i in {0..5}; do echo $i hello world!; done'

Volvemos a ejecutar el anterior comando cambiando el name, hostname y el mensaje.

docker run  --rm  --name prueba --hostname=prueba debian:jessie  bash -c 'for i in {0..5}; do echo $i Hola nundo!; done'

Con esto ya estemos un conjunto de datos para poder analizarlo y visualizarlo con Kibana

open http://`docker-machine ip dev`:5601

Donde “dev” es el nombre de la máquina virtual donde se está ejecutando Docker.


5. Ejemplo práctico

Antes de empezar a usar la herramienta vamos a describir las distintas secciones que forman la interfaz:

  • Discover: Pantalla que nos permite filtrar y buscar registros en un intervalo específico.
  • Visualize: Pantalla donde se pueden crear, modificar y ver sus propias visualizaciones personalizadas(Gráficos, tablas, …).
  • Dashboard: Pantalla donde se pueden crear, modificar y ver sus propios cuadros de mando personalizados .
  • Settings: Pantalla que permite cambiar la configuración por defecto o patrones de índice.

Lo primero que tenemos que hacer cuando entramos en Kibana es definir el índice que vamos a usar para visualizar los datos. En este caso solo tenemos el índice logstash y solo tenemos un campo que es timestamp. Click en la imagen para ampliar

Definición de índice

Lo siguiente es visitar la pantalla de Discover para ver los registro que tenemos guardados en elasticsearch. Click en la imagen para ampliar:

Discover

La pantalla Discover esta compuesta por:

  • Search Bar: Utilice esta opción para buscar campos específicos y/o los mensajes completos.
  • Time Filter: Arriba a la derecha (icono del reloj). Utilice esta opción para filtrar registros en base a varios intervalos de tiempo relativos y absolutos.
  • Field Selector: Izquierda, bajo la barra de búsqueda. Seleccione los campos a modificar que se mostrarán en la vista Anotaciones.
  • Date Histogram: Gráfico de barra debajo de la barra de búsqueda. Por defecto, muestra el recuento de todos los registros, en función del tiempo (eje x), acompañado por el filtro de búsqueda y el tiempo. Puede hacer clic en las barras, o hacer clic y arrastrar para reducir el filtro de tiempo.
  • Log View: Parte inferior derecha. Utilice esta opción para ver los mensajes de registro individuales, y mostrar los datos de registro filtrados por los campos. Si no se seleccionan los campos, se muestran los mensajes de registro enteras.

Vamos a buscar los registros que en el campo message tengan parte el texto ‘hola mundo’ con la siguiente expresión:

message: "Hola"

Click en la imagen para ampliar:

Búsqueda

El número de registros que han encontrado son 6, que concide con el script que hemos ejecutado anteriormente.

En las otras 2 pantallas (Visualize y Dashboard) os animo a que creéis vustras propias gráficas y cuadros de mandos.


6. Conclusiones

Como hemos visto utilizando el stack ELK, tenemos una manera de recoger (Logstash), guardar y de buscar (Elasticsearch), y visualizar y analizar (Kibana).

Un caso muy útil sería en la explotación de los logs de nuestras aplicaciones.

Espero que el tutorial os haya animado a usar (o al menos probar) este stack de tecnologías.

Puedes descargar el ejemplo de este tutorial desde aquí


7. Referencias

Comentando el libro: DevOps y el camino de baldosas amarillas

$
0
0

En esta ocasión comentamos el libro: DevOps y el camino de baldosas amarillas de José Juan Mora Pérez.

Lo bueno de ser el CEO (hiperactivo) de una empresa es que puedes elegir (aunque no siempre) el tema y nivel de profundidad al que te quieres dedicar en distintas épocas de tu vida.

En esta época me he enredado personalmente a acompañar algunas empresas en la adopción de metodologías ágiles. Aunque siendo más preciso, y sin posiblemente ellos saberlo, es una transformación de la organización hacia una estrategia más digital.

Encima de una mesa en un cliente vi el libro DevOps y el camino de baldosas amarillas y a poco hábil que seas, has de leer los libros que lean tus clientes. 😉

Me ha durado poco, un par de días, por lo que puedo decir que se lee con facilidad. Para mi gusto el autor se ha mojado poco a modo “esto es un marco y usa el sentido común”. 😉

Dice cosas interesantes:

Es mejor comprender el comportamiento de un sistema y entender cómo puede fallar, que gastar tiempo y dinero en intentar solucionar los problemas duplicando y repitiendo. Los fallos son intrínsecos de los sistemas complejos: la única forma de mitigarlos es con el conocimiento.

Es habitual que el proceso de evangelización DevOps dentro de la compañía se inicie desde el área de desarrollo, ya que culturalmente suelen ser menos reacios a adoptar nuevas formas de trabajo.

Las organizaciones con sistemas estancados adquieren la cultura de adoptar sus procesos al sistema y no al contrario. Este enfoque condiciona la estrategia de negocio en función del rendimiento del sistema.

Mucha gente confunde DevOps con Agile. La principal diferencia es que DevOps no es una metodología, mientras que la cultura ágil se basa en distintos tipos de metodología (dale tiempo que ya verás la cantidad de certificaciones y frameworks de escalado aparecen. :-)

IT ha evolucionado de una manera exponencial, lo que ha supuesto un desfase entre la creación de normas y la aparición de nuevas tecnologías. Este desfase genera problemas a la hora de aplicar a negocio aquellas tecnologías que acaban de aparecer en el mercado.

Las tres vías DevOps son:

  • Entender el sistema como un todo (evitando fronteras).
  • Incrementar el feedback.
  • Experimentación / aprendizaje continuo.

Es un error pensar que DevOps tiene como objetivo solucionar los problemas de tecnología. Tiene que resolver los de negocio. Esto se hace patente viendo el perfil buscado de experto en DevOps como un catálogo de conocimientos en productos y técnicas y no en habilidades.

DevOps es una caja de herramientas varias y se pueden meter en esa caja (aquí es donde digo que se moja poco 😉 ): automatizar, gestionar las configuraciones, despliegue automático, gestión de logs, gestión de rendimiento, gestión de la capacidad, escuchar, hablar y compartir.

El principal problema que podemos encontrar en el proceso de automatización es que focalicemos todo el esfuerzo en el proceso en sí y no tengamos en cuenta la relación con el resto de elementos y procesos del sistema.

Se suele malinterpretar el concepto de rendimiento: proporción entre el producto o el resultado obtenido y los medios utilizados. Se suele confundir velocidad con rendimiento.

Tiene que haber un equilibrio entre calidad y tiempo de entrega.

Comenta la agresividad que se respira en muchos departamentos. Mi pregunta es: ¿y quién consiente esto?

Uno de los puntos más destacados del libro es acerca de la comunicación.

Propone no cambiar la estructura del departamento de IT para la implantación de DevOps (esto me ha sonado conservador 😉 ). También recomienda no montar un equipo de DevOps (aunque yo si recomendaría una task-force para que alguien tenga la responsabilidad de empujar, pese al día a día).

Termina hablando de cómo Dorothy llevaba unos zapatos mágicos y golpeándolos 3 veces podrá haber vuelto a Kansas. No vale absolutamente de nada tener la herramienta más potente del mercado, cuando no tenemos conocimiento suficiente para usarla.

Tenemos que agradecer al autor el esfuerzo de escribir el libro y seguro que muchos responsables de tecnología lo encontrarán como lectura inspiradora.



Cómpralo en Amazon

He de decir que se podría ampliar la visión del libro:

  • En amplitud: porque para mi operación no es sólo operación en tecnología. 😉
  • En profundidad: porque no describe un modelo (aun advirtiendo que es sólo eso) de cómo puede funcionar en una organización con DevOps (incluyendo herramientas).

Por eso trato de complementar (en pocas palabras, más o menos): :-)

Una empresa del pasado puede funcionar así:

  • Las áreas de negocio (marketing, comercial, estrategia digital) se pueden creer muy listas y definir un nuevo producto pensando en qué es lo que sus clientes pueden demandar.

  • Hacen una definición vaga de lo que desean y se lo pasan a tecnología para que haga una evaluación del coste/impacto. Con un poco de suerte se definen una experiencia de usuario. Con poco tiempo, poca información y, muchas veces, con poco conocimiento técnico (por la externalización masiva) tienen que dar un precio y fecha, cosa que incluso a veces ya viene condicionado. El fallo no se contempla como una opción.

  • Los usuarios de negocio están alejados de los desarrolladores (no tienen tiempo o involucración suficiente) y pasan meses hasta que se entrega una primera versión. Normalmente es poco satisfactoria y, por la presión de la entrega, con muchos errores, donde la calidad se ha dejado de lado. Pasan semanas hasta que hay una entrega estable y medianamente satisfactoria, con un sobrecoste por el esfuerzo y el tiempo perdido en salir de mercado.

  • De arquitectura e infraestructura de sistemas “se ha pasado” casi hasta el momento final. Por lo que están cabreados y con falta de visión del impacto y tiempo para adquirir el conocimiento en las nuevas tecnologías.

  • Los equipos de desarrollo no tienen muy claro cuál es el objetivo y valor de negocio de muchas cosas que hacen. Tampoco tienen en cuenta que lo que hagan tiene que dejar herramientas para que el equipo de sistemas o las áreas de negocio sepan qué ha pasado ante un fallo.

  • Entregada la solución, hay una labor de formación a soporte, operaciones de negocio (como middle office) y comercial (las verdaderas operaciones a nivel de negocio).

Por tanto, se tardan meses desde que se conceptualiza algo y se obtiene feedback de cliente.

Y, ¿cómo puede funcionar una empresa del futuro?

Vamos a intentar pasar al extremo contrario e imaginemos qué tendría que pasar para que todos los días se pusiera en marcha nueva funcionalidad a los departamentos de negocio. ¿Cómo se enterarían? Imaginad un Web. ¿Cómo capitalizarían el conocimiento de las incidencias tan rápidamente, etc.?

  • Primero, negocio debería invitar a gente ajena a la organización a definir producto. Hay expertos externos, profesores de escuelas de negocio, etc. que ven muchas cosas distintas. Además, con conocimientos sistemáticos de definición de productos (inception).

  • Esta visión se debería contrastar con clientes finales para no pensar por ellos.

  • Se debería comunicar la visión interna y crear un equipo común entre negocio, tecnología, operaciones, etc. Desde el principio se debería permitir el fallo: falla rápido, falla barato.

  • A priori no se sabrá ni el coste final ni el alcance concreto, aunque se podrá tener un marco.

  • Hay que trabajar con gente competente, motivada y cualificada donde el día a día demuestra su implicación y calidad. No hay que medir después porque luce cada día.

  • Aquí ya compras empieza a sufrir porque, ¿ahora ¿a quién aprieta en precio?

  • Se debería definir una lista sobre las prioridades de los elementos por valor a negocio.

  • Desarrollo y negocio debe tener una comunicación continua para definir, probar, validar y pivotar.

  • Se debería documentar el comportamiento como parte del entregable (guía de usuario), casos de prueba automáticos de regresión (para garantizar los escenarios) y mecanismos de comunicación (FAQ y tablón o similar) para que el área de operación sepa cuál es la nueva funcionalidad que se va a poner en marcha, qué aspecto tiene, cómo venderla y cómo actuar si un cliente llama por teléfono. Quedaría feo que los usuarios supieran más que los operadores.

  • Desarrollo y otras áreas de sistemas (infraestructuras / comunicaciones / operación) tienen que trabajar todavía más cerca para definir los criterios de calidad del software, mecanismo de pruebas (TDD / funcionales), homogeneidad de logs, mecanismos de monitorización y alarmas, entornos y pases entre ellos. Todavía más compleja es la gestión de la configuración e integración del código en elementos con múltiples dependencias (como microservicios).

Por tanto, la comunicación es la pieza más importante, pero tenemos que mojarnos en soluciones artísticas (comunicación) y tecnológicas de referencia y de facto al tipo (sólo pongo algunos enlaces a cosas que tenemos):

Organización del Desarrollo:

Integración continua:

Construcción de entornos / virtualización:

Monitorizar aplicaciones en tiempo real:

Calidad de software

Creedme que lo que veo más fácil es cambiar a tecnología (aunque también hay mucho Cio-Saurio :-) ) porque el cambio más grande está en aceptar este nuevo “ritmo” y compromiso por negocio.


Unir multiples vídeos a la vez con FFmpeg y bash

$
0
0

Este pequeño tutorial nos servirá para mostrar las bondades de la herramienta multiplataforma FFmpeg para la edición de vídeo así como el uso de la programación bash para hacer pequeños scripts.

Índice de contenidos


1. Introducción. ¿Qué te hace querer unir varios videos a la vez?

Me surgió esta necesidad por querer añadir un opening o cabecera a varios vídeos que ya se habían realizado. Por tanto era necesario ir vídeo por vídeo añadiendo la cabecera. Quería algo que lo añadiese a todos los vídeos a la vez y así no perder el tiempo.

Descubrí FFmpeg, una herramienta que se usa por terminal y permite hacer todo tipo de operaciones con vídeos: recortes, conversiones, filtros, transcodificaciones, etc. El hecho de que se use por terminal facilita que se pueda hacer un sencillo script bash para que se aplique a todos los vídeos. Más adelante veremos cómo.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: 2 Ghz Intel Core I7, 8GB DDR3
  • Sistema Operativo: OS X El Capitan
  • Entorno de desarrollo: Atom
  • Entorno de desarrollo: Eclipse Mars

3. Instalación

La herramienta FFmpeg es multiplataforma, es posible instalarla tanto en mac, como en linux o windows. Descargamos la versión para nuestra plataforma y a trabajar!


4. Uniendo vídeos

A la hora de unir varios vídeos, es importante hacer una distinción: si tienen o no el mismo códec. Empezaremos con la forma general, por si tienen códecs distintos.

En primer lugar vamos a ver la orden aplicada a dos vídeos (aunque uno lo vamos a concatenar al principio y al final, o sea que tendremos 3 entradas), y luego vemos cómo podemos automatizarla para varios vídeos.

ffmpeg -i opening.mp4 -i video2.webm opening.mp4 \
-filter_complex "[0:v:0] [0:a:0] [1:v:0] [1:a:0] [2:v:0] [2:a:0] concat=n=3:v=1:a=1 [v] [a]" \
-map "[v]" -map "[a]"  salida.mp4

Empezamos seleccionando los vídeos fuente, los que queremos unir, con el argumento -i. Después observamos el argumento -filter_complex al que enviamos lo que se va a enviar al filtro de concatenado (señalar que lo que se envía al filtro ha de estar entre comillas).

La parte [0:v:0] [0:a:0] [1:v:0] [1:a:0] [2:v:0] [2:a:0] indica que la pista de video 0 (v:0) y de audio (a:0) del vídeo 0 (el primero que hemos pasado), siguiendo con el vídeo 1, y luego el 2, es decir, se van a concatenar audio y vídeo en ese orden, los tres videos en orden.

La pista de video 0 significa que es la primera pista de video que tenga el video (puede tener varias), lo mismo pasa con el audio 0 (hay videos con varias pistas, normalmente para cambiar de idioma).

Siguiendo en esa línea, concat=n=3:v=1:a=1 [v] [a], el n=3 significa que le estamos pasando 3 videos, y el v=1 y a=1 indican que al final vamos a tener una sola pista de audio y de video. La [v] y la [a] finales son nombres que se le dan a esas pistas de audio y video que generamos para referirnos a ellas posteriormente.

Finalmente -map “[v]” -map “[a]” le dice a ffmpeg que use las pistas de audio y video que acabamos de unir en vez de las originales para generar la salida.mp4. La orden funcionaría así perfectamente, sin embargo, usuarios avanzados pueden necesitar especificar unas opciones específicas de encoding para el vídeo de salida.

Llegados a este punto hemos unido varios vídeos en uno. ¿Cómo podemos automatizar esto para que se aplique a muchos vídeos? La respuesta es programación bash. Haremos un pequeño script que se aplique a todos los vídeos de nuestro directorio. Vamos a ver cómo se hace.


4.1 Generando nuestro script

Necesitamos aplicar el script a los videos de nuestro directorio. Supongamos que están en mp4. Usaremos un bucle for que recorra todos los archivos usando una expresión regular que corresponda al nombre de los videos, como puede ser *.mp4 (todos los archivos .mp4).

#/bin/bash

OPENING=opening.mp4

for video in [!o]*.mp4
do

ffmpeg -i $OPENING -i $video $OPENING \
-filter_complex "[0:v:0] [0:a:0] [1:v:0] [1:a:0] [2:v:0] [2:a:0] concat=n=3:v=1:a=1 [v] [a]" \
-map "[v]" -map "[a]" "$video"_conCabeceras.mp4

done;

Como vemos arriba, el bucle recorre la lista de archivos que mp4 que no empiezan por “o” (ya que queremos usar el archivo opening.mp4 como cabecera y final de los vídeos. Esto es totalmente personalizable, por supuesto.

En el cuerpo del bucle usamos la orden que hemos detallado antes, adaptando los nombres.


4.2. Mismos códecs

Recordamos que si los videos que queremos unir tienen el mismo códec, la orden ffmpeg era algo más simple:

ffmpeg -f concat -i listaDeVideos.txt -c copy salida

Como vemos, la orden es mucho más sencilla, lo único que hay que pasar una lista de los vídeos que queremos unir, dando sus rutas absolutas.


5. Otras posibilidades

Lo que hemos visto ahora es sólo una de las muchas posibilidades que ofrece la herramienta FFmpeg. Despediremos el tutorial con un ejemplo de cómo eliminar los primeros 15 segundos de los vídeos mp4 del directorio.

#/bin/bash

for file in *.mp4
do

ffmpeg -i $file --ss 00:00:15 “$file”_recortado.mp4

done

6. Conclusiones

Como hemos visto es bastante sencillo. No salen apenas líneas para este tipo de operaciones. Únicamente hay que investigar qué se puede hacer con ffmpeg y una programación bash básica.


7. Referencias

Ejemplo en NodeJS: normalizando audio de vídeos con FFMpeg (y TDD con Mocha+Chai)

$
0
0

En este tutorial vamos a ver un ejemplo de desarrollo con TDD en NodeJS, empleando Mocha y Chai. Lo usaremos para normalizar (ajustar volumen) el audio de unos vídeos con la librería FFMpeg

0. Índice de Contenidos

1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Acer Aspire One 753 (2 Ghz Intel Celeron, 4GB DDR2)
  • Ubuntu 15.10
  • NPM 1.4.21
  • NodeJS v0.10.25
  • FFMpeg 2.7.3

Puedes descargar el código de este tutorial en https://github.com/4lberto/VideoAudioNormalizer

2. Problema a Resolver

En Autentia estamos grabando cursos on-line de nuestras especialidades. Para ello, usamos normalmente la modalidad de screencast que grabamos en una cámara especial de grabación para que el audio tenga cierta calidad: sin reverberaciones ni interferencias externas. Además empleamos un buen micrófono de grabación.

El audio de los vídeos es fantástico (salvo por el narrador jeje), pero quizá el nivel no sea el adecuado: posiblemente es demasiado bajo, lo que puede causar molestias a los alumnos, que deberán ajustar el audio de su sistema en exclusiva para estos vídeos.

El nivel de audio se mide en decibelios (dB). Al ser un fichero del que se lee, el nivel final dependerá de cómo lo reproduzca el usuario. Por eso, se establecen valores negativos, que luego serán amplificados.

El objetivo será aumentar el volumen de cada vídeo hasta cierto nivel establecido. Para complicarlo un poco más y ampliar nuestro campo de trabajo, vamos a hacerlo en NodeJS.

Para ello se seguirá la siguiente lógica:

  1. Obtener el volumen máximo del vídeo, por ejemplo -5.7dB.
  2. Aumentar el volumen máximo hasta llegar a 0.0dB. Por tanto, subir ese vídeo en 5.7dB positivos.

Si algún ingeniero de sonido lee esto seguro que me mata, pero para las grabaciones que hemos hecho y lo que buscamos no es suficiente. También podría tomar el volumen medio y subirlo hasta un nivel determinado, pongamos -20.0dB, pero no quería tampoco hacer “clipping“.

No obstante, el programa está disponible en GitHub, así que puedes hacer un fork y tunearlo a tu gusto.

3. FFMpeg

Para tratar el vídeo nos hace falta una biblioteca de tratamiento de vídeo y audio, como es FFMpeg. Puede ser descargada en: https://www.ffmpeg.org/.

Esta herramienta nos va a permitir analizar los vídeos y transformarlos con las instrucciones que indiquemos para alterar el volumen del audio y que lo amplifique. Vamos a ver cómo usarlo.

3.1. Instalando FFMpeg

FFMpeg es muy fácil de instalar. O bien lo podemos descargar de su página oficial, eligiendo el sistema operativo de nuestro ordenador: Mac, Windows o varias distribuciones de Linux, o podemos emplear el gestor de paquetes de nuestro sistema operativo.

Si te lo vas a descargar es muy fácil: se baja un fichero comprimir en cuyo interior está el ejecutable que se utiliza, ¡nada más! Una vez se tiene el ejecutable lo podemos referenciar en el path del sistema para que al escribir “ffmpeg” en cualquier lado, el sistema responda.

Como estoy usando Ubuntu 15.10 voy a usar el gestor synaptic. No puede ser más fácil.

sudo apt-get install ffmpeg -y

Esperamos un poco y ya lo tenemos instalado. Sin ningún problema :).

El gestor de paquetes lo mete en el path, así que probamos directamente a escribir en un terminal “ffmpeg” y vemos que funciona sin problemas:

ffmpeg

El resultado debería ser algo de este estilo:

ffmpeg version 2.7.3-0ubuntu0.15.10.1 Copyright (c) 2000-2015 the FFmpeg developers
  built with gcc 5.2.1 (Ubuntu 5.2.1-22ubuntu2) 20151010
  configuration: --prefix=/usr --extra-version=0ubuntu0.15.10.1 --build-suffix=-ffmpeg --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --enable-shared --disable-stripping --enable-avresample --enable-avisynth --enable-frei0r --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libopenjpeg --enable-openal --enable-libopus --enable-libpulse --enable-librtmp --enable-libschroedinger --enable-libshine --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libxvid --enable-libzvbi --enable-opengl --enable-x11grab --enable-libdc1394 --enable-libiec61883 --enable-libzmq --enable-libssh --enable-libsoxr --enable-libx264 --enable-libopencv --enable-libx265
  libavutil      54. 27.100 / 54. 27.100
  libavcodec     56. 41.100 / 56. 41.100
  libavformat    56. 36.100 / 56. 36.100
  libavdevice    56.  4.100 / 56.  4.100
  libavfilter     5. 16.101 /  5. 16.101
  libavresample   2.  1.  0 /  2.  1.  0
  libswscale      3.  1.101 /  3.  1.101
  libswresample   1.  2.100 /  1.  2.100
  libpostproc    53.  3.100 / 53.  3.100
Hyper fast Audio and Video encoder
usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...

3.2. Aplicando FFMpeg a los vídeos

FFMpeg tiene multitud de opciones entre las que tuve que navegar para encontrar lo que quería. Básicamente quería dos cosas:

  • Obtener el valor del nivel de audio máximo para el vídeo.
  • Alterar el valor del vídeo.

Para conocer el valor del nivel máximo de audio de un vídeo, ejecutaremos en nuestra línea de comandos la siguiente instrucción:

ffmpeg -i INPUT.mp4  -af "volumedetect" -f null /dev/null

Donde INPUT.mp4 es el vídeo del cual queremos conocer el volumen máximo. La salida es bastante extensa, y puede llevar un rato en procesarse, dependiendo del tamaño y calidad del vídeo. Lo que nos interesa es la parte final:

video:12kB audio:2988kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
[Parsed_volumedetect_0 @ 0x1948820] n_samples: 1529856
[Parsed_volumedetect_0 @ 0x1948820] mean_volume: -41.0 dB
[Parsed_volumedetect_0 @ 0x1948820] max_volume: -10.7 dB
[Parsed_volumedetect_0 @ 0x1948820] histogram_10db: 34
[Parsed_volumedetect_0 @ 0x1948820] histogram_11db: 81
[Parsed_volumedetect_0 @ 0x1948820] histogram_12db: 72
[Parsed_volumedetect_0 @ 0x1948820] histogram_13db: 41
[Parsed_volumedetect_0 @ 0x1948820] histogram_14db: 35
[Parsed_volumedetect_0 @ 0x1948820] histogram_15db: 34
[Parsed_volumedetect_0 @ 0x1948820] histogram_16db: 55
[Parsed_volumedetect_0 @ 0x1948820] histogram_17db: 72
[Parsed_volumedetect_0 @ 0x1948820] histogram_18db: 112
[Parsed_volumedetect_0 @ 0x1948820] histogram_19db: 267
[Parsed_volumedetect_0 @ 0x1948820] histogram_20db: 347
[Parsed_volumedetect_0 @ 0x1948820] histogram_21db: 485

Y específicamente nos interesan estas líneas.

[Parsed_volumedetect_0 @ 0x1948820] n_samples: 1529856
[Parsed_volumedetect_0 @ 0x1948820] mean_volume: -41.0 dB
[Parsed_volumedetect_0 @ 0x1948820] max_volume: -10.7 dB

Podemos ver el volumen medio (mean_volume) y el máximo (max_volume). En el vídeo que hemos utilizado es de -10.7dB, así que subiremos el volumen general 10.7dB para que el volumen máximo final sea de 0.0dB. O si lo prefieres puedes acercarte algo menos a 0.0dB, como por ejemplo dejándolo en -5.0dB.

También si quieres puede usar el volumen medio… aunque correrías el riesgo de cortar alguna señal por volumen máximo…

Para la segunda parte, que sería ordenar subir 10.7dB positivos al volumen del audio del vídeo, tenemos este comando:

ffmpeg -i INPUT.mp4 -af "volume=10.7dB" -strict -2 INPUT_NORMALIZED.mp4

Donde INPUT.mp4 es el fichero al que le vamos a aplicar la transformación, e INPUT_NORMALIZED.mp4 es el fichero de salida.

Esto es todo lo que debemos conocer de FFMpeg para hacer nuestro programa para normalizar audio de nuestros vídeos.

Como podrás suponer, la dificultar radica en ejecutar FFMpeg, recoger el resultado de salida y aplicar el segundo comando correctamente. Vamos a ver cómo hacemos todo esto en NodeJs.

4. Proyecto en NodeJS

Vamos con lo más interesante, porque lo anterior es sólo un pretexto para tener algo que hacer con nodeJS :).

Deberías tener instalado NodeJS y NPM en tu sistema. Ya hay otros tutoriales en los que explicamos cómo hacerlo. Por ejemplo este en el que hablo de Polymer y que necesitamos nodeJS y NPM para montar el entorno: Introducción a Polymer. Por tanto no voy a repetir aquí la instalación. Asumimos que los tienes instalados, listos para usar, al menos en las versiones que indico en el apartado Entorno.

4.1. Creación de la estructura

Comencemos creando el proyecto en un directorio determinado que hemos reservado en nuestro ordenador.

mkdir ffmpeg
cd ffmpeg

Ahora iniciamos el package.json con npm. Aquí residirá la metainformación del proyecto de NodeJS y las dependencias, así que tómatelo en serio :)

npm init

Completa la información que pide: nombres, versiones, GitHub…

Ahora debemos instalar las dependencias, que serán:

  • Chai 3.4.1
  • Mocha 2.3.4
  • fluent-ffmpeg 2.0.1

Las dos primeras son en tiempo de desarrollo y la tercera forma parte del programa. Para instalarlas haremos:

npm install -D mocha
npm install -D chai
npm install --save fluent-ffmpeg

El fichero package.json debería quedar algo así:

{
  "name": "ffmpeg",
  "version": "0.0.1",
  "description": "NormalizeAudios",
  "main": "audio.js",
  "dependencies": {
    "fluent-ffmpeg": "^2.0.1"
  },
  "devDependencies": {
    "chai": "^3.4.1",
    "mocha": "^2.3.4"
  },
  "scripts": {
    "test": "mocha"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/4lberto/VideoAudioNormalizer.git"
  },
  "keywords": [
    "audio",
    "video",
    "normalize",
    "ffmpeg"
  ],
  "author": "4lberto",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/4lberto/VideoAudioNormalizer/issues"
  }
}

El siguiente paso consiste en crear un directorio “libs” donde residirán los módulos auxiliares de la aplicación, y otro “test” donde estarán los ficheros de test que se ejecutarán con Mocha. Muy fácil:

mkdir test
mkdir libs

Finalmente vamos a instalar Mocha para que pueda ejecutar los test desde línea de comandos. Efectivamente, si escribimos “mocha” en nuestra shell, debería funcionar, sino lo hace, lo tenemos que instalar con NPM, haciendo uso de la opción “-g” para indicar que es una instalación global a todo el sistema.

npm install -g mocha

Ahora si escribimos mocha, y tenemos el directorio test creado pero vacío, debería responder con esta salida:

0 passing (1ms)

4.2. Comienzo del desarrollo

Antes de comenzar con el desarrollo del programa, vamos a ver en primer lugar una característica muy importante de NodeJS que va a influir determinantemente en cómo se desarrolla: los módulos.

4.2.1. El sistema de Módulos de NodeJS

NodeJS está basado en módulos. Los módulos, sin entrar mucho en detalle, son conjuntos de funciones agrupadas en un único archivo. De este modo, dentro de nuestro fichero de NodeJS podemos cargar otros módulos sobre los que nos podemos apoyar. Y por supuesto, también podemos crear nuestros propios módulos.

Podrás imaginar entonces la importancia de NPM y la gestión de dependencias en NodeJS: puedes instalar cualquier módulo y hacer uso de él muy fácilmente. ¿Cómo lo hago? Es muy fácil: utilizando la función require(“nombreMódulo”). Así es cómo cargamos el módulo nativo FS (FileSystem) de NodeJS:

var fs = require("fs");
fs.exists("/etc/hosts", function(res){console.log(res);})

Con estas sencillas instrucciones (vete acostumbrado al mundo de los callback), hemos logrado consultar si el archivo “/etc/hosts” existe en nuestro sistema.

Como puedes ver, se asigna el módulo a la variable “fs” y luego se hace uso de ella. El parámetro de require(“fs”) indica el módulo que se quiere cargar. Así, se puede indicar por el nombre directamente o por el fichero. Algunos módulos forman parte del core de NodeJS, otros están instalados globalmente, otros son del propio proyecto, y finalmente otros son simplemente ficheros que cargar. Por tanto, la notación soporta parámetros del estilo:

var modulo = require("./directorio/fichero.js");

La función require realiza una búsqueda según unos ciertos criterios.

¿Cómo puedo crear mis propios módulos? Aquí es donde vamos… Simplemente creando un fichero de JavaScript que contiene las funciones. Pero hay una cosa más… tienes que exportar esas funciones para que sean accesibles desde el exterior. No todas las funciones y contenidos son expuestos cuando son importados.

En todo módulo hay un objeto implícito llamado “module”, que referencia al objeto que representa el módulo actual. No es una referencia global al programa, sino que es algo del propio módulo que estamos creando. Dentro de las propiedades del objeto “module” hay varias, como referencia a las dependencias, si está cargado o no, carga de otros módulos, pero sobre todo nos interesa “exports”. Con module.exports indicamos qué funciones pueden ser empleadas por los módulos que hacen la carga de éste. Como es muy usado, tiene un alias que es simplemente “exports”. Lo vemos mejor en código.

Vamos a crear un módulo de operaciones aritméticas. La típica calculadora con suma y resta:

module.exports.suma = function(a,b){return a+b};
module.exports.resta = function(a,b){return a-b};

Y ya está… como module.exports tiene una variable exports (es decir exports = module.exports), lo podríamos haber reducido a:

exports.suma = function(a,b){return a+b};
exports.resta = function(a,b){return a-b};

Imagina que guardamos este código en el fichero “calculadora.js”. ¿Cómo lo usamos en otro módulo?

var calculadora = require("./calculadora.js");
console.log("La suma de 2 y 3 es:" + calculadora.suma(2,3));

Básicamente esto es lo que debemos saber acerca de los módulos. Hay otros detalles, pero son más avanzados. Sabiendo esto, tenemos suficiente para adentrarnos en la aplicación.

4.2.2. Organización del código

Con este mecanismo de módulos, se me ha ocurrido crear la siguiente estructura:

  • Fichero principal ejecutable: audioNormalized.js
  • Directorio para archivos auxiliares:libs
    • Fichero para funciones principales: audioLib.js
    • Fichero para funciones auxiliares: utils.js
  • Directorio para test(TDD):tests
    • Fichero de test para funciones principales: audioLibTest.js
    • Fichero de test para funciones auxiliares: utilsTest.js

Quizá no sea la mejor opción de organizar todo, pero sí que me ha permitido aplicar un poco de TDD para la construcción. La aplicación es relativamente sencilla, así que no hay problemas. En código:

touch audioNormalizad.js
mkdir libs
touch ./libs/audioLib.js
touch ./libs/utils.js
mkdir test
touch ./test/audioTest.js
touch ./test/utilsTest.js

4.3. Aplicando TDD

Es hora de empezar a programar, y para ello vamos a hacer TDD: se trata de hacer primero los test y luego hacer el código que cumple los test. En realidad es algo más elaborado. Puedes echar un ojo a una crítica que hice del libro en el que se basa, y donde explico en qué consiste en esta entrada.

4.3.1. Mocha y Chai

Pero para hacer TDD necesitamos un sistema para hacer test. La parte buena de NodeJS es que hereda los sistemas de test existentes para JavaScript, que además, últimamente está teniendo un empuje enorme.

Para este tutorial me he decantado por Mocha y Chai. Mocha es un framework de testing que da soporte para ejecutar test en Javascript. Chai por su parte es una librería de “asserts”, es decir, facilita expresar los predicados que tienen que cumplir los test. Algo así como que hace sencillo escribir en código “el resultado debe estar entre 5 y 50”.

En secciones anteriores ya hemos explicado cómo instalar Mocha y Chai (via npm). Incluso si hemos instalado globalmente Mocha (npm install -g mocha) disponemos del comando “mocha” en nuestro ordenador.

4.3.2. Comenzando TDD

Comenzamos ejecutando el comando mocha en nuestro terminal en el directorio raíz. Como hemos creado el subdirectorio “test” con dos ficheros vacíos, Mocha encontrará 0 test que ejecutar:

0 passing (1ms)

Esto es una buena noticia porque ya tenemos un punto de partida. Vamos a hacer TDD: primero hacemos un tests de una necesidad. Se me ocurre que en primer lugar necesitamos obtener una lista de ficheros de una extensión determinada (vídeos) de un directorio sobre el que actuar. Vamos a expresarlo en un test.

var utils = require('../libs/utils');
var chai = require('chai');

var DIR = './test/';
var VIDEO_EXTENSION = 'mp4';

describe('getFilesFromDirWithExtension', function() {
	it("Should get more than 0 files from the dir", function(){
		chai.assert.isAbove(utils.getFilesFromDirWithExtension(DIR, VIDEO_EXTENSION).length,0);
	});

Bueno, en realidad se trata de un test de integración porque tenemos que preparar el entorno. Hay algunas cosas interesantes que nos van a forzar a programar, más allá del test:

  • Que el fichero donde va a estar la función que vamos a testear es utils.js.
  • Que debemos cargar algún fichero de extensión mp4 en el directorio “test”.
  • Que la función se va a llamar “getFilesFromDirWithExtension” y que tiene dos parámetros: el directorio y la extensión.

Ya tenemos el test. Pues lo ejecutamos para ver nuestra primera bandera roja:

getFilesFromDirWithExtension
    1) Should get more than 0 files from the dir


  0 passing (4ms)
  1 failing

  1) getFilesFromDirWithExtension Should get more than 0 files from the dir:
     TypeError: Object # has no method 'getFilesFromDirWithExtension'
      at Context. (test/utilsTest.js:9:29)

He ido demasiado rápido estableciendo el test completo, debería haber ido más despacio, pero nos podríamos eternizar… Vamos a ver qué código hace que se cumpla el test:

var fs = require("fs");

var getFilesFromDirWithExtension = function(directory, extension){
	return fs.readdirSync(directory).filter(function(element){
		return element.substr(element.length-3,element.length)==extension;
	});
};

exports.getFilesFromDirWithExtension = getFilesFromDirWithExtension;

Si te fijas, he creado la variable para asignarla a la función y en un segundo paso, en la última línea se ha hecho la asignación a exports, que recuerda, es el equivalente de module.exports, y se usa para que los otros módulos que hagan un require, puedan utilizar la función.

Otra cosa que te llamará la atención es que se hace uso del módulo “fs” de NodeJS, que se emplea para el manejo de archivos. Así podemos obtener con readdirSync todos los archivos de un directorio indicado. Posteriormente lo filtramos con filter para quedarnos con los de extensión deseada.

Ahora ponemos un fichero con extensión .mp4 en el directorio test y ejecutamos Mocha. Esta vez tenemos buenas noticias:

getFilesFromDirWithExtension
    ✓ Should get more than 0 files from the dir

  1 passing (3ms)

¡Ya tenemos nuestro primer test pasado! Ahora es cuestión de ir añadiendo más y mas test hasta completar la funcionalidad.

No voy a poner el código de todos los test de la parte de utilidades que realicé. Voy a enumerar algunos de ellos simplemente:

  • Que devuelva solamente los ficheros de la extensión indicada.
  • Que tome el valor de la salida de FFmpeg correspondiente al volumen máximo con 1 entero y 1 decimal.
  • Que tome el valor de la salida de FFmpeg correspondiente al volumen máximo con 2 entero y 1 decimal.
  • Que tome el valor de la salida de FFmpeg correspondiente al volumen máximo con 4 entero y 1 decimal.
  • Que dado un valor en dB negativos le invierta el signo.
  • Que dado un valor en dB positivos le invierta el signo.
  • Que dado un nombre de fichero le añada “_NORMALIZED” al final del nombre.

Una vez que ya tenemos las funciones de util.js, que son las básicas, podemos ponernos con las principales del fichero audioLib.js, que básicamente son dos:

  • Recoger el valor de volumen máximo en dB de un vídeo.
  • Aumentar el volumen del audio de un vídeo en la cantidad indicada.

Si ya no lo recuerdas, correspondía a las llamadas de estas dos funciones:

ffmpeg -i INPUT.mp4  -af "volumedetect" -f null /dev/null 
ffmpeg -i INPUT.mp4 -af "volume=10.7dB" -strict -2 INPUT_NORMALIZED.mp4

Para modelarlas vamos a utilizar dos métodos diferentes:

  1. Usando el wrapper fluent-ffmpeg para manejar FFMpeg desde NodeJS.
  2. Utilizando la capacidad de NodeJS para lanzar comandos.

Vamos con ello:

4.3.3. Fluent-FFMpeg y Testing de Asincronía.

Se trata de un módulo de NodeJS para poder manejar FFMpeg de una forma amigable. Se puede encontrar en https://github.com/fluent-ffmpeg/node-fluent-ffmpeg. Se instala como una dependencia más. Si recuerdas, la hemos instalado al comienzo.

Es fácil de utilizar, como todo en nodeJS:

var ffmpeg = require('fluent-ffmpeg');

El principal problema que nos encontramos es la asincronía de las operaciones. Es decir, como son operaciones de proceso y Javascript es asíncrono, el resultado no se obtiene al final del proceso, sino que se obtiene en un callback al que llama la función cuando acaba. Lo vemos mejor en código:

El código Javascript usando Fluent-FFMpeg para la llamada:

ffmpeg -i INPUT.mp4  -af "volumedetect" -f null /dev/null

es el siguiente:

var getMaxdBFromFile = function(pathTofile, callback){
	var command = ffmpeg(pathTofile)
	.withAudioFilter('volumedetect')
	.addOption('-f', 'null')
	.on('start', function(commandLine) {
		console.log('FFmpeg Command:' + commandLine);
	})      
	.on('error', function(err, stdout, stderr) {
		console.log('An error occurred: ' + err.message);
	})
	.on('end', function(stdout, stderr){        
		var returnedText = utils.getMaxDbFromText(stderr);
		callback(returnedText)
	})
	.saveToFile('/dev/null')
}

exports.getMaxdBFromFile = getMaxdBFromFile;

Si te fijas en la parte de configuración de callbacks, hay 3: start, error y end, que son llamadas para cada evento. El resultado queda en la parte de on:

.on('end', function(stdout, stderr){        
		var returnedText = utils.getMaxDbFromText(stderr);
		callback(returnedText);
	})
}

Básicamente lo que se ha programado son dos cosas:

  • Coger el texto de la salida que se muestra con el resultado (esta librería lo deja en sterr) y se pasa por la función de utilidades que es capaz de extraer sólo la información de los dB de volumen máximo.
  • Pasarle el valor ya filtrado a una función de callback que operará con ello.

Esta función de callback es fundamental, tanto para el desarrollo del programa como para el test oportuno. Por eso hemos puesto el código primero, para demostrar cómo se trata la asincronía con Mocha.

Si intentamos hacer un assert de Chai llamando a la función, fallará al instante porque Mocha, al ejecutar el código Javascript, llama a la función y continúa la ejecución: no espera a que el proceso asíncrono se ejecute.

Afortunadamente Mocha cuenta con un mecanismo para tratar la asincronía. Vamos a hacer el test.

Accedemos al fichero audioLibTest.js en el directorio test para hacer el test de integración sobre un fichero. A este fichero ya le hemos pasado el ffmpeg manualmente, así que sabemos ya el resultado.

Para tratar la asincronía hacemos uso del apartado “before” dentro de un describe, que tiene que ejecutarse antes de los “it” de Mocha. Además, el before, recibe por parámetro la función reservada “done()” de Mocha, que asegura que esperará a que se termine de ejecutar.

Básicamente lo que hacemos es llamar a la función a testear dentro del “before”, y además incluimos en la llamada una función de callback, preparando una variable “resultText” que es la que se evaluará en los asserts posteriores. Este es el código final:

var audioLib = require('../libs/audioLib');
var utils = require('../libs/utils');
var chai = require('chai');

var DIR = './test/';
var VIDEO_FILE = 'SampleVideo_1080x720_1mb.mp4';
var VIDEO_EXTENSION = 'mp4';

describe('Use of FFMpeg to get Max Volume', function() {
	var resultText;

	before(function(done){
		audioLib.getMaxdBFromFile(DIR + VIDEO_FILE, function(text){resultText = text;done();});
	});

	it("Should get max_volume correct value for a specific video file using ffmpeg", function(){
		chai.assert.include(resultText, "-10.7", "Max Volume found");
	});
});

Como se puede ver, la función callback se limita a recoger el resultado y dejarlo en una variable con un scope al que puedan acceder los asserts de chai. Además, incluye done(), que espera a que concluya el proceso asíncrono, de modo que cuando se ejecutan los bloques “it”, la variable “resultText” ya tiene el valor adecuado y puede ser evaluada.

Para el comando de variación del nivel de audio del vídeo vamos a hacer una llamada directamente al sistema con el módulo nativo de NodeJS “child_process”. Como usaremos la salida y la entrada estándar (stdin/stdout) -lo requiere ffmpeg-. Tenemos que usar la función spawn.

Del mismo modo que antes, se trata de una operación lenta y asíncrona por lo que el resultado se dará en un callback.

Para el comando:

ffmpeg -i INPUT.mp4 -af "volume=10.7dB" -strict -2 INPUT_NORMALIZED.mp4

Tendremos el siguiente código:

var spawn = require('child_process').spawn;
var utils = require('./utils.js');

var normaliceAudio = function(pathTofile,leveldB, callback){
	
	var outputFile = utils.getVideoNameForNormalizado(pathTofile);
	utils.deleteFileIfexists(outputFile);

	var command = spawn('ffmpeg',['-i',pathTofile,'-af','volume='+leveldB+'dB','-strict','-2',outputFile]);

	command.stdout.on('data', function (data) {
		console.log('stdout: ' + data);
	});

	command.stderr.on('data', function (data) {
		console.log('stderr: ' + data);
	});

	command.on('close', function (code) {
		console.log('FFMpeg process exited with code ' + code);
		callback();
	});
}

exports.normaliceAudio = normaliceAudio;

Si observas, el require de “child_process” tiene un .spawn al final. Esto quiere decir que sólo se quiere importar la función “.spawn”.

Como antes, establecemos unos callback: para la salida estándar (stdout), para la salida de error (stderr) y para cuando se cierre el comando porque acaba (‘close’). De nuevo ponemos ahí la referencia a una función de callback que pasamos por parámetro para procesar.

El Test de integración para ver si ha funcionado es más enrevesado esta vez, porque tiene que procesar el fichero y para comprobar que lo ha hecho bien, tiene que volver a calcular el volumen máximo. ¿Cómo lo encadenamos? De nuevo a base de callbacks:

describe('Use of FFMpeg to get Normalize', function() {
	var resultText;

	before(function(done){
		audioLib.normaliceAudio(DIR + VIDEO_FILE, "10.7", function(text){
			audioLib.getMaxdBFromFile(utils.getVideoNameForNormalizado(DIR + VIDEO_FILE), function(text){
				resultText = text;done();
			});
		});
	});

	it("Should obtain -0.0 db as max_volume after normalization of the video", function(){
		chai.assert.include(resultText, "-0.0", "Max Volume found");
	});
});

Si te fijas, ahora el callback es la llamada a la otra función y dentro se introduce de nuevo un callback para sacar el resultado de analizar el volumen máximo. Como los callback se ejecutan al finalizar las operaciones, no hay problemas de sincronización.

Y de nuevo la omnipresente función done(), que provoca que el “before” no termine hasta que se han ejecutado todas las funciones.

4.4. El módulo final

Ya tenemos todas las funciones testeadas y con los test correctos, incluidos los de integración. Ahora queda juntar todo en un módulo principal que pueda ejecutar NodeJS y que contenga las llamadas a los módulos que acabamos de crear.

Básicamente, lo que tiene que hacer es:

  1. Obtener el listado de los ficheros de la extensión indicada a procesar de un directorio.
  2. Calcular el volumen máximo del fichero.
  3. Alterar el fichero para subir el volumen.

Sacar el listado de ficheros es sencillo. Lo hemos hecho antes:

var videos = audioLib.getMp4Files(videoDir,videoExtension);

Para mayor utilidad, el directorio (videoDir) y la extensión (videoExtension) las hemos sacado de los parámetros al llamar a la ejecución del fichero con:

node audioNormalizer.js directorio extension
var videoDir = process.argv[2];
var videoExtension = process.argv[3];

El valor 2 y 3 corresponden al parámetro número 3 y 4 de la llamada node. El 0 corresponde a node y el 1 corresponde a audioNormalizer.js.

Una vez tenemos todos los ficheros los recorremos con un bucle y llamamos a las otras dos funciones. Pero ahora veremos que no es tan sencillo.

Como hemos dicho, las funciones de audioLib getMaxdBFromFile y normaliceAudio son funciones que tienen llamadas asíncronas, y por tanto, una vez invocadas, devuelve rápidamente el control al flujo del programa, lanzándose asíncronamente sus procesos y devolviendo en un callback el resultado. ¿Cómo afecta esto al recorrer el array de ficheros de vídeos? De una manera no deseada: se lanzan todos los procesos de cada vídeo en paralelo. Si son 2 o 3 puede ser hasta bueno (aunque FFMpeg hace un muy buen uso de los multiprocesos). Pero si son más, es claramente ineficiente y puede bloquear el ordenador en el que lo estamos ejecutando.

Como hemos visto al hacer los test de integración de cada una de las partes, la clave está en las funciones de Callback. Estas funciones son las que recogen el resultado y aseguran que tenemos un punto en el código en el que hay certeza de que la operación asíncrona lanzada ha finalizado. Así pues tenemos este esquema:

  • Llamada a getMaxdBFromFile para obtener el volumen máximo del vídeo.
    • Callback que recoge el valor y llama a normalizeAudio.
      • Callback sobre el resultado para encolar el siguiente vídeo de la lista, aumentando un contador y volviendo a llamar al proceso.

Sí, estamos haciendo uso de la recursividad. En vez de un bucle o una instrucción map sobre un array, lo que vamos a hacer es mantener un índice que indica sobre qué video del array de vídeos se va a operar. Este índice se altera dentro del callback final, que además vuelve a llamar a la función que lo engloba todo. Lo veremos mejor en el código:

var counter = 0;

var loopProcessVideos = function(videos, counter){

  if (counter" + dB);
      audioLib.normaliceAudio(videoDir + videos[counter].toString(),audioLib.reverseSign(dB),function(){
        console.timeEnd("Main");
        loopProcessVideos(videos,++counter);
      } )
    });
  }
};

loopProcessVideos(videos,0);

Podemos ver el contador, que se inicializa a 0 para comenzar con el primer vídeo del array. La última instrucción es la llamada inicial a la función recursiva, loopProcessVideos(videos,0) y que desencadena todo el proceso.

Y entre ambos está la definición de la función recursiva. ¿Cómo sabemos que es recursiva? Fácil: tiene dos condicionantes. En primer lugar la condición de parada:

if (counter

De otro modo estaría ejecutándose de forma infinita. Esta condición comprueba que el índice no se salga del vector. Es decir, que cuando llegue al último finalice el proceso.

Y también:

loopProcessVideos(videos,++counter);

Que no es otra cosa que la llamada recursiva de nuevo a la propia función pero aumentando un número más el valor el contador o puntero a los vídeos para que procese el siguiente.

Como se puede ver, a través del uso de callbacks y una estructura recursiva, hemos eliminado los problemas de asincronía que surgen si se emplean estructuras que están preparadas únicamente para mecanismos síncronos. Es cierto que también existen otros modos de tratar los problemas de sincronización, como por ejemplo la librería async, pero lo dejamos para otros tutoriales..

5. Ejecución

No he incorporado todo el código en este tutorial porque sería demasiado extenso. Pero puedes hacerte un fork del repositorio de gitHub en el que he alojado la versión final y probarlo por ti mismo (y mejorarlo y adaptarlo a tus necesidades). Lo puedes descargar con:

git clone https://github.com/4lberto/VideoAudioNormalizer

Tampoco olvides descargar las dependencias. Ejecutando este comando en el directorio raíz:

npm install

Y como aparece en el README.md la ejecución es muy sencilla, siempre que tengas instalados los requisitos:

node audioNormalizer.js directorio extension

Por ejemplo:

node audioNormalizer.js /home/4lberto/Video mp4

Pero como somos desarrolladores, y hemos empleado tests para el desarrollo, te recomiendo que lo primero que hagas sea pasar los test con Mocha y veas que todo funciona correctamente (espero). Incluso he incorporado un sencillo fichero .mp4 en el directorio test para poder ejecutar los test de integración.

Simplemente ejecutamos:

mocha

Deberíamos obtener una salida como la siguiente:

✓ Should obtain -0.0 db as max_volume after normalization of the video

  getFilesFromDirWithExtension
    ✓ Should get more than 0 files from the dir
    ✓ Should return only .mp4 files

  textUtils
    ✓ Should get value for max volume for 1 integer and 1 decimal
    ✓ Should get value for max volume for 2 integer and 1 decimal
    ✓ Should get value for max volume for 4 integer and 1 decimal
    ✓ Should return the reverse sign of value obtained
    ✓ Should return the positive value obtained
    ✓ Should return the normalizado Filename


  11 passing (2s)

En realidad no están todos los 11 test porque antes de este texto aparecen las trazas del procesamiento de FFMpeg y sería demasiado extenso como para ponerlo aquí.

6.Conclusiones

Hemos utilizado la excusa de la normalización de audio de los vídeos para explorar el mundo de NodeJS y sus particularidades. Como todo buen inicio en una nueva tecnología de programación, se debe comenzar creando un entorno en el que se puedan ejecutar tests para guiar nuestro desarrollo, para lo cual hemos incluido Mocha y Chai. Se ha revisado el sistema de módulos de NodeJS para compartimentar las aplicaciones. También hemos visto las particularidades de NodeJS con los procesos asíncronos, y cómo se pueden testear a través de callbacks y la función done() de Mocha.

Haz tu código más fiable con Asserts

$
0
0

En este tutorial veremos cuales son las ventajas de realizar comprobaciones en nuestro código mediante asserts (o aserciones), y las distintas opciones de las que disponemos en Java.

Índice de contenidos


1. Introducción

La mayoría de los métodos y de las clases mantienen ciertas restricciones sobre los valores que aceptan como parámetros.

Este tipo de restricciones deben documentarse correctamente para que, con el paso del tiempo, no caigan en el olvido, favoreciendo el hecho de que la aplicación falle o caiga en estados incoherentes si se realizan modificaciones sobre el código sin tener esta serie de restricciones en cuenta.

Por otro lado, documentar el código no siempre es la mejor solución: Es fácil pasarlo por alto, añade tareas de mantenimiento sobre el mismo,…

Por ello, suele ser mejor práctica hacer que nuestro código revele toda la información necesaria para comprender todo el dominio que nuestro sistema trata de dar solución y, tal y como se establece en el diseño por contrato, en este punto es donde entran las aserciones.

Las aserciones son predicados (funciones que devuelven valores lógicos verdadero-falso) y que se espera que siempre sean verdad. Si una aserción se evalúa como falsa en tiempo de ejecución la aserción falla, normalmente lanzando una excepción.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.7 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitan 10.11

3. Ventajas

La programación con aserciones nos ofrece múltiples beneficios. A continuación realizaremos un pequeño resumen de los principales, aunque algunos ya los hemos nombrado en el punto anterior:

  • Favorecen el diseño por contrato, pasando del establecimiento de axiomas a la comprobación de valores de una manera limpia y efectiva.
  • Favorecen el principio falla pronto y rápido si detectas un error.
  • Se garantizan las condiciones necesarias para que nuestro código funcione correctamente.
  • Mejora la mantenibilidad y la legibilidad de nuestro código, facilitando las labores de modificación, refactorización y optimización del mismo.

4. Librerías de Asserts


4.1. Spring Framework

Spring Framework ofrece, dentro de su repositorio de utilidades, una clase a través de la cual realizar estas aserciones: org.springframework.util.Assert. A modo de ejemplo se muestran a continuación algunos ejemplos de las funciones que ofrece la clase:

  • isAssignable(superType, subType):
    • Comprueba que la subclase superType es superclase de subType.
    • Assert.isAssignable(FourWheelsVehicle, Motorcycle, “La moto no es una subclase de VehículoDeCuatroRuedas.”)
  • state(boolean)
    • Comprueba el estado de un objeto.
    • Assert.state(vehicle.isReady(),”El vehículo no está listo todavía.”)
  • notNull(object)
    • Comprueba que el valor no es nulo.
    • Assert.notNull(document, “El documento no puede ser nulo.”)

Cuando alguna de las aserciones no se cumpla, el sistema lanzará una excepción de tipo java.lang.IllegalArgumentException.

Para más información referente a las funciones que mantienen la clase se puede visitar la página de documentación.


4.2. Guava

El conjunto de librería comunes de Google nos ofrece, una serie de utilidades para la realización de aserciones a través de la clase com.google.commons.base.Preconditions. Cada aserción tiene tres variantes:

  • Sin argumentos adicionales: lanza las excepciones sin mensajes o información adicional.
  • Un argumento de tipo Object: lanza las excepciones con el mensaje contenido en object.tostring().
  • Un argumento de tipo String: lanza las excepciones con el mensaje establecido en el argumento.

Algunas de las aserciones que mantiene son:

  • checkArgument(boolean)
    • Comprueba que el predicado es cierto.
    • Preconditions.checkArgument(minute < 60, “Los minutos deben ser inferiores a 60.”)
  • checkNotNull(T)
    • Comprueba que el valor no es nulo.
    • Preconditions.checkNotNull(document, “El documento no puede ser nulo.”)
  • checkState(boolean)
    • Comprueba el estado de un objeto.
    • Preconditions.checkState(vehicle.isReady(), “El vehículo no está listo todavía.”)

En los casos en los que no se cumpla la aserción el sistema lanzará una excepción de tipo java.lang.IllegalArgumentException.

Para más información se puede visitar su repositorio en github, donde se explican la totalidad de los métodos de la clase.


4.3. Apache Commons

El proyecto Apache Commmons provee una serie de funcionalidades reusables y de ámbito general, entre las cuales cuenta con la clase org.apache.commons.lang3.Validate que nos provee de las funciones necesarias para la realización de aserciones. Cada aserción tiene tres variantes:

  • Sin argumentos adicionales: Lanza las excepciones sin mensajes o información adicional.
  • Con un argumento de tipo String: Lanza las excepciones con el mensaje establecido en el argumento.
  • Con un argumento de tipo String seguido por varargs: Lanza la excepción con el mensaje establecido en el argumento y sustituyendo las etiquetas con los valores de definidos como varargs.

A continuación, una muestra de las funciones para la realicación de aserciones:

  • isTrue(boolena)
    • Comprueba que el predicado es cierto.
    • Validate.isTrue(minute < 60, “Los minutos deben ser inferiores a 60.”)
  • notNull(T object)
    • Comprueba que el valor no es nulo.
    • Validate.notNull(document, “El documento no puede ser nulo.”)
  • validState(T collection, index)
    • Comprueba el estado de un objeto.
    • Validate.validState(vehicle.isReady(),”El vehículo no está listo todavía.”)

En caso no cumplirse la aserción se lanzará una excepción de tipo java.lang.IllegalArgumentException.

Para más información se puede visitar la documentación de la clase, donde se explican la totalidad de los métodos de la clase.


4.4. Java

Las aserciones en Java aparecen por primera vez en la versión 1.4 y no ofrecen otra acción que la misma aserción. Se desaconseja su uso en entornos productivos y requiere de el establecimiento de ciertas opciones de la JVM (-ea).

Un ejemplo de uso puede verse a continuación:

assert null != name : "El nombre no puede ser nulo.";

En caso de no cumplirse la aserción se lanza un error de tipo java.lang.AssertionError.


5. Conclusiones

Durante el tutorial hemos visto que el trabajo con aserciones nos ofrece numerosas ventajas durante la práctica del desarrollo. También hemos visto algunas de las opciones de las que dispone Java para realizar aserciones.

Como consecuencia ¡ya no tendréis excusas para no utilizarlas!

Composition y Custom Exporter en JSF2 – Caso práctico

$
0
0

En este tutorial veremos como mostrar en una misma celda de una tabla JSF una lista de valores separados por comas. Además veremos qué debemos hacer para exportar dicho contenido a una hoja de cálculo con el soporte de Primefaces Extensions.

Índice de contenidos


1. Introducción

En este tutorial trataremos un caso muy concreto que nos podemos encontrar a la hora de trabajar con Datatables JSF. Imaginemos que el contenido de una de las celdas de la tabla viene dado por una lista de objetos y lo que queremos mostrar es un campo concreto de dichos objetos, separando cada uno de los valores por comas. Una solución podría ser modificar la clase de negocio utilizada para rellenar dicha tabla, teniendo un método que nos transforme el contenido de la lista a un string. Sin embargo, en este tutorial veremos como hacerlo a nivel de vista, evitando acoplar nuestra capa de negocio con una necesidad concreta de la capa de vista. Además, veremos como customizar el exporter a Excel de Primefaces para que nos genere un fichero de hoja de cálculo con el contenido tal y como lo vemos en la tabla.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

    • Hardware: Portátil MacBook Pro 15’ (2 GHz Intel Core i7, 8 GB 1333 MHz DDR3)
    • Sistema Operativo: Mac OS X Yosemite 10.10.4
    • Entorno de desarrollo: Eclipse Java EE IDE, Mars Release (4.5.0)
    • JSF 2.1
    • Primefaces 5.3
    • Apache Maven 3.3.3

3. Desarrollar un servicio de ejemplo

Como suele ser habitual, para ver este tipo de cosas, lo mejor es que tratemos con un ejemplo sencillo. Y también como siempre, en primer lugar, incluimos en nuestro pom.xml las dependencias necesarias:

pom.xml
...

	<dependencies>
		<dependency>
			<groupId>javax.faces</groupId>
			<artifactId>jsf-api</artifactId>
			<version>2.1</version>
		</dependency>
		<dependency>
			<groupId>org.primefaces</groupId>
			<artifactId>primefaces</artifactId>
			<version>5.3</version>
		</dependency>
		<dependency>
			<groupId>org.primefaces.extensions</groupId>
			<artifactId>primefaces-extensions</artifactId>
			<version>4.0.0</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish</groupId>
			<artifactId>javax.faces</artifactId>
			<version>2.2.12</version>
		</dependency>
		<dependency>
			<groupId>javax.el</groupId>
			<artifactId>el-api</artifactId>
			<version>2.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi</artifactId>
			<version>3.13</version>
		</dependency>
		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi-ooxml</artifactId>
			<version>3.13</version>
		</dependency>
	</dependencies>

...

En este caso, vamos a crearnos un servicio que genere una serie de personas con una serie de hobbies que serán mostrados en una tabla JSF. Para ello, nos creamos un nuevo proyecto maven preparado para JSF y desplegable en servidor. En primer lugar vamos a crearnos nuestros POJOs, en primer lugar tendremos una clase Hobby que definimos de la siguiente manera:

Hobby.java
package com.autentia.tutoriales.uirepeat.domain;

import java.io.Serializable;

public class Hobby implements Serializable {

    private final String id;

    private final String description;

    public Hobby(String id, String description) {
        this.id = id;
        this.description = description;
    }

    public String getId() {
        return id;
    }

    public String getDescription() {
        return description;
    }

}

Y por otro lado, tendremos una clase Person, que tendrá como atributo una lista de la clase Hobby que definimos de la siguiente manera:

Person.java
package com.autentia.tutoriales.uirepeat.domain;

import java.io.Serializable;
import java.util.List;

public class Person implements Serializable {

    private final int id;

    private final String name;

    private List hobbies;

    public Person(int id, String name, List hobbies) {
        this.id = id;
        this.name = name;
        this.hobbies = hobbies;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public List getHobbies() {
        return hobbies;
    }

}

Una vez definido nuestro dominio, implementamos nuestra capa de negocio. En este caso, se trata de un servicio que genera personas con unos ciertos hobbies que luego serán consumidas por nuestra capa de vista. Por tanto, definimos nuestro PersonService de la siguiente manera:

PersonService.java
package com.autentia.tutoriales.uirepeat.service;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;

import com.autentia.tutoriales.uirepeat.domain.Hobby;
import com.autentia.tutoriales.uirepeat.domain.Person;

@ManagedBean(name = "personService")
@ApplicationScoped
public class PersonService implements Serializable {

    private static final int NUMBER_OF_HOBBIES = 3;

    private static final String[] names;

    private static final String[] hobbys;

    static {
        names = new String[5];
        names[0] = "Lucía";
        names[1] = "Pedro";
        names[2] = "María";
        names[3] = "Jose";
        names[4] = "Laura";
    }

    static {
        hobbys = new String[5];
        hobbys[0] = "Lectura";
        hobbys[1] = "Manualidades";
        hobbys[2] = "Pintura";
        hobbys[3] = "Deportes";
        hobbys[4] = "Nuevas tecnologías";
    }

    public PersonService() {

    }

    public List generatePersons(int numberOfPersons) {
        List persons = new ArrayList();
        for (int i = 0; i < numberOfPersons; i++) {
            persons.add(new Person(i, names[i], getRandomHobbies()));
        }
        return persons;
    }

    private List getRandomHobbies() {
        List hobbies = new ArrayList();
        for (int i = 0; i < NUMBER_OF_HOBBIES; i++) {
            Hobby hobbie = getRandomHobbyInstance();
            if (!hobbies.contains(hobbie)) {
                hobbies.add(hobbie);
            }
        }
        return hobbies;
    }

    private Hobby getRandomHobbyInstance() {
        String name = hobbys[(int)(Math.random() * 5)];
        return new Hobby(name, name);
    }

}

A partir de este momento, tenemos la información necesaria que queremos plasmar en una tabla JSF. En el siguiente apartado veremos como tratar la lista de hobbies.


4. Mostrar contenido en una tabla JSF

Cuando queremos desarrollar nuestra capa de vista, en la que queremos tener una tabla en la que se muestre el nombre de la persona y sus hobbies separados por comas, el mayor problema viene derivado de esta segunda parte, ¿cómo mostrar dichos hobbies en una misma celda separados por comas?, además hacerlo a nivel de vista. Lo que vamos a hacer es definirnos un componente por composición JSF, de esta forma, podremos reutilizarlo en otras vistas. Para ello, nos podemos crear una carpeta components dentro de la carpeta webapp/resources de nuestro proyecto, en la que definimos nuestro componente JSF customizado al que hemos llamado commaSeparatedEntities.xhtml:

commaSeparatedEntities.xhml
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:composite="http://java.sun.com/jsf/composite">

<h:head>

	<title>This will not be present in rendered output</title>

</h:head>

<composite:interface>
	<composite:attribute name="entities" type="java.util.List"
		required="true" />


	<composite:attribute name="property" type="java.lang.String"
		required="true" />
</composite:interface>

<composite:implementation>
	<ui:repeat value="#{cc.attrs.entities}" var="entity" varStatus="status">
		<h:outputText value="#{entity[cc.attrs.property]}" />
		<h:outputText value=", " rendered="#{!status.last}" />
	</ui:repeat>
</composite:implementation>

</html>

No entraré en detalle de como definir un componente por composición, ya que mi compañero de Autentia Jose Manuel Sánchez lo trata en detalle en este tutorial.

A grandes rasgos, vemos una primera parte en la que definimos los parámetros que debe recibir el componente, en este caso el atributo name que recibirá la lista de objetos a recorrer, en nuestro caso una lista de hobbies, y por otro lado, el atributo property, que indica el atributo que queremos pintar en pantalla.

Por otro lado tenemos la implementación del componente por composición. En este caso, utilizamos otros tres componentes JSF para definirlo, uirepeat que recorre la lista de objetos pasada a través del atributo entities, y dos outputText dentro de ese bucle. En el primero de ellos, mostraremos el valor que nos interese pintar de cada uno de los objetos de la lista, y en el segundo incluiremos una carácter coma y un espacio. Como es lógico, al encontrarnos en el último valor a mostrar, no queremos que se incluya la coma. Este detalle lo controlamos con la variable status que nos permite controlar ciertos parámetros del bucle. En este caso utilizamos el campo last, que será cierto cuando nos encontremos al final de la lista. De esta forma podemos pedir al segundo outputText que se renderice siempre que no nos encontremos al final del bucle.

Definido nuestro componente por composición, vamos a usarlo en la vista de nuestro ejemplo, que tendrá la siguiente pinta:

commaSeparatedEntities.xhml
<html xmlns="http://www.w3c.org/1999/xhtml"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:p="http://primefaces.org/ui"
	xmlns:component="http://java.sun.com/jsf/composite/components"
	xmlns:pe="http://primefaces.org/ui/extensions">

<h:head>
</h:head>

<h:body>

	<p:dataTable id="hobbiesTable" var="person"
		value="#{hobbiesView.persons}">
		<p:column>
			<f:facet name="header">
				<h:outputText value="Name" />
			</f:facet>
			<h:outputText value="#{person.name}" />
		</p:column>
		<p:column>
			<f:facet name="header">
				<h:outputText value="Hobbies" />
			</f:facet>
			<component:uirepeat entities="#{person.hobbies}"
				property="description">
			</component:uirepeat>
		</p:column>
	</p:dataTable>

</h:body>
</html>

Como podemos observar, tenemos una tabla que recorre una lista de personas (cada una con sus hobbies), y se nos muestra en una primera columna el nombre de la persona y en la segunda sus hobbies. Como vemos, hemos conseguido mostrar dicha lista en una misma celda utilizando nuestro uirepeat definido como un componente por composición. Como vemos, solo debemos indicar los dos parámetros de dicho componente necesarios para mostrar la información en el formato que hemos decidido. Por tanto, como entities tenemos la lista de hobbies, y como property el valor que queremos imprimir, en este caso description.

Otro detalle a tener en cuenta es que hemos definido en nuestros espacios de nombre xmlns:component=”http://java.sun.com/jsf/composite/components” para poder hacer referencia a nuestro componente por composición.

Si desplegamos nuestro servicio en un servidor y accedemos a la página commaSeparatedEntities.xhtml el resultado será el siguiente:


5. Exportar contenido a hoja de cálculo

Ya podemos visualizar el contenido de la tabla de la forma que queríamos, pero ¿qué pasa si hacemos esta tabla exportable a Excel? Pues que si utilizamos el exporter de JSF nos encontraremos que el contenido de las celdas de hobbies no es el esperado. Si queremos que dicho contenido sea el que visualizamos en nuestra página, debemos crear un exporter customizado, con el soporte de Primefaces Extensions, siguiendo los siguientes pasos:

  • Dentro de la carpeta webapp/resources crearnos una carpeta META-INF (si no existe previamente), y a su vez crearnos otra carpeta dentro de META-INF llamada service. Dentro de la carpeta service debemos crearnos un fichero llamado org.primefaces.extensions.component.exporter.ExporterFactory El contenido de dichero fichero ha de ser el siguiente (para nuestro ejemplo concreto):
    org.primefaces.extensions.component.exporter.ExporterFactory
    com.autentia.tutoriales.component.CustomExporterFactory

    Lo que estamos definiendo es el lugar donde encontramos nuestra fábrica de exporter. En nustro casos en el paquete del proyecto com.autentia.tutoriales.component.CustomExporterFactory

  • El siguiente paso es implementar dicha fábrica tal y como se muestra en el siguiente bloque de código:
    CustomExporterFactory.java
    package com.autentia.tutoriales.component;
    
    import javax.faces.FacesException;
    import javax.faces.context.FacesContext;
    
    import org.primefaces.extensions.component.exporter.ExcelExporter;
    import org.primefaces.extensions.component.exporter.Exporter;
    import org.primefaces.extensions.component.exporter.ExporterFactory;
    
    public class CustomExporterFactory implements ExporterFactory {
    
        static public enum ExporterType {
            XLSX
        }
    
        public Exporter getExporterForType(String type) {
    
            Exporter exporter = null;
    
            FacesContext context = FacesContext.getCurrentInstance();
    
            try {
                ExporterType exporterType = ExporterType.valueOf(type.toUpperCase());
    
                switch (exporterType) {
    
                case XLSX:
                    exporter = new ExcelCustomExporter();
                    break;
    
                default: {
                    exporter = new ExcelExporter();
                    break;
                }
    
                }
            } catch (IllegalArgumentException e) {
                throw new FacesException(e);
            }
    
            return exporter;
        }
    
    }

    Como se puede observar, este extiende de la clase de primefaces extensions ExporterFactory, y con él podemos definir un comportamiento particular para casos concretos. En el ejemplo que nos atañe, queremos exportar a excel y que cuando lo haga sea a través de un exporter customizado al que hemos llamado ExcelCustomExporter y que veremos a continuación. Por tanto, hemos implementado la lógica necesaria para que al encontrarnos ante un exporter a XLSX (Hoja de cálculo Excel), utilice nuestro exporter customizado.

  • Debemos de crear un exporter a excel que trate de manera concreta los componentes uirepeat. Este exporter lo hemos llamado ExcelCustomExporter y se define de la siguiente manera:
    ExcelCustomExporter.java
    package com.autentia.tutoriales.component;
    
    import java.io.IOException;
    import java.io.StringWriter;
    
    import javax.faces.component.UIComponent;
    import javax.faces.component.UINamingContainer;
    import javax.faces.context.FacesContext;
    import javax.faces.context.ResponseWriter;
    
    import org.primefaces.extensions.component.exporter.ExcelExporter;
    
    public class ExcelCustomExporter extends ExcelExporter {
    
        @Override
        protected String exportValue(FacesContext context, UIComponent component) {
            if (component instanceof UINamingContainer) {
                final StringWriter stringWriter = new StringWriter();
                final ResponseWriter cachingResponseWriter = context.getRenderKit().createResponseWriter(stringWriter,
                        "text/html", "UTF-8");
                context.setResponseWriter(cachingResponseWriter);
                try {
                    component.encodeAll(context);
                } catch (IOException e) {
                }
    
                return stringWriter.getBuffer().toString().replaceAll("\\<.*?>", "");
            }
    
            return super.exportValue(context, component);
        }
    }

    Como vemos, extiende del exporter a Excel por defecto de primefaces, pero se diferencia en que a la hora de exportar un valor que viene dado de un un componente UINamingContainer como es el caso de nuestra columna de hobbies, muestra el valor renderizado eliminando posteriormente las etiquetas de la vista. En los casos en los que no se trate de ese componente, llamará al método padre que se encargará de obtener los valores a exportar de la manera habitual.

  • El último paso será incluir en nuestro fichero commaSeparatedEntities.xhtml el siguiente trozo de código (inmediatamente después de la definición de la datatable):
    commaSeparatedEntities.xhml
    <html xmlns="http://www.w3c.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:p="http://primefaces.org/ui"
    xmlns:component="http://java.sun.com/jsf/composite/components"
    xmlns:pe="http://primefaces.org/ui/extensions">
    
    ...
    
    	</p:dataTable>
    
    	<h:form>
    		<h:commandLink
    			styleClass="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only ui-priority-primary"
    			style="padding:5px;">
    			Export
    			<pe:exporter type="xlsx" target=":hobbiesTable" fileName="hobbies" />
    		</h:commandLink>
    	</h:form>
    
    </h:body>
    </html>

    De esta forma, incluiremos un botón que nos permitirá dar la orden de exportar la tabla a Excel. Como hemos definido un exporter customizado de excel, se llamará a dicho exporter.

Después de esta implementación, podemos exportar el contenido de la tabla obteniendo un fichero Excel de la siguiente forma:


6. Conclusiones

Espero que este tutorial os sea útil si os encontráis ante situaciones concretas como estas, en las que queremos cumplir unos requisitos concretos sin recurrir a soluciones que acoplen las diferentes capas de nuetros proyectos y así mantener un código de una mejor calidad.

Tengo que dar las gracias a mi compañero Jose Manuel Sánchez por ser él quién me proporcionó esta solución para resolver este problema, surgido de las necesidades de un proyecto. Y que además fue quién me sugirió la idea de plasmar la información en un tutorial de adictosaltrabajo.

Sin más, solo me queda decir que espero que os haya servido de ayuda este tutorial tal y como me va a servir a mí cuando pase mucho tiempo y se me haya olvidado todo esto y tenga que recurrir a él porque me habré visto en una situación igual o al menos parecida.


7. Referencias

Tutorial ‘Docker Integration Test’

$
0
0

Siguiendo con la serie de tutoriales dedicados a Docker, vamos a ver como integrar Docker dentro de nuestros proyectos a través de Maven y como lanzar nuestros test de integración sobre una base de datos creada en un contenedor.

0. Índice de contenidos


1. Introducción

En el tutorial Docker para Bases de Datos habíamos creado una serie de imágenes con diferentes motores de BBDD, ahora vamos a dar un paso más y vamos a integrar una de estas imágenes (en concreto la imagen con DB2 ) dentro de nuestro proyecto Maven.

El objetivo es que durante la fase de tests de integración, construyamos la imagen con DB2, arranquemos el contenedor, ejecutemos los test de integración y destruyamos el contenedor.


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.9.1, Docker Machine 0.5.2

3. Plugins Maven Docker

En la actualidad existen varios plugin para integrar docker con nuestros proyectos java, entre ellos destacamos:

Entre estas alternativas nos hemos decantado por rhuss/docker-maven-plugin, básicamente porque dispone de una documentación más completa. Gracias a este plugin podemos hacer uso de docker a traves de Maven con los siguientes goals:

Goal Description
docker:start Arranca un contenedor
docker:stop Para y destruye el contenedor
docker:build Construye la imagen
docker:watch Habilita la reconstrucción de imágenes y rearranque de contenedores en caso de cambios
docker:push Sube la imagen al repositorio
docker:remove Elimina la imagen
docker:logs Muestra los logs del contenedor

4. Configuración del plugin

En este apartado vamos a ver como configuramos el plugin:

<plugin>
	<groupId>org.jolokia</groupId>
	<artifactId>docker-maven-plugin</artifactId>
	<version>0.13.6</version>
	<configuration>
		<logDate>default</logDate>
		<autoPull>true</autoPull>
		<images>
			<image>
				<alias>db</alias>
				<name>jpacheco/db2-formacion</name>
				<build>
					<assembly>
						<dockerFileDir>demo</dockerFileDir>
					</assembly>
				</build>
				<run>
					<privileged>true</privileged>
					<ports>
						<port>50001:50000</port>
					</ports>
					<wait>
						<time>200</time>
					</wait>
					<log>
						<color>yellow</color>
						<prefix>DB PRUEBA DOCKER MAVEN</prefix>
					</log>
					<cmd>db2start</cmd>
				</run>
			</image>
		</images>
	</configuration>

En el apartado configuration indicamos:

  • logDate: que tome la configuración de fecha por defecto para los logs
  • autoPull: indicamos que si las imágenes base no están en nuestro repositorio local, intente hacer un docker pull ..

En el apartado images describimos las imágenes que podemos gestionar desde nuestro proyecto. En nuestro ejemplo vamos a describir la imagen con DB2 dentro del tag image :

  • alias: Alias de la imagen (puede ser usada para vincularla a otra imagen)
  • name: Nombre de la imagen

En la sección build le indicamos la configuración necesaria para construir la imagen, la información que tomará cuando ejecutemos mvn docker:build

  • assembly / dockerFileDir :Directorio donde se encuentra el DockerFile y ficheros necesarios para construir la imagen

En la sección run le indicamos la configuración necesaria para arrancar un contenedor mvn docker:start

  • privileged :Directorio donde se encuentra el DockerFile y ficheros necesarios para construir la imagen
  • ports / port : Mapeo de puertos entre el contenedor y la máquina anfitriona
  • wait / time : Tiempo en ms de espera a que arranque el contenedor
  • log / color / prefix : Configuramos el color y el prefijo de los logs del plugin
  • cmd : Comando de arranque del contenedor

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

mvn docker:build

Con la que construimos la imagen.

mvn docker:start

Arranca el contenedor, como podemos comprobar con docker ps.

mvn docker:stop

Paramos y destruimos el contenedor, como podemos comprobar con docker ps -a.


5. Integración con Test de Integración

Una vez hemos configurado correctamente el plugin, vamos a engancharlo con nuestros test de integración, para ello vamos a partir de un proyecto Spring + MyBatis + Maven que esta disponible aquí

No se trata de explicar el código del proyecto, basta decir que hemos usado el plugin de Maven Failsafe para lanzar los test de integración usando la configuración básica y cumpliendo con la convención de llamar a nuestros tests de integración con el sufijo “IntegrationTest”. (conviene indicar que asociaremos la fase de de test de integración a un ‘profile’ maven llamado integration)

Sólo nos falta enganchar el plugin de Docker en las fases pre-integration-test (construimos la imagen y arrancamos el contenedor) y post-integration-test (paramos – borramos el contenedor y destruimos la imagen)

Para ello basta con añadir a la configuración del plugin lo siguiente:

<executions>
	  <execution>
	    <id>start</id>
	    <phase>pre-integration-test</phase>
	    <goals>
	      <goal>build</goal>
	      <goal>start</goal>
	    </goals>
	  </execution>
	  <execution>
	    <id>stop</id>
	    <phase>post-integration-test</phase>
	    <goals>
	      <goal>stop</goal>
	      <goal>remove</goal>
	    </goals>
	  </execution>
	</executions>

como se puede observar en el código estamos vinculando los goals de build y start a la fase de pre-integration-test y los de stop y remove a la fase post-integration-test

Una vez configurado todo podemos lanzar los test de integración, ejecutando

mvn clean verify -P integration

Como podemos apreciar en la imagen se construye la imagen Built image 38786c137995, arranca el contenedor Start container 5b7eb588ea26, se lanzan los test org.autentia.tutoriales.ProfesorMapperIntegrationTest y finalmente paramos y destruimos el contenedor Stop and remove container 5b7eb588ea26 y eliminamos la imagen Remove


6. Conclusiones

Como hemos podido comprobar a través de esta serie de tutoriales, los ámbitos de aplicación de Docker parecen ilimitados, en próximos tutoriales seguiremos descubriendo nuevas características y usos de Docker.

Un saludo.


7. Referencias

Creación de un generador de Yeoman

$
0
0

En este tutorial vamos a ver lo sencillo que es crear un generador de Yeoman para ajustarlo a las necesidades de nuestros proyectos, y evitar el temido “folio en blanco” y no volver a repetir una y otra vez la configuración inicial cada vez que queramos arrancar un nuevo proyecto JavaScript.

Índice de contenidos


1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Mac Book Pro 15″ (2,3 Ghz Intel Core i7, 16 GB DDR3)
  • Sistema Operativo: Mac OS X El Capitan
  • NVM v0.29.0
  • NodeJS v5.1.0
  • npm 3.3.12
  • Atom 1.3.2
  • Yeoman 1.5.1

2. Introducción

La forma más rápida de crear proyectos en JavaScript es utilizando Yeoman que es la herramienta que nos permite crear un proyecto a partir de una plantilla, a los javeros esto les recordara a los arquetipos de Maven.

Para poder utilizarla lo único que tenemos que hacer es instalarla con npm

$> npm install -g yo

En Internet hay multitud de plantillas llamadas “generators”, un amplio listado lo podemos encontrar en: http://yeoman.io/generators/

Estos pueden ser instalados de la misma forma, por ejemplo:

$> npm install -g generator-ionic
$> npm install -g generator-ng2

Nota: es importante instalarlos con -g para que se haga de forma global, ya que no tenemos proyecto al que asociarlo.

De esta forma cuando queramos crear un proyecto usando una de estos generadores, solo tendremos que hacer dentro del directorio de nuestro proyecto.

$> yo nombre_generador_sin_generator
$> yo ionic
$> yo ng2

Y ya tendremos una semilla para empezar a programar.


3. Creación de un generador

Si no encontramos el generador adecuado siempre nos queda la opción de crear el nuestro propio, sigue estos pasos.

Lo primero será instalar el generador que nos permite hacer generadores. 😉

$> npm install -g generator-generator

Ahora creamos el directorio de nuestro generador y nos situamos dentro de él.

$> mkdir generator-tutorial
$> cd generator-tutorial

Invocamos al generador ejecutando:

$> yo generator

Y este nos hace una serie de preguntas:

  • Your generator name: por defecto, toma el nombre de la carpeta.
  • Description: damos una breve descripción de no más de una línea de lo que hace nuestro generador.
  • Project homepage url: si contamos ya con una url para el proyecto. Se suele poner la URL de github.
  • Author’s Name: el nombre que se quiera atribuir la creación del generador.
  • Author’s Email: el email del creador.
  • Author’s Homepage: si se quiere poner la URL de la página del autor.
  • Package keywords (comma to split): palabras significativas para clasificar el generador en los buscadores de npm.
  • Send coverage reports to coveralls: por si queremos enviar los informes para que estén disponibles.
  • GitHub username or organization: el usuario de github con el que queramos registrar el paquete.
  • Your website: por si quieres poner la URL a tu página.
  • Which license do you want to use?: permite seleccionar la licencia con la que queremos registrar el generador. Apache 2.0 es bastante adecuada, aunque dependerá del objetivo de cada uno.

Una vez aportada toda esta información se nos creará la siguiente estructura de carpetas:

img1-yeoman-creator

De esta estructura los archivos importantes son:

  • package.json: que va a contener toda la información del generador y que almacena parte de la información que hemos introducido en el asistente.
  • generators/app/index.js: es el fichero donde se detalla el proceso de instalación y copia de recursos necesarios.
  • generators/app/templates: va a contener los ficheros que queramos que tenga el proyecto que vamos a generar.

Para poder ir probando el desarrollo, npm nos permite enlazar este paquete en el registro global de nuestra máquina de forma que podremos crear un proyecto haciendo uso de este generador.

$> npm link

Ahora creamos el proyecto haciendo uso del generador, simplemente, creamos un directorio para nuestro proyecto dentro de él ejecutamos:

$> yo tutorial
Lo primero que llama la atención es la presentación y la pregunta “Would you like to enable this option? (Y/n)” que es un prompt de ejemplo que eliminaremos más tarde. img2-yeoman-creator

Contestamos a la pregunta y nos va a crear la estructura de ficheros que tuviésemos dentro de la carpeta “templates”.

Ahora es cuestión de adaptar el contenido de la carpeta “templates” a nuestras necesidades. Para ello, vamos creando o copiamos de otros proyectos los ficheros deseados, como package.json, gulpfile.js, index.html, y directorios como app, test, …

Para este ejemplo, vamos a copiar un fichero package.json, junto con un index.html y un directorio app con el contenido del proyecto de ejemplo.

Pero no basta solo con copiarlos dentro del directorio “templates” tenemos que indicar explícitamente que queremos que ese fichero sea parte de la salida del generador.

Para ello, editamos el fichero generators/app/index.js que tendrá un contenido parecido a este:

'use strict';
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var yosay = require('yosay');

module.exports = yeoman.generators.Base.extend({
  prompting: function () {
    var done = this.async();

    // Have Yeoman greet the user.
    this.log(yosay(
      'Welcome to the brilliant ' + chalk.red('generator-tutorial') + ' generator!'
    ));

    var prompts = [{
      type: 'confirm',
      name: 'someOption',
      message: 'Would you like to enable this option?',
      default: true
    }];

    this.prompt(prompts, function (props) {
      this.props = props;
      // To access props later use this.props.someOption;

      done();
    }.bind(this));
  },

  writing: function () {
    this.fs.copy(
      this.templatePath('dummyfile.txt'),
      this.destinationPath('dummyfile.txt')
    );
  },

  install: function () {
    this.installDependencies();
  }
});

En este fichero vamos a realizar una serie de modificaciones: la primera de ellas será eliminar el prompt que viene por defecto. Para ello sustituimos todo este bloque:

var prompts = [{
      type: 'confirm',
      name: 'someOption',
      message: 'Would you like to enable this option?',
      default: true
    }];

    this.prompt(prompts, function (props) {
      this.props = props;
      // To access props later use this.props.someOption;

      done();
    }.bind(this));

Por:

done();

La siguiente modificación será indicar los ficheros que van a formar parte de la salida del generador. Esto se hace dentro de la función “writing”, quedando de esta forma:

writing: function () {
    this.fs.copy(
      this.templatePath('package.json'),
      this.destinationPath('package.json')
    );
    this.fs.copy(
      this.templatePath('index.html'),
      this.destinationPath('index.html')
    );
    this.directory(
      this.templatePath('test'),
      this.destinationPath('test')
    );
  },

Tanto en “copy” como en “directory” especificamos el origen en el template y donde lo queremos situar en la salida.

Solo hay que tener especial cuidado con el fichero .gitignore, ya que node lo renombra a .npmignore (no me preguntéis por qué) y a la hora de crear el proyecto lógicamente no lo encuentra. Para solucionarlo hay que renombrar el fichero del template a _gitignore, por ejemplo, y especificar este nombre en el templatePath y poner .gitignore en destinationPath, de esta forma:

this.fs.copy(
      this.templatePath('_gitignore'),
      this.destinationPath('.gitignore')
    );

La función “install” es la encargada instalar las dependencias que vaya a necesitar nuestro proyecto:

install: function () {
    this.npmInstall(['tsd'], { 'global': true });
    this.npmInstall();
    this.runInstall(‘bower’');
  }

La primera sentencia sería para instalar cualquier dependencia de forma global, la segunda sería para ejecutar un “npm install” y la tercera se utiliza para ejecutar el comando “install” de bower, jspm, o lo que necesitemos.

Ahora para probar los cambios simplemente vamos al directorio de nuestro proyecto de prueba, borramos su contenido y volvemos a ejecutar:

$> yo tutorial

Y vemos como ahora el proyecto ha tomado la estructura que le hayamos indicado en el generador.


4. Publicación del generador

Una vez estemos contentos con nuestro generador, el siguiente paso será publicarlo, en el repositorio público de npm o en cualquiera que tengamos privado.

Este paso no difiere en nada de publicar cualquier otro paquete en el registro de npm público por lo que dentro del directorio de nuestro generador ejecutamos:

$> npm publish

Si es la primera vez que publicamos en npm, nos dará un error informando que primero tenemos que ejecutar el comando:

$> npm adduser

En caso de no tener cuenta en npm, primero vamos a la página https://www.npmjs.com/ y la creamos. Al ejecutar el comando, nos preguntará:

  • Username: nuestro usuario de npm
  • Password: nuestra password de npm
  • Email: (This is public): el email que queremos que se muestre

A continuación volvemos a la lanzar el comando:

$> npm publish

El registro se hará efectivo pasados unos minutos y cualquiera podrá instalar nuestro generador para poder ser utilizado mediante el comando:

$> npm install -g generator-tutorial

Si queremos eliminar la publicación del paquete solo tendremos que ejecutar desde dentro del directorio del generador:

$> npm unpublish --force

Nota: No es obligatorio pero si muy aconsejable tener un repositorio en GitHub asociado con el proyecto o el generador que hayamos subido a NPM


5. Conclusiones

Pues ya veis que crear nuestro propio generador de Yeoman aporta muchas ventajas y no es nada complicado, todo va a depender de lo especifico que lo queramos hacer.

Cualquier duda o sugerencia en la zona de comentarios.

Saludos.

Encuestas en dos minutos con SurveyMonkey

$
0
0

Dedicaremos este tutorial a crear encuestas rápidamente con SurveyMonkey paso a paso comentando, entre otras, la personalización y las estadísticas posteriores.

Índice de contenidos


1. Introducción

La idea de desarrollar este tutorial surgió ante la perspectiva de realizar una encuesta interna en Autentia con un tiempo limitado y una semana con bastante trabajo y diversos proyectos. Por ello recurrí a una vía rápida pero efectiva que ya había utilizado en el pasado para realizar encuestas: SurveyMonkey.

Es la opción perfecta en este caso, ya que cuenta con plantillas a disposición de sus usuarios, lo que le ahorra mucho tiempo.


2. Crear una encuesta

Una vez un usuario se registra en una cuenta de SurveyMonkey accede a una web con varios botones. Si deslizamos el ratón por ellos en “Ejemplos”, la primera opción que aparece es la de “Plantillas de encuestas”.


Plantillas de encuestas

Como estamos tratando de crear una encuesta de forma rápida y anónima para los participantes de momento adoptaremos esta opción. La nueva página que se carga ofrece una larga lista de plantillas a elegir en función del tema a tratar. Algunos de los casos citados son “encuestas de satisfacción al cliente, de investigación académica y universitaria, de atención médica o del departamento de personal, por mencionar alguno de los posibles casos. Seleccionemos una de las anteriores, la de “encuestas a empleados” sin ir más lejos.


Casos de ejemplos de plantillas

En la imagen superior se pueden observar los consejos que ofrece la plantilla una vez seleccionada. Me siento en la obligación de recordar que seguimos en la versión gratuita de SurveyMonkey y que por tanto, estos consejos se ofrecen de forma gratuita. En este caso se orienta al hipotético usuario sobre cómo usar las encuestas de empleados, se le ofrecen pautas sobre cómo obtener una respuesta natural y las mejores prácticas de los mismos, siempre sin olvidar el anonimato en el que permanecen los encuestados si es que el gestor del test así lo decide, punto muy positivo si se trata de opiniones privadas en el ámbito laboral.


Comenzar encuesta

La siguiente pantalla que aparece ante los ojos del usuario se refiere directamente a la configuración de la encuesta. Aquí se vuelve a ofrecer la posibilidad al usuario de realizar la encuesta desde cero a su criterio, de copiar una encuesta ya existente en su perfil o bien de comenzar a partir de una plantilla diseñada por expertos. Elegiremos esta última opción ya que la disyuntiva planteada en este caso partía de la premisa de la carencia de tiempo.


2.1. Personalización de la encuesta

Este es el punto de la configuración en el que el usuario establecerá sus preguntas y las opciones de respuesta, tanto si esta es de respuesta múltiple (el mítico multiple choice de los exámenes de Inglés), de respuesta de rellenar huecos (el fill in the gaps), etc. Asimismo podremos elegir el número de preguntas que queremos, el formato, las posibles imágenes en el caso de que queramos incluirlas, cuadros de comentarios, etc.


Personalización encuesta

En este paso de la realización de la encuesta nos encontramos con más opciones únicamente disponibles con el servicio Premium. Quizá la que más eche en falta el usuario sea la eliminación del pie de página en el que aparece un “distribuido por SurveyMonkey”. Como es evidente, cuando se intenta suprimir esta franja la página sugiere al usuario que pase al servicio Premium para poder realizar tal operación.

Terminada, sólo queda enviarla a los correos electrónicos que se desee o bien proporcionar un link para que los encuestados accedan a ella.


3. Las respuestas

Una vez enviada la encuesta, cuando los encuestados comienzan a responder, podemos encontrar en nuestro panel de control algo como esta encuesta interna que creé para un caso interno con mis compañeros de Autentia. Se pueden observar 29 respuestas y una serie de acciones representadas en tres iconos.


Muestra de los que se ve en dashboard

El primer botón permite editar la encuesta, el segundo observar las respuestas a las preguntas y el tercero ver las estadísticas de la campaña. Como mencionaba, el segundo ofrece un listado de todas las respuestas recibidas categorizadas según las preguntas. Esto es, aparece la primera pregunta y debajo el número de respuestas recibidas y omitidas.


Resultados

Por último, el tercero de los botones expone las estadísticas y selecciona las respuestas según sus usuarios, uno por uno, aunque insisto aparecen como números si hemos seleccionado durante la configuración que deseábamos que se tratase de una encuesta anónima. Así, en esta imagen se puede observar al usuario 27 que ha respondido a la pregunta 1, “sí” y a la 2 “Dependiendo del tamaño. Sólo si fuese pequeña (tamaño pegatina, de un teléfono móvil)”.


Respuesta individual

Espero que os haya servido este pequeño consejo sobre cómo salir un día del paso en el caso de que os toque realizar una encuesta repentina.


Migración de proyectos entre cuentas en Pivotal Tracker

$
0
0

Tutorial donde se explica paso a paso de la forma más práctica posible cómo poder migrar un determinado proyecto de Pivotal Tracker de una cuenta a otra sin que se pierda ninguna información en el camino.

Índice de contenidos


1. Introducción

En el tutorial de hoy vamos a explicar paso a paso de la forma más práctica posible cómo poder migrar un determinado proyecto de Pivotal Tracker de una cuenta a otra sin que se pierda ninguna información en el camino.

Puede ser común encontrarse con proyectos que inicialmente son arrancados por unas determinadas personas o empresas como responsables de éstos pero que tras el paso de los meses o tras la finalización del proyecto pasan a manos de otras personas/empresas. Es en ese momento donde pueden surgir algunas dudas al respecto de cómo hacer este traspaso de papeles y roles entre ambas partes. En este tutorial vamos a ver con un projecto de ejemplo como hacer esta migración de una cuenta a otra comprobando que el proyecto queda igual que antes sin pérdida de información.

Para más información sobre la herramienta Pivotal Tracker podeís visitar el siguiente tutorial de Alejandro Pérez, “Gestión de proyectos ágiles con Pivotal Tracker”


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.5

3. Cuentas vs. usuarios

Lo primero que tenemos que tener en cuenta son las diferencias existentes entre dos conceptos cuya frontera puede parecer muy fina: cuenta y usuario.

Un usuario es una persona física que se registra en la herramienta Pivotal Tracker con un email, password y una determinada información personal.

Una cuenta es una agrupación de proyectos en los que pueden participar ciertos usuarios de la herramienta. Es decir, una cuenta tiene proyectos y un proyecto tiene usuarios. Con este concepto claro parece obvio que lo que no tenemos que hacer es borrar una cuenta ya que los proyectos asociados a esa cuenta se borrarán también y perderemos toda su información.


4. Sólo un propietario

En Pivotal Tracker sólo puede existir un miembro de la cuenta con el rol de propietario (owner) por cada proyecto. El rol de owner corresponde al creador inicial de la cuenta. Este miembro es el que gestionará lo relativo a pagos, subscripciones, etc. Por lo demás tendrá los mismos privilegios que el rol de administrador (admin).


5. Creamos proyecto de ejemplo

Lo primero que vamos a hacer es crear un proyecto de ejemplo con algunas historias y acciones realizadas por un usuario en concreto, ALFONSOBLANCO.

Panel principal del proyecto de ejemplo

Panel principal del proyecto de ejemplo


6. Preparamos la migración

Tras tener el proyecto listo para la migración vamos a añadir un nuevo miembro a nuestro proyecto con el rol de administrador (admin). Para ello nos vamos al menu contextual de la derecha bajo el usuario y accedemos a la opción de “Accounts”.

Menú de usuario

Menú de usuario

Ahora pulsamos sobre el botón de “Manage account” y nos vamos a la sección de “Account members”.

Proyectos de la cuenta

Proyectos de la cuenta

Una vez allí pulsamos en el botón de “Add member” para añadir un nuevo miembro a la cuenta de ALFONSOBLANCO. Si tenemos otro usuario ponemos su username o su email y le asignamos el rol de admin para la cuenta. Se enviará de forma automática un email al nuevo usuario para confirmar dicha acción y tras esta confirmación podremos ver el estado de los miembros de la cuenta ALFONSOBLANCO.

Miembros de la cuenta

Miembros de la cuenta

Añadimos un nuevo miembro a la cuenta

Añadimos un nuevo miembro a la cuenta

Ahora nos vamos al menu de “Actions” del nuevo miembro dado de alta en nuestra cuenta y le añadimos al “Proyecto A” con el rol de owner. Aquí hay que volver a señalar la diferencia entre los roles de los miembros de la cuenta y los roles de cada miembro sobre los proyectos ya que son cosas distintas.

Miembros de la cuenta. El nuevo estará en gris hasta que confirme

Miembros de la cuenta. El nuevo estará en gris hasta que confirme

Añadimos el miembro al Proyecto A como "owner"

Añadimos el miembro al Proyecto A como “owner”

En este momento se volverá a mandar un email al nuevo usuario para confirmar dicha acción y tras dicha confirmación ya podrá entrar a gestionar el “Proyecto A” con el rol de admin.


7. Terminamos la migración

En este punto tenemos dos miembros en la cuenta, uno con rol owner y otro con rol admin. Ambos tienen el rol owner a nivel de proyecto. Es decir, los dos usuarios ahora son idénticos a nivel de privilegios por lo que sólo quedará terminar la migración desvinculando el usuario antiguo del proyecto y dejando al nuevo usuario como owner de la cuenta.

Desde la página de miembros del proyecto pulsamos sobre el enlace “Remove” del antiguo usuario para desvincularlo del “Proyecto A”. Tras confirmarlo ya no podrá ver el “Proyecto A”.

Borramos del proyecto el antiguo usuario con email alfonsobc@gmail.com

Borramos del proyecto el antiguo usuario con email alfonsobc@gmail.com

Ahora tenemos que logarnos con el nuevo usuario desde donde podremos gestionar el último paso de la migración. Vemos que tenemos permisos de administrador para el “Proyecto A”. Accedemos a los “Settings” del proyecto y pulsamos sobre el botón de “Change account” donde nos aparecerá un desplegable con los dos miembros owners de la cuenta. Seleccionamos el nuevo usuario y guardamos.

Dashboard del nuevo usuario

Dashboard del nuevo usuario

Cambios el owner

Cambios el owner

Tras esto tendremos en nuestra cuenta el “Proyecto A” con sólo un miembro con el rol owner.


8. Conclusiones

Tutorial muy sencillo pero a la vez de gran utilidad si se está usando la herramienta Pivotal Tracker para la gestión de proyectos ágiles. Espero que este tutorial os haya sido de ayuda. Un saludo.

Alfonso Blanco Criado

ablanco@autentia.com

Introducción a test parametrizados con JUnit 4

$
0
0

En este tutorial vamos a ver cómo se pueden emplear las capacidades de JUnit 4 para crear tests con asserts que se obtienen de un conjunto de datos.

Índice de contenidos


1. Introducción

En ocasiones en nuestros test realizan múltiples comprobaciones dentro de un mismo test, simplemente para probar diferentes casos lo cual nos lleva a repetir código dentro de nuestros test, por ejemplo:

MultiplyTest.java
package es.autentia.adictosaltrabajo.test;

public class MultiplyTest {
	@Test
    public void givenTwoNumbersShouldBeMultiplyResult() {
        Assert.assertEquals(4, 2*2);
        Assert.assertEquals(6, 3*2);
        Assert.assertEquals(5, 5*1);
        Assert.assertEquals(10, 5*2);
    }

}

Como podemos comprobar en este caso, puede ser tedioso y repetitivo comprobar todos los casos, ante esto, JUnit nos proporciona la característica de test parametrizados.


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 X El Capitan Versión 10.11.2
  • Entorno de desarrollo: ItelliJ Idea Comunity
  • JUnit 4.12

3. Qué son y cómo funcionan los test parametrizados en JUnit

Para construir test parametrizados, JUnit, utiliza un custom runner que es Parametrized, que nos permite definir los parámetros de varias ejecuciones de un solo test, veamos cómo funciona:

MultiplyTest.java
package es.autentia.adictosaltrabajo.test;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class MultiplyTest {
    @Parameters
    public static Iterable data(){
        return Arrays.asList(new Object[][]{
                {4,2,2},{6,3,2},{5,5,1},{10,5,2}
        });
    }

    private int multiplierOne;
    private int expected;
    private int multiplierTwo;

    public MultiplyTest(int expected, int multiplierOne, int multiplierTwo) {
        this.multiplierOne = multiplierOne;
        this.expected = expected;
        this.multiplierTwo = multiplierTwo;
    }

    @Test
    public void givenTwoNumbersShouldBeMultiplyResult(){
        Assert.assertEquals(expected,multiplierOne*multiplierTwo);
    }
}

¿Bien?¿Alguna duda? Imagino que sí, así que vayamos por partes. Como vemos al principio tenemos la linea:

@RunWith(Parameterized.class)

En esta línea lo que indicamos es que vamos a utilizar el runner de Parameterized, que se encargará de ejecutar el test las veces necesarias dependiendo del número de parámetros configurado. Cosa que vamos a hacer en el siguiente bloque:

@Parameters

La anotación Parameters indica cual es el método que nos va a devolver el conjunto de parámetros a utilizar por el runner. En nuestro ejemplo, un colección de arrays de objetos, que se utilizarán para construir el test cada vez.

Por supuesto ahora lo que necesitamos es un constructor que permita ser inicializado con los objetos que tenemos en cada elemento de la colección

Finalmente, se ejecutará el test como siempre hemos hecho utilizando los datos que hemos recogido en el constructor.


4. ¿Constructor? Inyectando los parámetros.

JUnit, también nos permite en vez de utilizar el constructor, inyectar los parámetros en propiedades de la clase test mediante la anotación @Parameter, para ello, simplemente omitimos el constructor y anotamos nuestras propiedades con @Parameter (deberán ser propiedades públicas).

@Parameter(value = 0) //El value indica la posición en el array de objeto que se va a inyectar, en caso de no estar indicado el valor por defecto es 0.
   public int expedted;
   @Parameter(value = 2)
   public int multiplierTwo;

5. ¿Con qué caso está trabajando nuestro test?

Para poder identificar de una manera sencilla qué test se está ejecutando en cada momento, JUnit no da la posibilidad de de dar un nombre a la ejecución utilizando la propiedad “name” de la anotación @Parameters, por ejemplo:

@Parameters(name = "{index}: a*b --> {1}*{2} = {0}")

Donde {index} nos indica la posición del parámetro actual y {0}, {1} , etc… nos indica el valor del parámetro dentro del array de objetos que estamos pasando a la ejecución del test. En nuestro ejemplo esto nos daría una salida tal que:

resultado_test


6. Conclusiones

Como hemos podido comprobar en este sencillo tutorial, JUnit, nos proporciona una variedad notable de herramientas para facilitar nuestros test, en este caso con la incursión de un runner que nos ayudará a evitar los test repetitivos con parámetros.


7. Referencias

Iniciación al framework web Django

$
0
0

En este tutorial veremos cómo dar nuestros primeros pasos en el framework web Django con Python. Crearemos una aplicación básica, con la cual trabajemos con la mayor parte de los elementos que nos proporciona el framework

Índice de contenidos


1. Introducción

Django es un framework web escrito en Python, creado en base a la arquitectura MVC. Crearemos una aplicación básica de ejemplo, en la cual mostraremos una serie de cursos disponibles y el profesor que impartirá el curso.


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
  • Python 2.7.x
  • Django 1.9.1

3. Instalación de dependencias

La version “El Capitán” de OSX viene con Python instalado por defecto, pero no con la herramienta “pip”, un gestor de paquetes que simplifica mucho la tediosa tarea de tener que instalarlos a mano. Según la fuente de la instalación de Python, “pip” puede venir instalado de serie o no. El instalador para OSX disponible en https://www.python.org/ lo instala de serie, y en Linux variará según la distribución. En caso de no tenerlo instalado, basta con ejecutar el comando:

sudo easy_install pip

Para la creación del esqueleto del proyecto utilizaremos una herramienta de Python llamada “virtualenv” que nos permite crear entornos virtuales para cada proyecto, de forma que se puedan mantener distintas versiones de una dependencia para distintos proyectos sin que interfieran los unos con los otros. Para instalar la herramienta “virtualenv” en caso de que aun no la tengamos instalada ejecutamos el siguiente comando:

sudo pip install virtualenv

Una vez instalada, crearemos un directorio en un directorio de nuestro sistema en el que almacenar nuestro proyecto. En su interior, utilizaremos el comando:

virtualenv env

El cual nos creará un directorio llamado “env” donde python guardará las distintas dependencias que vayamos incluyendo en nuestro proyecto. Para activar el entorno virtual, utilizaremos

source env/bin/activate

Una ve activado el entorno virtual, haremos uso de “pip” para instalar las distintas dependencias que necesitaremos para nuestro proyecto. Al haber activado un entorno virtual estas dependencias se almacenarán en el directorio “env”. Vamos a instalar Django para este entorno:

pip install django

Con esto instalaremos la última versión de Django que haya disponible, si quisiéramos instalar una versión en concreto se le puede indicar a pip de la siguiente forma:

pip install <paquete>==<version>

4. Creación del proyecto

Una vez instalado Django, crearemos el esqueleto del proyecto, al cual llamaré “cursosproject” usando el comando (cuidado con el “.” al final del comando):

django-admin startproject cursosproject .

Con esto django creará el esqueleto mínimo de un proyecto de Django que por defecto utilizará SQLite como base de datos con la siguiente estructura:

.
├── cursosproject
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── env
│   └── ...
└── manage.py

Los dos ficheros mas interesantes de momento son el fichero settings.py y el fichero urls.py.

En el fichero settings.py se configura todo lo necesario para que Django funcione. Existen un monton de opciones disponibles (https://docs.djangoproject.com/en/1.9/ref/settings/), y ademas podemos crear nuestras propias variables de configuración.

El fichero urls.py actua como router y configura como responderá python a las distintas peticiones.

Para probar que todo ha ido bien podemos ejecutar el comando

python manage.py runserver

Y deberíamos ser capaces de ver lo siguiente en el navegador

django funciona


5. Creación de aplicaciones

La mejor forma de trabajar con Python es mediante la creación de aplicaciones. De esta forma nos permite la creación de distintas funcionalidades que luego podremos integrar a nuestro proyecto.

Para crear una aplicación, utilizaremos el comando:

django-admin startapp cursos

Django nos creará un directorio con la siguiente estructura

cursos
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py

Por el momento nos centraremos en los ficheros models.py y tests.py. En el fichero models.py definiremos nuestro modelo de datos para nuestra aplicación. El el fichero tests.py crearemos nuestros tests unitarios.

Una vez creada nuestra aplicación, debemos indicarle a Django que la utilice añadiendola a la lista de aplicaciones instalada dentro del fichero settings.py del proyecto.

INSTALLED_APPS = [
    …,
    'cursos'
]

6. Creación de los modelos de datos

Vamos a crear un modelo sencillo para nuestra aplicación.

from django.db import models

class Curso(models.Model):
	activo = models.BooleanField()
	titulo = models.CharField(max_length=50)
	num_horas = models.PositiveIntegerField()
	profesor = models.ForeignKey('Profesor', on_delete=models.CASCADE)

	BASICO = 'B'
	INTERMEDIO = 'I'
	AVANZADO = 'A'
	NIVELES = (
		(BASICO, 'Basico'),
		(INTERMEDIO, 'Intermedio'),
		(AVANZADO, 'Avanzado'),
	)
	nivel = models.CharField(max_length=1,
							 choices=NIVELES,
							 default=BASICO)

	def __unicode__(self):
		return "Curso " + self.titulo

class Profesor(models.Model):
	nombre = models.CharField(max_length=20)
	apellidos = models.CharField(max_length=40)

	def __unicode__(self):
		return self.nombre + " " + self.apellidos

Django se encargará de convertir cada una de las subclases de la clase Models a una tabla de la base de datos. Cada atributo que definamos dentro de estas clases se transformará en una columna de dicha tabla. Existen una gran cantidad de campos predefinidos en Django (https://docs.djangoproject.com/en/1.9/ref/models/fields/), y ademas estos pueden ser extendidos según nuestras necesidades. También existe una gran cantidad de métodos que se pueden sobreescribir para modificar el comportamiento por defecto, como por ejemplo el método “__unicode__()” que nos devuelve la descripción del objeto.

Una vez hemos terminado de definir nuestro modelo de datos, necesitamos indicarle a Django que construya o altere el esquema de la base de datos, para hacerlo utilizamos primero el siguiente comando:

python manage.py makemigrations

Este comando genera el código SQL necesario para modificar el esquema de la base de datos que irá almacenando en el directorio “migrations” dentro del directorio de la aplicación y que podremos revisar. La primera vez que lo ejecutemos se dedicará básicamente a construir las tablas, pero si modificamos a posteri el modelo de lo que se encargará será de generar el código necesario para modificar el esquema. Hay que tener cuidado en ciertos casos ya que si por ejemplo, renombramos una subclase de “Models”, puede suceder que al generar las migraciones este decida eliminar la tabla X y construir una tabla Y. En cualquier caso Django suele avisar cuando va a hacer cosas arriesgadas de este tipo.

Una vez realizadas las migraciones, para indicarle a Django que realice la modificación del esquema utilizaremos el comando:

python manage.py migrate

7. Plantillas y creación de vistas

Ahora que tenemos nuestro modelo de datos, es hora de mostrar la información. Existen múltiples formas de devolver la información que deseamos con Django, pero quizá una de las más interesantes es el mecanismo de templates que proporciona Django.

El sistema de templates combina código HTML con sintaxis propia de Django, de tal forma que cuando Django reemplaza su sintaxis propia con lo que corresponda genera el código HTML (https://docs.djangoproject.com/es/1.9/ref/templates/builtins/). Para empezar a utilizar el sistema de plantillas crearemos nuestra plantilla en la siguiente estructura de directorios dentro del directorio de nuestra aplicación.

cursos
├── ...
├── templates
│   └── cursos
│       └── listado_cursos.html

Aunque no es estrictamente necesaria esta estructura de directorios, si que es la que recomiendan los desarrolladores de Django. Añadiremos el siguiente código a la plantilla:

<head>
<style>
table, th, td {
	border: 1px solid black;
	border-collapse: collapse;
}
th, td {
	padding: 5px;
}
</style>
</head>
<body>
	<h1>Listado de cursos</h1>
	<table style="width: 100%">
		<thead>
			<td>Titulo</td>
			<td>Horas</td>
			<td>Nivel</td>
			<td>Profesor</td>
		</thead>
		{% for curso in cursos %}
		<tr>
			<td>{{curso.titulo}}</td>
			<td>{{curso.num_horas}}</td>
			<td>{{curso.nivel}}</td>
			<td>{{curso.profesor.nombre}} {{curso.profesor.apellidos}}</td>
		</tr>
	{% endfor %}
	</table>
</body>

El siguiente paso es hacer que nuestra vista renderice la plantilla. Para ello añadiremos el siguiente código a nuestro fichero “views.py”

from django.shortcuts import render
from cursos.models import Curso

def lista_cursos(request):
	lista_cursos = Curso.objects.all()
	return render(request, 'cursos/listado_cursos.html', {"cursos": lista_cursos})

Con esto le diremos a Django que consulte en base de datos todos los objetos de la clase curso. La función render se encarga de combinar el template con el diccionario, de tal forma que en el template se encargará de reemplazar las ocurrencias de “cursos” por el contenido de “lista_cursos“.


8. Rutas

En este punto la aplicación ya cuenta con un modelo de datos básico y una vista con la que mostrar los datos. Ahora necesitamos que cuando una URL concreta, ya sea root o por ejemplo “/cursos”, nos muestre la página que nosotros queramos. Para ello emplearemos el fichero “urls.py” que se encuentra en el directorio “cursosproject” y añadiremos lo siguiente.

from django.conf.urls import url
from django.contrib import admin
from cursos import views as cursos_views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', cursos_views.lista_cursos)
]

Las URL están formadas por una expresión regular, y la función que queremos que Django ejecute para responder a la petición cuando solicita una URL que coincide con dicha expresión regular.

Podemos comprobar en nuestro navegador que accediendo a la URL raíz nos muestra la respuesta a nuestra petición.

Se puede ver que Django nos devuelve nuestra plantilla pero, como aún no hemos introducido ningún dato en nuestra base de datos la tabla está vacía. Podemos refactorizar el código de nuestra plantilla para mejorarla un poco.

<head>
<style>
table, th, td {
	border: 1px solid black;
	border-collapse: collapse;
}
th, td {
	padding: 5px;
}
</style>
</head>
<body>
	<h1>Listado de cursos</h1>
	{% if cursos %}
		<table style="width: 100%">
			<thead>
				<td>Titulo</td>
				<td>Horas</td>
				<td>Nivel</td>
				<td>Profesor</td>
			</thead>
		{% for curso in cursos %}
			<tr>
				<td>{{curso.titulo}}</td>
				<td>{{curso.num_horas}}</td>
				<td>{{curso.nivel}}</td>
				<td>{{curso.profesor.nombre}} {{curso.profesor.apellidos}}</td>
			</tr>
		{% endfor %}
		</table>
	{% else %}
		<p> Actualmente no hay cursos disponibles </p>
	{% endif %}
</body>

De esta forma ahora mostrará un mensaje algo más detallado cuando la tabla de cursos aún esté vacía.

Texto alternativo cuando aun no hay cursos registrados


9. Interfaz de administración

Ahora que tenemos los elementos básicos de nuestra aplicación, solo nos hacen falta datos. Existen muchas maneras de generar esos datos, como por ejemplo usando directamente SQL sobre la base de datos, creando una vista con un formulario que permita introducir esos datos… Pero una de las formas más simples y a la vez uno de los componentes más potentes de Django es su interfaz de administración.

La interfaz de administración nos permite realizar CRUD sobre los modelos que hayamos creado con tan solo registrarlos. Lo mejor de todo es que Django se encarga de crear la interfaz pertinente mediante introspección de los modelos.

Por defecto, cuando se genera un proyecto con la ayuda de “django-admin”, este se genera con la interfaz de administración habilitada. Si le echamos un vistazo a nuestro “urls.py”, se puede ver que existe la siguiente ruta:

url(r'^admin/', admin.site.urls)

Si accedemos a dicha URL veremos la siguiente página:

Login

Para poder utilizar la interfaz de administración, antes debemos crear un superusuario. Para hacerlo utilizaremos el comando.

python manage.py createsuperuser

Que nos pedirá un nombre de usuario y una contraseña. Una vez creado, nos podremos loguear en la interfaz de administración.

Django admin dashboard

Si queremos que se muestre en otro idioma distinto a la lengua de Shakespeare, podemos editar el fichero “settings.py”:

LANGUAGE_CODE = 'es-es'

Si queremos poder manipular los objetos de nuestro modelo, debemos indicarle a Django que los utilice en nuestra interfaz de administración. Para ello debemos modificar el fichero “admin.py” que se encuentra en el directorio de nuestra aplicación y añadir el siguiente código.

from django.contrib import admin
from cursos.models import Curso, Profesor

admin.site.register(Curso)
admin.site.register(Profesor)

Si volvemos a acceder a la interfaz de administración, podemos ver que aparece un nuevo apartado llamado “Cursos” (el nombre de nuestra aplicación):

Django admin dashboard 2

Ahora podemos crear, editar y eliminar objetos cómodamente desde la interfaz de administración.

Añadir profesor

Añadir curso

Curso añadido

Una vez hemos añadido contenido veremos que la vista que creamos empieza a mostrar el contenido:

Lista cursos


10. Conclusiones

Django es un framework muy completo y muy interesante. En este tutorial solo hemos rascado un poco de la superficie, pero existen múltiples temas a tratar.

Sin duda uno de sus puntos fuertes es su interfaz de administración, de una forma rápida, sencilla y cómoda se puede añadir a nuestra aplicación web la posibilidad de editar el contenido con un usuario autenticado con unos permisos específicos.

También es ampliamente personalizable, tanto los modelos, la etiquetas de las vistas e incluso la interfaz de administración puede ser personalizada según nuestras propias necesidades, ya sea creando nuevos tipos de datos, nuevos widgets, nuevos tags.

El propio framework también contiene numerosas aplicaciones y middleware para funcionalidades como la autenticación de usuarios, internacionalización, sesiones, y un largo etcétera. Y por si esto fuera poco, también es fácil incorporar aplicaciones o middleware creados por la comunidad, como por ejemplo “django rest framework” para facilitar el desarrollo de una API REST.

Como desventajas, hay que decir que la curva de aprendizaje es un poco dura, sobre todo al principio. Otra posible desventaja es el hecho de que Django está planteado para trabajar con sistemas de gestión de bases de datos relacionales, algo que hay que tener en cuenta en función de la aplicación a desarrollar.

TDD: Outside-In vs Inside-Out

$
0
0

En este tutorial comentamos dos corrientes a la hora de enfocar el diseño guiado por test o TDD.

Ya hemos hablado con anterioridad de la importancia de realizar nuestros desarrollos siguiendo el mantra del TDD (como se puede comprobar aquí), conocemos por tanto sus virtudes, las garantías que nos ofrece y que, algunos, consideramos imprescindibles en nuestro día a día. Sin embargo, una faceta menos conocida del TDD son sus distintas escuelas o maneras de enfocar los problemas a la hora de resolverlos.

En este tutorial trataremos de presentar esas escuelas y sus principales características para así añadirlas a nuestra “caja de herramientas”.

Índice de contenidos


1. Introducción

Dos son las principales vertientes que vamos a presentar en este tutorial: la Escuela Clásica y la escuela de Londres.

La escuela clásica toma su nombre por representar el concepto original definido en libros como “Test-driven Development By Example” de Kent Beck, y que se distingue por enfatizar la práctica de la “triangulación“, que explicaremos más en profundidad con posterioridad.

La escuela de Londres toma su nombre debido a que tiene su base, principalmente, en el libro “Growing Object Oriented Software Guide By Test” de Stephen Freeman y Nat Pryce, enfatizando los roles, responsabilidades e interacciones.

A continuación bajaremos más al detalle en cada una de ellas:


2. Escuela Clásica (Inside-Out)

La escuela clásica se distingue por centrarse en la verificación del estado de los objetos, siendo por ello imprescindible que el contexto de los test siempre deba estar formado por “objetos reales“, configurados previamente. Para la correcta generación de estos contextos se pueden crear clases que nos ayuden, por ejemplo a través de los denominados “MotherObjects” o implementaciones del patrón “Factory“.

La existencia previa de estos “objetos reales“, implica que el diseño de nuestra solución irá creciendo poco a poco desde la base hasta la funcionalidad final. De ahí el sobrenombre de técnica Inside-out.

Otra de las características más distintivas de este enfoque es la triangulación. El concepto se basa en obtener 2 o más casos similares antes de proceder con la implementación de una solución genérica.

Test_TDD_triangulacion-02

Pongamos por caso que, en una aplicación que estamos desarrollando, necesitamos tratar con múltiples proveedores de información, todos ellos similares, aunque cada uno de ellos mantiene una serie de particularidades. Los pasos serían los siguientes:

  • El primer paso sería realizar la implementación concreta para un único proveedor, obviando el hecho de que existen más proveedores.
  • El segundo paso sería realizar la implementación para el segundo proveedor.
  • Por último, realizaríamos la generalización de la solución para los N casos puesto que en los dos casos implementados deberían poder observarse puntos en los que realmente pueden establecerse generalizaciones. En caso de no observar las suficientes similitudes, siempre se puede continuar implementando soluciones hasta observar las similitudes.

3. Escuela de Londres (Outside-In)

La escuela de Londres toma un enfoque distinto, centrándose en verificar que el comportamiento de los objetos es el esperado. Dado que este el objetivo final, verificar las correctas interacciones entre objetos, y no el estado en sí mismo de los objetos, mediante este enfoque podremos ahorrarnos todo el trabajo con los objetos reales (creación y mantenimiento) sustituyéndolos por dobles de test.

Siendo este el caso, podremos empezar por la funcionalidad final que se necesita, implementando poco a poco toda la estructura que dé soporte a dicha funcionalidad. Por esto, esta técnica recibe el nombre de Outside-in, en este caso, desarrollando las funcionalidades desde el exterior hacia dentro.

También es importante remarcar el uso de la técnica del doble bucle:

  • Comenzaremos por un test de aceptación que falle, lo que nos guiará al bucle interno.
  • En el bucle interno, que representa la metodología de trabajo TDD (“Red-Green-Refactor”), implementaremos la lógica de nuestra solución.
  • Realizaremos las iteraciones necesarias de este bucle interno hasta conseguir pasar el test de aceptación.
Test_TDD_bucle

4. Conclusiones

Motivaciones o gustos personales aparte, ambas técnicas son increíblemente útiles a la hora de desarrollar, siempre que se utilicen en el momento adecuado.

¿Cuándo guiarnos por la Escuela Clásica?

  • Cuando se quiera verificar el estado de los objetos.
  • Cuando las colaboraciones entre objetos son sencillas.
  • Si no se quiere acoplar la implementación a los test.
  • Si se prefiere no pensar en la implementación mientras se escriben los test.

¿Cuándo guiarnos por la Escuela de Londres?

  • Cuando se quiera verificar el comportamiento de los objetos.
  • Cuando las colaboraciones entre objetos sean complejas.
  • Si no importa acoplar la implementación a los test.
  • Si se prefiere pensar en la implementación mientras se escriben los test.

5. Referencias

Cómo reducir el código repetitivo con Lombok

$
0
0

Hoy voy a presentar Lombok, una solución que nos permite evitar tener que escribir una y otra vez ese código repetitivo, que tantas veces tenemos que implementar en nuestras clases. Getters y Setters se reducen a una única línea de código.

Índice de contenidos


1. Introducción

En nuestras clases Java hay mucho código que se repite una y otra vez: constructores, getters y setters. Métodos que quedan definidos una vez que dicha clase ha concretado sus propiedades, y que salvo ajustes menores, serán siempre sota, caballo y rey. Con Lombok se hace eso mismo: definimos las propiedades y nos despreocupamos. Con unas pocas anotaciones cubrimos casi todos los casos que se pueden dar.


2. Entorno

Para escribir este tutorial he usado el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15″ (2,5 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS X El Capitán
  • Entorno de desarrollo: Eclipse Mars .1
  • Atom 1.4.0 para redactar este post

3. Un primer acercamiento

Primero veamos qué puede hacer Lombok por nosotros y luego veamos cómo conseguirlo.

Anotación @Data de Lombok

En la imagen anterior se observa como apenas hemos declarado la clase y unas pocas propiedades. Sin embargo, en la ventana de outline de Eclipse se ven todos los getters, setters, el constructor y algún otro método como equals, canEquals, hashCode y toString. Y lo único que se ha hecho es anotar la clase con

@Data
y añadir su correspondiente import. Está claro que para que esto ocurra, nuestro IDE debe reconocer lombok, y por otro lado, nuestro proyecto debe importar la librería.

4. Instalando Lombok

Desde la web del proyecto Lombok se puede descargar un jar, que debería poder instalarse con un simple doble click. En el caso de que esto no funcionara habría que recurrir a la línea de comandos y ejecutar:

java -jar lombok.jar

El instalador intentará descubrir los IDEs que tienes instalados y preguntará en cuales quieres instalar Lombok. En este equipo me detectó el Eclipse. En caso de que no encuentre ninguno de los que soporta, se puede indicar dónde está instalado el IDE en el que queremos añadir lombok.

Lombok, durante el proceso de instalación en nuestro IDE

También hay que añadir el JAR al proyecto donde lo queramos usar. Lo podemos añadir como dependencias de maven en el pom.xml

<dependency>
	     <groupId>org.projectlombok</groupId>
	     <artifactId>lombok</artifactId>
	     <version>1.16.6</version>
	     <scope>provided</scope>
	</dependency>

5. Algunas características de Lombok

Ya hemos visto lo que puede hacer la anotación

@Data
por nosotros. Pero hay muchas otras. Algunas de las más reseñables son:
  • @Getter
  • @Setter
  • @NoArgsConstructor
  • @AllArgsConstructor
  • @Builder
  • @Log

La mayoría son autoexplicativas y no merece la pena profundizar en ellas. Baste con un par de experimentaciones rápidas para darse cuenta de que lo que hacen es lo que dice la anotación. En este sentido, lombok es muy intuitivo.

Lombok con @NoArgsConstructor y @AllArgsConstructor

En el ejemplo anterior, hemos anotado la clase con

@NoArgsConstructor
y
@AllArgsConstructor
, y comprobamos que el resultado en el outline, es que tenemos dos constructores: uno sin argumentos y otro con todos los argumentos.

A nivel de propiedades, hemos anotado una con

@Setter
y otra con
@Getter
, y automáticamente nos genera dichos métodos para esas propiedades. Estas anotaciones se pueden usar a nivel de clase, y generaría esos métodos para todas las propiedades. Muy fácil.

6. Implementación del patrón Builder

Lombok también nos permite implementar el patrón Builder de forma efectiva en nuestro código, únicamente debemos anotar la clase con

@Builder
, y automáticamente nos creará el método público build y el objeto estático Builder con todos sus atributos y métodos.

Para quien no lo conozca, en el siguiente enlace se muestra el funcionamiento del patrón Builder de forma muy didáctica.

Patrón Builder implementado por Lombok

De esta forma siempre podremos hacer:

private Usuario cliente
                = new Usuario.UsuarioBuilder()
                                    .id(102)
                                    .nombre("Alejandro")
                                    .primerApellido("Magno")
                                    .mail("correo@ejemplo.com")
                                    .build();

7. Anotaciones para el tratamiento de logs

Hay seis posibles anotaciones de clase para el tratamientos de logs que crean la variable estática, privada y final log, dependiendo del sistema con el que se desee registrar las trazas.

Distintas anotaciones para el tratamientos de logs

  • @CommonsLog
  • @Log
  • @Log4j
  • @Log4j2
  • @Slf4j
  • @XSlf4j

Se pueden ver explicadas con más detalle en la página oficial

El resultado es que de forma sencilla y efectiva podemos tener control sobre nuestros POJOs con unas simples anotaciones, permitiéndonos aislarnos de la “morralla” habitual vinculada al tratamiento de este tipo de clases, que en el caso de modelos de datos con numerosos atributos, pueden ser muy tediosos de gestionar, aunque los IDEs actuales nos ayuden a ello.


8. Referencias

Viewing all 989 articles
Browse latest View live