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

Introducción a los Microservicios

$
0
0
En este tutorial realizaremos una introducción al concepto de microservicios respondiendo a preguntas como: ¿Qué son?¿Cuáles son sus ventajas/desventajas?¿Cuándo utilizarlos?

Índice de contenidos

1. Introducción

Los microservicios están de moda porque muchas compañías grandes y tecnológicamente sofisticadas lo usan. Amazon, eBay, Twitter, Netflix, PayPal, son algunos ejemplos exitosos de microservicios. ¿qué es un microservicio? ¿Qué beneficios aporta? ¿Cuál es el peaje a pagar para disfrutar de dichos beneficios?

En este tutorial vamos a realizar una introducción teórica para dar respuesta a estas preguntas, además de dar unas pequeñas pautas sobre cuándo es recomendable hacer uso de ellos.

2. ¿Qué son?

Tradicionalmente los sistemas grandes y complejos se han abordado mediante grandes sistemas mono-proceso (monolitos), donde el mantenimiento y evolución del mismo era complejo. En contraposición, los microservicios son un patrón de diseño software a nivel arquitectónico que implementa servicios mediante la colaboración otros servicios más pequeños y autónomos.

Microservicios

Cada microservicio debe dar solución a un área de negocio concreta, abstrayendo al resto del sistema de los detalles concretos de la implementación, favoreciendo su independencia, el mantenimiento y la evolución de cada uno de ellos. Al trabajar cada uno sobre su propio proceso, permiten ser desplegados sin perjudicar a los demás, favoreciendo así la escalabilidad, y se comunican a través de mecanismos ligeros, como pueden ser peticiones HTTP, entre las diferentes APIs de cada microservicio.

3. ¿Cuáles son sus ventajas?

Gracias, en gran medida, a la independencia de los microservicios se pueden enumerar las siguientes ventajas:

  • La escalabilidad es más eficiente.
  • La capacidad de prueba de cada microservicio es mayor.
  • Potencia la diversidad tecnológica: utilizar múltiples lenguajes así como diferentes stacks tecnológicos.
  • Permite que los desarrollos sean independientes y en paralelo.
  • Facilita el mantenimiento.
  • Permite despliegues independientes.
  • Aumenta la tolerancia a fallos.

4. ¿Cuáles son sus desventajas?

Las desventajas de los microservicios son, en gran parte, debidas a la introducción del concepto de distribución. A continuación las listamos:

  • Aumentará la complejidad en la gestión de la configuración. Cada microservicio necesitará su porpio canal de construcción y despliegue (Maven, Git, Jenkins, etc..).
  • También aumentará la complejidad para mantener la transaccionalidad de cada operación.
  • Normalmente será necesario disponer de un servicio para la localización de servicios.
  • El rendimiento se puede ver afectado debido a saturaciones de la red o a procesos de (de)serialización.
  • Monitorizar el sistema será más complejo.
  • Será más difícil gestionar los errores correctamente.

5. ¿Cuándo utilizarlos?

Como hemos visto el enfoque de microservicios exige por parte de los desarrolladores esfuerzos no solo en la implementación del sistema sino también en la monitorización de los microservicios, el tratamiento de errores, la consistencia de los datos, adem´s de otros factores asociados a los sistemas distribuidos.

Todo esto supone un coste de esfuerzo/tiempo añadido al proyecto que debe ser sopesado debidamente.

La decisión de si utilizar o no este tipo de diseño a la hora de construir nuestro sistema se fundamenta básicamente en el nivel complejidad que va a alcanzar.

Si el nivel de complejidad del sistema es bajo no se aconseja hacer uso de este tipo de diseños. Debido al esfuerzo que supone configurar y mantener el entorno de desarrollo, una decisión en favor de los mismos nunca será rentable hablando en términos de productividad del equipo. En este tipo de situaciones el equipo será más productivo trabajando sobre una aplicación monolítica.

A partir de cierto nivel de complejidad del sistema, las ventajas que ofrecen los microservicios harán viable el esfuerzo necesario que la productividad del equipo sea mayor.

6. Conclusiones

A lo largo de este tutorial se ha definido que es una arquitectura de microservicios, cuales son sus ventajas/desventajas y se ha dado alguna pista para saber cuando utilizar dicha arquitectura.

En posteriores tutoriales se mostrarán detalles de implementación específicos como implementación de microservicios con SpringBoot, securización de microservicios, etc…

7. Referencias


Logback en Spring Boot

$
0
0
Este tutorial explica cómo personalizar la generación de logs en Spring Boot para poder explotarlos de manera cómoda y efectiva.

Índice de contenidos

1. Introducción

Spring Boot además de un framework de desarrollo de aplicaciones Java orientadas a microservicios, es un entorno de ejecución completo y autocontenido para nuestras aplicaciones.

Cuando ejecutamos una aplicación Spring Boot, las opciones de logging por defecto pueden ser suficientes en un entorno de desarrollo, pero obviamente en producción se quedan cortas y es necesario controlar qué log se genera y cómo y dónde se guarda.

El objetivo es ser más efectivos a la hora de detectar, identificar y diagnosticar problemas en las aplicaciones que están en un entorno de pruebas, preproducción o producción.

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 Yosemite 10.10
  • Entorno de desarrollo: Eclipse MARS.2
  • Spring Boot 1.3.3
  • Apache Maven 3.3.9

3. Logging en Spring Boot

Spring Boot es muy flexible a la hora de configurar cómo se gestionan los logs. Esto era de esperar porque a buen seguro cualquier aplicación va a depender de un buen número de librerías, que a su vez probablemente dependerán de diferentes utilidades de logging.

Afortunadamente, en la actualidad no hay (o no suele haber) dependencias directas con utilidades de logging concretas, sino con fachadas (patrón facade o adapter según el caso) que abstraen la implementación concreta del logging, permitiendo además intercambiar implementaciones con bastante facilidad.

Las fachadas más conocidas son commons-logging y slf4j (Simple Logging Facade for Java). El propio Spring utiliza internamente commons-logging para sus mensajes de log, pero muchas otras librerías dependen de slf4j.

Las implementaciones más populares son Log4j, Java Util Logging y Logback.

La configuración por defecto de Spring Boot canaliza a Logback los mensajes enviados a través de las fachadas commons-logging y slf4j siempre y cuando Logback esté presente. Esto último ocurre de manera automática si nuestra aplicación depende de spring-boot-starter-web o spring-boot-starter-logging (POMs de Maven).

Cuando el mensaje llega a Logback pueden ocurrir dos cosas (siempre hablando del comportamiento por defecto):

  • Si la propiedad logging.file tiene algún valor, se enviarán los mensajes al fichero referenciado por ese valor, ya sea un nombre simple, una ruta relativa o una ruta absoluta. El fichero se limpiará cuando alcance un tamaño de 10 MB, pero antes se archivará una copia en el mismo directorio y con el mismo nombre añadiendo un sufijo numérico.
  • Si la propiedad logging.file no tiene valor, los mensajes se enviarán a la salida estándar. Además, si la salida estándar es el terminal, los mensajes estarán coloreados.

4. Personalización de Logback

4.1. Ficheros implicados

Logback en Spring Boot busca configuraciones en los ficheros logback.xml o logback-spring.xml en las raíces del classpath de la aplicación. Según esto, la manera más sencilla de personalizar Logback es definir un fichero logback-spring.xml en el directorio src/main/resources de cualquiera de los proyectos que componen nuestra aplicación Spring Boot. Más adelante se explica qué contiene este fichero.

Para facilitar la personalización, Spring Boot proporciona bajo la ruta org/springframework/boot/logging/logback/ varios ficheros include:

  • defaults.xml Establece colores de la salida por terminal, patrones de mensajes, conversores de excepciones y filtros para algunos paquetes de infraestructura. Salvo que tengas una muy buena razón, este fichero debe incluirse siempre.
  • console-appender.xml Define las características de la salida por terminal (las salidas se llaman appenders en la jerga de logging). Inclúyelo si necesitas sacar mensajes por terminal.
  • file-appender.xml Define una salida a ficheros que rotan cuando alcanzan un tamaño de 10 MB, sin límite de número de ficheros. La ruta del fichero debe establecerse en la propiedad logging.file, normalmente en el fichero src/main/resources/application.properties de alguno de los proyectos de nuestra aplicación.
  • base.xml Este fichero incluye a los tres anteriores y establece que todos los mensajes se envíen tanto a la terminal como al fichero.
La idea es que estos ficheros se importen en logback-spring.xml según los vayamos necesitando.

4.2. Contenidos de los ficheros

Logback distingue el fichero de configuración principal del resto de ficheros incluídos. El primero se identifica porque su elemento raíz es , mientras que en los segundos el elemento raíz es .

Por ejemplo, para definir una configuración que sea idéntica a la que proporciona Spring Boot bastaría con tener el siguiente fichero logback-spring.xml:

logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 <include resource="org/springframework/boot/logging/logback/base.xml" />
</configuration>
Si nos gusta esa configuración pero queremos añadir un filtro de mensajes, podríamos hacer lo siguiente:
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 <include resource="org/springframework/boot/logging/logback/base.xml" />
 <logger name="org.springframework" level="INFO"/>
</configuration>
Aquí es necesario hacer un pequeño inciso. En las utilidades de logging, el filtrado de mensajes se basa principalmente en dos criterios:
  • Por un lado, el origen del mensaje, o sea, quién ha generado el mensaje, que suele ser una clase Java identificada por su ruta completa (paquete + nombre).
  • Por otro lado, el nivel del mensaje, es decir, cómo de importante es. Consiste en un valor enumerado que va de menos importante a más importante: TRACE, DEBUG, INFO, WARN y ERROR.

Así, los filtros más básicos se definen con el elemento , que representa a los mensajes emitidos por una clase o paquete Java.

En el ejemplo anterior, el filtro sólo deja pasar los mensajes emitidos por el paquete y subpaquetes de org.springframework con un nivel de importancia INFO o superior (WARN y ERROR).

Logback tiene muchísimas posibilidades de configuración, pero eso cae fuera del objetivo de este tutorial.

4.3. Configuración por perfil

Uno de los aspectos más interesantes de Logback en Spring Boot es la posibilidad de definir configuraciones de logging diferentes según los perfiles activos en la aplicación.

Los perfiles activos se identifican con la propiedad spring.profiles.active, por ejemplo en application.properties o en los parámetros al arrancar la máquina virtual Java.

Para segregar la configuración de logging según perfil se utiliza el elemento .

Por ejemplo, si deseo que el perfil development tenga salida por consola y el perfil production salida a fichero, utilizaría esta configuración:

logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>

<configuration>
 <!-- Ya no nos sirve base.xml porque queremos separar consola y fichero seg&uacute;n el perfil -->
 <include resource="org/springframework/boot/logging/logback/defaults.xml" />

 <!-- Este filtro se aplica a todos los perfiles -->
 <logger name="org.springframework" level="INFO"/>

 <springProfile name="<b>development</b>">
 <!-- El perfil "development" define una salida por terminal llamada "CONSOLE" -->
 <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
 <!-- Todos los mensajes ser&aacute;n enviados a la salida llamada "CONSOLE".
 El filtro por defecto establece un nivel mínimo de "DEBUG". -->
 <root level="DEBUG">
 <appender-ref ref="CONSOLE" />
 </root>
 </springProfile>

 <springProfile name="<b>production</b>">
 <!-- El perfil "production" define una salida a fichero llamada "FILE".
 La ruta se indica en logging.file o logging.path; si no, se guardar&aacute; en /tmp/spring.log -->
 <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-{java.io.tmpdir:-/tmp}}/}spring.log}"/>
 <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
 <!-- Todos los mensajes se enviar&aacute;n a la salida llamada "FILE".
 El filtro por defecto establece un nivel mínimo de "INFO". -->
 <root level="INFO">
 <appender-ref ref="FILE" />
 </root>
 </springProfile>
</configuration>

5. Un caso práctico

En un proyecto real se tomó la decisión de configurar dos salidas de mensajes: por consola para el perfil development y por fichero para el resto de perfiles. Los ficheros rotan diariamente (es decir, cada día se archiva el log generado) y se quieren conservar los registros de los últimos 20 días.
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>

<configuration>
 <!-- Default config and other properties -->
 <include resource="org/springframework/boot/logging/logback/defaults.xml" />
 <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-{java.io.tmpdir:-/tmp}}/}spring.log}"/>

 <!-- Profile independent loggers -->
 <logger name="com.autentia.myapplication" level="DEBUG"/>
 <logger name="org.springframework" level="INFO"/>

 <!-- Development profile -->
 <springProfile name="<b>development</b>">
 <appender name="APPENDER" class="ch.qos.logback.core.ConsoleAppender">
 <encoder>
 <pattern>${CONSOLE_LOG_PATTERN}</pattern>
 <charset>utf8</charset>
 </encoder>
 </appender>

 <logger name="org.springframework.security" level="DEBUG"/>
 </springProfile>

 <!-- Staging/production profile -->
 <springProfile name="<b>!development</b>">
 <appender name="APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
 <encoder>
 <pattern>${FILE_LOG_PATTERN}</pattern>
 </encoder>
 <file>${LOG_FILE}</file>
 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
 <fileNamePattern>${LOG_FILE}.%d</fileNamePattern>
 <maxHistory>20</maxHistory>
 </rollingPolicy>
 </appender>
 </springProfile>

 <!-- Root logger -->
 <root level="INFO">
 <appender-ref ref="APPENDER" />
 </root>
</configuration>

6. Conclusiones

Spring Boot como plataforma para el desarrollo de microservicios y aplicaciones autocontenidas en Java proporciona una gran cantidad de servicios y obliga a seguir buenas prácticas que aceleran el desarrollo y mejoran la calidad. Tratándose de un elemento básico en cualquier aplicación, el logging no podía ser menos como ha quedado explicado aquí.

Por su parte, Logback ha sido desarrollado por el creador original de Log4j tras muchos años de experiencia acumulados. Sus posibilidades y facilidad de personalización son enormes; no dudes en echarle un vistazo al manual.

Antes de terminar, una buena práctica fundamental: nunca dependas de una librería de logging concreta, utiliza slf4j o commons-logging como APIs de logging en tus aplicaciones.

7. Referencias

Introducción a Protractor

$
0
0

En este tutorial vamos a ver cómo realizar pruebas end2end en aplicaciones implementadas con AngularJS con la herramienta Protractor.

Í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
  • Atom 1.5.3
  • Protractor 3.1.1

2. Introducción

Protractor es la herramienta que la gente de Google nos ofrece para la implementación de pruebas E2E de aplicaciones AngularJS, y recalco lo de aplicaciones AngularJS porque solo funciona con este tipo de aplicaciones.

Ya sabemos de otros tutoriales que AngularJS nos permite crear tests de cada elemento de la aplicación como vimos en este tutorial de Miguel Arlandy.

En esta ocasión vamos a hablar de pruebas máquina a máquina o test de aceptación, que se caracterizan por ser tests que realizan las verificaciones una vez la aplicación esta desplegada, gracias a Selenium, del que también hemos hablado en varias ocasiones.


3. Vamos al lío

Lo primero que tenemos que hacer es instalar la herramienta, que se distribuye como paquete npm.

$> npm install -g protractor

Esto nos va a instalar de forma global las herramientas protractor y webdriver-manager. Esta última nos va a ayudar a levantar una instancia de Selenium Server para lo que tendremos que ejecutar.

$> webdriver-manager update

Y una vez se haya descargado todos los binarios necesarios arrancamos la instancia ejecutando:

$> webdriver-manager start

Por defecto, nos levantará el servidor de Selenium en la URL: http://localhost:4444/wd/hub

El siguiente paso es configurar Protractor para lo que creamos el fichero e2e.conf.js en la raíz del proyecto con el siguiente contenido:

exports.config = {
  seleniumAddress: 'http://127.0.0.1:4444/wd/hub',
  multiCapabilities: [{
    browserName: 'firefox'
  }, {
    browserName: 'chrome'
  }],
  specs: ['test/e2e/**/*.spec.js'],
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000
  }
}

En este fichero le estamos indicando cuál es la URL del servidor de Selenium que vamos a utilizar , en qué navegadores vamos a ejecutar, en este caso, Firefox y Chrome, dónde van a residir nuestros tests dentro de la estructura del proyecto y la configuración de Jasmine, para que muestre colores y establecer un timeout máximo de espera.

Ahora creamos nuestro primer tests, para lo cual necesitamos que la aplicación que vamos a probar este corriendo. En este enlace podéis descargar la aplicación de ejemplo con todo el contenido https://raguilera@bitbucket.org/raguilera/ng-protractor.git. Ahora ejecutáis dentro del directorio del proyecto:

$> bower install
$> live-server

Y ya tendréis la aplicación corriendo. En caso de error aseguraos que tenéis instalado bower y/o live-server, en caso contrario:

$> npm install -g bower
$> npm install -g live-server

La aplicación se levantará por defecto si no está ocupado en el puerto 8080 y simplemente consiste en un pequeño formulario que solo admite números y permite sumarlos, mostrando el resultado con un filtro que le añade tres asteriscos por delante y por detrás.

Ahora en vuestro proyecto creáis el fichero test/first.spec.js con el siguiente contenido:

describe('E2E: main page', function(){

  it('should exists calc div', function(){
    browser.get('http://127.0.0.1:8080/')
    var ele = by.id('calc')
    expect(browser.isElementPresent(ele)).toBe(true)
    element(by.model('calc.a')).sendKeys(2)
    element(by.model('calc.b')).sendKeys(4)
    element(by.id('suma')).click()
    expect(element(by.binding('calc.c')).getText()).toEqual('***6***')
  })

})

En este test primero conectamos con la URL donde está desplegada la aplicación y comprobamos que el div con id ‘calc’ existe. Acto seguido añadimos los valores deseados a los elementos del formulario por su propiedad ng-model y forzamos un click en el botón con identificador ‘suma’. Inmediatamente podemos evaluar el valor de la variable {{calc.c}} gracias a la función binding y con el matcher toEqual podemos ver que efectivamente es el resultado esperado.

Ahora ejecutamos en el terminal el comando protractor pasándole como argumento el fichero de configuración:

$> protractor e2e.conf.js

Esto hace que se ejecuten todos los tests que tengamos, lo que hará que se abran los navegadores especificados y que el resultado de los mismos nos lo muestre por pantalla.


4. Conclusiones

Como habéis podido ver resulta realmente sencillo hacer test end2end con protractor aunque recordad que en ningún caso deben sustituir a los test unitarios, ya que estos test son los más frágiles y requieren de mucho más mantenimiento.

Cualquier duda o sugerencia en la zona de comentarios.

Paquetes para Atom enfocados a diseño

$
0
0

Paquetes de Atom que aumentan su funcionalidad como editor para diseño gráfico.

Antes de nada os pondré en situación, no me considero un front developer. Los paquetes que muestro en este tutorial, son simplemente los que a mí me han ayudado en mis escarceos como diseñador.

Para los que no conozcáis Atom, se trata de un editor de código de Github (software libre) que, entre otras cosas, se le pueden instalar paquetes que le otorgan “superpoderes”. La integración de estos paquetes es muy sencillo, ya que, está integrado con Github.

Una vez descargado Atom, vamos a vitaminarlo. La instalación de estos paquetes se realiza a través de la sección de “ajustes > paquetes” de la propia herramienta. Simplemente con realizar la búsqueda de los paquetes que enumerare es posible instalarlos.

Color Picker

https://github.com/thomaslindstrom/color-picker Captura de pantalla 2016-03-16 a las 15.09.07

Este paquete permite cambiar colores de una forma muy sencilla. Muestra los colores en varios formatos: HEX, HEXa, RGB, RGBa, HSL, HSLa, HSV, HSVa, VEC3 and VEC4. Dispone de un acceso directo que podemos personalizar.


Less Compiler

https://github.com/Azakur4/less-compiler

En este caso me centraré en LESS, ya que es lo que habitualmente manejo. El paquete LESS Compiler es muy completo para poder compilar y crear CSS. Si tu estructura de archivos requiere importaciones por la dependencia de estos, LESS Compiler está pensado para ello. Simplemente con añadir una línea a tus archivos LESS, cada vez que hagas cambios en archivos dependientes estos se actualizarán y se actualizará el CSS. Además cuenta con la opción de crear un CSS minimizado y con notificación de errores, muy adecuado para saber que clase te has dejado sin cerrar ;).

Open in a Browser

https://atom.io/packages/open-in-browser

Se trata de una herramienta que Atom no trae por defecto y que este paquete incluye de forma muy elegante. La instalación de este paquete te permitirá lo que su nombre mismo indica: abrirlo en un navegador. A pesar de que hay varios paquetes que cumplen esta función, creo que este es el que menos funcionalidades tiene, pero que tiene las funcionalidades necesarias. Hace una cosa pero la hace bien.

Pigments

https://github.com/abe33/atom-pigments Captura de pantalla 2016-03-16 a las 15.18.04

Un paquete que le viene como anillo al dedo al color Picker. Con Pigments serás capaz de ver el color que estás indicando a través de código de forma visual, esto es, te pintará el color encima de código. Muy útil para poder ver la evolución de colores entre degradados y tonalidades de un mismo color.

En sus ajustes podrémos fijar el tiempo de respuesta del color fijado por código, autocompletar el color o incluir la posibilidad de completado de colores en base a variables, muy usado en LESS. También ofrece la posibilidad de unir colores duplicados.

Auto ident

https://atom.io/packages/auto-indent

Tener un código ordenado e identado hace que sea más fácil de leer. Este paquete te lo hará más sencillo. Carece de ajustes.

Auto close

https://atom.io/packages/autoclose-html

Al igual que el paquete anterior, hace lo que dice. Son dos paquetes que bien podrían estar incluidos entre los paquetes de la versión inicial. Tiempo al tiempo.

Google Guava: colecciones con programación funcional en Java 6/7

$
0
0

No hace falta disponer de Java8 para que puedas usar la Programación Funcional a las colecciones. Usando la librería Guava de Google es posible. En este tutorial te lo contamos.

0.Índice de contenidos


1.Introducción

Como seguro que ya sabes, una de las novedades de Java8 es la posibilidad de emplear la programación funcional para tratar con, entre otras cosas, listas en Java.

(Si quieres saber más sobre Java8 te recomiendo que te consultes este tutorial sobre lambdas en Java8, o mucho mejor, te apuntes a nuestro curso de Java 8)

Pero no todo el mundo tiene la posiblidad de trabajar sobre un proyecto Java8. Lo más normal es que los que toman las decisiones no se arriesguen a utilizar la última versión de Java (mal hecho) y nos tengamos que conformar con versiones anteriores como Java6 o Java7 que no tienen estas capacidades. Si estás en esta situacion no te preocupes, hay salida para todo :).

Google puso hace tiempo a disposición de la comunidad una librería de utilidades para Java llamada Guava (sí, G(J)ava), que permite, entre otras cosas, recorrer y tratar Colecciones de una forma más o menos funcional: sin usar bucles para entendernos. Y claro está, es compatible con Java6 y Java7, asi que no tienes excusa para pasarte al lado funcional, aunque sea de forma un poco artificial.

2. Entorno

Para realizar este tutorial se ha empleado el siguiente entorno de desarrollo:

  • Hardware: Mac Book Pro 15″ Intel Core i7 2,8 GHz, 16 GB RAM.
  • Sistema Operativo: Mac OS X El Capitán.
  • Software:Java: 1.7.43; maven 3 y Git si quieres probar los ejemplos.

No obstante, cualquier máquina en la que ejecutes con Java es suficiente.

3. Instalación

Vamos a partir de un proyecto de Maven, así que simplemente deberemos expresar la dependencia de Guava en el pom.xml

Para encontrar la dependencia vamos a MavenCentral y buscamos la librería de Guava. Nos indicará este artefacto: http://mvnrepository.com/artifact/com.google.guava/guava. En el momento de escribir este tutorial, la última versión era la 19, así que la elegimos. El código que habrá que incluir en el pom.xml en el apartado de dependencies es el siguiente:

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>19.0</version>
</dependency>

Para esta demostración también vamos a usar test, asi que aprovechamos y buscamos la dependencia de JUnit4 para poder insertarla. La puedes sacar de aquí:http://mvnrepository.com/artifact/junit/junit/4.12. No obstante todo el código que se expone en este tutorial lo puedes encontrar en un repositorio de mi cuenta de GitHub: https://github.com/4lberto/guavaLists por si quieres experimentar por tu cuenta.

4. Conceptos iniciales: Predicados y Funciones

Antes de ponernos a recorrer listas tenemos que conocer dos conceptos básicos que maneja esta librería y que le permiten modelar el paradigma de la programación funcional: los predicados y las funciones.

Básicamente lo que hace Guava es tomar una lista y aplicar o bien predicados o bien funciones que nosotros hemos diseñado. Es como una encapsulación de operación básica que se aplica a cada elemento de la lista para ver que cumple una condición o transformarlo.

Si estás familiarizado con los patrones de diseño de la programación orientada a objetos, podríamos decir que es una especie de patrón strategy en el cual se extrae la operación a una clase específica (la función o procedimiento), que un iterador especial se dedica a aplicar a cada elemento.

Mejor lo vemos en código:

4.1. Predicados

Un predicado es un método que recibe un objeto por parámetro y devuelve un valor de tipo boolean (true o false). Así de simple.

Esta es su interfaz:

Interface Predicate boolean apply(T)

Y esto es un predicado que dado un número entero dice si es mayor que 10:

final Predicate<Integer> predicateGreaterThan10 = new Predicate<Integer>() {
	@Override
	public boolean apply(final Integer input) {
		return input > 10;
	}
};

¿Para qué se utilizan los predicados?

Se aplican a los elementos de una colección o algo que sea iterable, y decidir si cumplen una condición o no. De este modo se puede decidir sobre si se incluyen en el resultado de la operación o no. Por ejemplo:

  • Si un número es par.
  • Si un cadena de texto tiene más de una longitud determinada.
  • Si un objeto tiene un parámetro que cumple la condición.
  • Si un elemento es nulo.
  • Si un elemento está en una lista

Así, si tenemos una lista con objetos, podremos filtrarlos por el cumplimiento o no de un atributo. Nos será muy útil para descartar elementos y crear listas nuevas que solamente tengan los objetos que nos interesan.

4.2. Funciones

Las funciones son métodos que toman un objeto de entrada y producen un objeto de salida.

Interface Function T apply(F)

Esto es una función que dada una cadena de texto, elimina el último caracter, devolviendo una nueva cadena de texto a la salida:

final Function functionRemoveLastChar = new Function() {
	@Override
	public String apply(final String input) {
		return input.substring(0, input.length() - 1);
	}
};

¿Para qué se utilizan las funciones?

Son las operaciones de transformación de una lista de objetos en otra lista de objetos. Así se puede por ejemplo:

  • Sumar una determinada cantidad a cada elemento.
  • Simplificar un objeto para convertir la lista en una lista de identificadores de objetos o un campo que nos interse para hacer una operación posterior.
  • Completar objetos o convertirlos en otros, generando una lista nueva más completa.
  • O incluso si la salida de la función es un Boolean, tendremos una especie de predicado.

Una lista a la que se le aplica una función genera una lista nueva de igual longitud. Ya podremos aplicarles predicados u otras funciones especiales para recortarlas si fuera necesario.

Más adelante veremos las funciones y los predicados en más detalle:

5. Aplicando Programación Funcional a las colecciones: FluentIterable

Ya sabemos de qué van los predicados y las funciones. Ahora sólo queda aplicarlas.

Hay diferentes formas de hacerlo en Guava, empleando los diferentes mecanismos como las clases de utilidades com.google.common.collect.Lists o com.google.common.collect.Collections2 por ejemplo.

Pero para facilitar el manejo en Guava tenemos com.google.common.collect.FluentIterable<E> que nos da una interfaz común a los métodos más usados (realmente en su código luego hace referencia a estas librerías). Además, esta clase está preparada para operar como una Interfaz fluida para que el código sea más legible

A FluenIterable simplemente se le indica a través del método .from() el iterable sobre el que se va a operar, y a continuación las operaciones que se le aplican, siempre siguiente su API fluida. En su propia documentación nos indican un ejemplo:

FluentIterable
.from(database.getClientList())
.filter(activeInLastMonth())
.transform(Functions.toStringFunction())
.limit(10)
.toList();

Vamos a ver con unos test de ejemplo cómo podemos usarla. Antes unas advertencias:

* Puedes descargar los ejemplos y jugar con ellos desde: https://github.com/4lberto/guavaLists.

** Antes de cada test hay una lista de Strings generada con clases de utilidad llamada listOfStrings.

*** Uso test para las pruebas por simpleza a la hora de probar los ejemplos y comprobrar resultado. Obviamente el código explicado se puede emplear en cualquier clase de Java.

5.1. Dada una lista desordenada de Strings, devuelve una lista con los elementos que comienza por “A”

@Test
public void selectStarsWithA_UsingGuava() {

	// given
	final Predicate<? super String> selectStartsWithA = new Predicate<String>() {
		@Override
		public boolean apply(final String word) {
			return word.toLowerCase().startsWith("a");
		}
	};
	
	// when
	final List<String> stringsStartsWithA = FluentIterable.from(listOfStrings).filter(selectStartsWithA).toList();

	// then
	assertTrue(stringsStartsWithA.get(0).toLowerCase().startsWith("a"));
	assertTrue(stringsStartsWithA.get(stringsStartsWithA.size() - 1).toLowerCase().startsWith("a"));
}

Como podrás ver se ha generado un Predicado que se aplica a cada elemento de entrada, que es de tipo String. El predicado devuelve un boolean, que será true si la palabra empieza por “a” y falso en otro caso.

final Predicate<? super String> selectStartsWithA = new Predicate<String>() {
	@Override
	public boolean apply(final String word) {
		return word.toLowerCase().startsWith("a");
	}
};

A continuación, y aquí está la potencia de la programación funcional, se aplica sobre la entrada, que es “listOfStrings” y que ha sido generada anteriormente (bajate los fuentes para verlo).

Gracias a la API Fluida de FluenIterable, cualquiera puede ver leyendo en inglés lo que hace:

final List<String> stringsStartsWithA = FluentIterable.from(listOfStrings).filter(selectStartsWithA).toList();

Es decir, partiendo de listOfStrings, aplica un filtro, que será el predicado y la salida la convierte en una lista, que además será de tipo com.google.common.collect.ImmutableList, que es un tipo de lista inmutable de alto rendimiento (mejor para el sincronismo y la programación funcional).

Luego aplico unos asserts para comprobar que da el resultado que quiero. Como he dicho antes, uso test para que cuadre en la demo, pero se podría usar en cualquier lugar.

5.2. Devolver la primera palabra de una lista que empiece por la letra “j”

Gracias a la API fluida de FluentIterable podemos hacerlo fácilmente. Simplemente creamos un predicado y lo aplicamos. A la salida en vez de una lista le decimos que nos devuelva un único elemento con .get

@Test
public void firstWithJ_UsingGuava() {
	final Predicate<? super String> startsWithJPredicate = new Predicate<String>() {
		@Override
		public boolean apply(final String word) {
			return word.toLowerCase().startsWith("j");
		}
	};

	final String firstStartsWithJ = FluentIterable.from(listOfStrings).firstMatch(startsWithJPredicate).get();

	assertTrue(firstStartsWithJ.toLowerCase().startsWith("j"));
}

Una de las ventajas de tener un predicado (o función) fuera del recorrido de los elementos, es poder reutilizar el código del predicado. Imagina que puedes tener una factoría de predicados o funciones separada y utilizarla a lo largo de tu código en cualquier lugar.

5.3. Añadir un sufijo a cada palabra de una lista

Simplemente creando una función que dado un String nos devuelva el String más el sufijo podemos hacerlo fácilmente. Luego la función se aplica a una lista de Strings que tenemos preparada:

@Test
public void addSufix_UsingGuava() {

	//given
	final Function<String, String> addSufix = new Function<String, String>() {
		@Override
		public String apply(final String word) {
			return word.concat(SUFIX);
		}
	};
	
	//when
	final List<String> listOfStringsWithSuffix = FluentIterable.from(listOfStrings).transform(addSufix).toList();

	//then
	assertTrue(listOfStringsWithSuffix.get(0).endsWith(SUFIX));
}

Fijate que ahora se ha empleado la operacion .transform(function) para que transforme los elementos de la lista, y la salida se ha pedido que sea una lista con .toList().

5.4. Ordernar una lista de palabras

No puede ser más fácil y expresivo con la operación .toSortedList pasando por parámetro otra utilidad de Guava, en este caso Ordering.natural(), que devuelve un Comparable preparado para devolver los elementos ordenados de modo natural (así no tenemos que implementar una clase anónima al vuelo)

@Test
public void order_UsingGuava() {
	//given

	//when
	final List<String> result = FluentIterable.from(listOfStrings).toSortedList(Ordering.natural());

	//then
	assertTrue(result.get(0).compareTo(result.get(listOfStrings.size() - 1)) < 0);
}

Como el resultado es de tipo ImmutableList<E> entonces podemos aplicar otro método de la API fluida de este miembros de Guava, como por ejemplo subList(0,10) para seleccionar los 10 primeros elementos.

final List result = FluentIterable.from(listOfStrings).toSortedList(Ordering.natural()).subList(0, 10);

Si lo hubiéramos hecho dentro de un fluentIterable, podríamos haber empleado .limit(10) para obtener sólo 10 elementos de la lista.

5.5. Predicado parametrizado en clase anónima: Intersección de listas

Quizá estos ejemplos te hayan contentado, pero puede que haya una cosa que no te termine de cuadrar: los predicados sólo admiten elementos de la lista como entrada, y las funciones igual. Si queremos complicar las condiciones admitiendo otros parámetros… ¿cómo podemos hacerlo?

En este ejemplo queremos que, dada una lista de Strings, nos devuelva otra lista con aquellos elementos que están en otra lista. Para ello definiremos el predicado para que tenga acceso a las variables del scope del método en el que se define.

De este modo, dentro de la definición del predicado y de su método apply, haremos uso de variables que no están definidas de forma explícita en el predicado. Mejor con código:

@Test
public void getElementsMatchingInAList_UsingGuava() {

	// given
	final List<String> listOne = Arrays.asList("Uno", "dos", "tres", "cuatro");
	final List<String> listTwo = Arrays.asList("dos", "tres");

	final Predicate<String> predicado = new Predicate<String>() {
		@Override
		public boolean apply(final String input) {
			return listTwo.contains(input);
		}
	};

	// when
	final List<String> listMatching = FluentIterable.from(listOne).filter(predicado).toList();

	// then
	assertEquals(listMatching.size(), 2);
	assertEquals(listMatching.get(0), "dos");
	assertEquals(listMatching.get(1), "tres");
}

En este ejemplo, dentro del predicado se hace referencia a listTwo, que es una variable a nivel de método del test. Al definirse la clase anónima que implementa la interfaz Predicate de Guava, por la especificación del scope del Java Language Specification, se puede hacer uso de ello.

@Override
public boolean apply(final String input) {
	return listTwo.contains(input);
}

De nuevo, gracias a FluenIterable de Guava, aplicando un predicado con filter y pidiendo que el resultado sea una lista con toList, se simplifica bastante el matching de ambas listas para encontrarr la intersección.

Pero siempre hay algo que objetar. Quizá eres de la idea inicial que comentaba más atrás se crear unas funciones/predicados que fuesen más o menos reutilizables. En este caso, nuestro predicado únicamente se puede emplear dentro de ese test, ya que tiene una dependencia clara de la lista anterior. Vamos a ver alguna alternativa.

5.6. Predicado parametrizado en nueva clase: Intersección de listas

Un predicado en Guava se trata simplemente de implementar la interfaz Predicate, asi que no hay nada que nos impida darle un poco más de funcionalidad con tal de que el apply siga funcionando tal y como figura en el contrato de la interfaz.

Ahora creamos una clase estática dentro de la clase de tests. No hace que falta que sea estática per se, sino que para simplificar no voy a crear un fichero Java nuevo, de modo que la introduzco dentro de la clase de test. Sácala a un fichero nuevo si crees conveniente:

public static class PredicateMatchingList implements Predicate<String> {

	public PredicateMatchingList(final List<String> listWithElementsToBeMatched) {
		super();
		this.listWithElementsToBeMatched = listWithElementsToBeMatched;
	}

	private final List<String> listWithElementsToBeMatched;

	@Override
	public boolean apply(final String input) {
		return listWithElementsToBeMatched.contains(input);
	}
}

Ásí tenemos nuestro nuevo predicado creado llamado PredicateMatchingList. Así podremos reutilizarlo en otros desarrollos de iterable. El test ahora quedaría así:

@Test
public void getElementsMatchingInAListPredicateOutSide_UsingGuava() {

	// given
	final List<String> listOne = Arrays.asList("Uno", "dos", "tres", "cuatro");
	final List<String> listTwo = Arrays.asList("dos", "tres");

	final Predicate<String> predicado = new PredicateMatchingList(listTwo);

	// when
	final List<String> listMatching = FluentIterable.from(listOne).filter(predicado).toList();

	// then
	assertEquals(listMatching.size(), 2);
	assertEquals(listMatching.get(0), "dos");
	assertEquals(listMatching.get(1), "tres");
}

Aún podemos darle una vuelta de tuerca más generando predicados con un método Factory, más acorde con las buenas prácticas de desarrollo.

Por cierto, en la clase com.google.common.base.Predicates hay factorías para este tipo de predicados, como por ejemplo in(Collection<? extends T> target), que nos fabricaría un predicado similar al anterior.

Simplemente cambiaríamos la forma de obtener el predicado por:

final Predicate<String> predicado = Predicates.in(listTwo);

5.7. Predicado parametrizado desde un método Factory: números mayor que el parámetro.

Este ejemplo es similar al anterior. Queremos un predicado que nos diga si el elemento sobre el que se aplica es mayor que un número dado por parámetro. Ya sabes las otras dos alternativas anterior. Vamos ahora con un método fáctory que nos genere el predicado.

public static Predicate<Integer> getPredicateBiggerThanFactory(final int number) {
	return new Predicate<Integer>() {
		@Override
		public boolean apply(final Integer input) {
			return input > number;
		}
	};
}

Si te fijas bien, es una especie de mezcla entre crear una clase con un constructor con el parámetro y otra de tomar la variable del scope: el valor “number” se aplica dentro del nuevo predicado generado porque forma parte del scope del método factory.

Aplicarlo es tan fácil como esperamos:

@Test
public void predicateBasedOnFactory_UsingGuava() {
	// given
	final Predicate<Integer> predicateBiggerThan73 = getPredicateBiggerThanFactory(73);

	final int numberBiggerThan73 = 89;

	// when
	final boolean isBiggerThan73 = predicateBiggerThan73.apply(numberBiggerThan73);

	// then
	assertTrue(isBiggerThan73);
}

Por cierto, estos ejemplos para predicados también son perfectamente aplicables a las funciones de Guava (y a cualquier cosa que implemente una interfaz, claro) :).

5.8. Suma de precios de los 2 coches más potentes

Seguro que has oído hablar de map-reduce en bases de datos NoSQL, que al final aplican funciones a listados de datos.

  • Map: se encarga de seleccionar y transformar los elementos buscados. Por ejemplo ordena los coches por potencia y devuelve los dos primeros de la lista.
  • Reduce: realiza una operación de “resumen”, como por ejemplo la media o una suma. En nuestro caso será la suma del precio.

Ya sabes que lo que estamos haciendo hasta ahora se corresponde con el Map, pero nos falta una operación que haga “reduce”, es decir, que recorra los elementos de una lista y haga una operación acumulativa que devuelva un único resultado. ¿Cómo se hace en Guava?.

La respuesta es… No se puede hacer en Guava la operacion reduce a menos que la implementemos nosotros.

Y la forma de implementarlo nosotros es a través de un tradicional bucle que recorra el resultado del Map. O si somos más sofisticados hagamos nuestra propia operación reduce que tome una lista y una operación de dos elementos como parámetro y lo aplique en parejas: el 1 con el 2, el resultado al 3, y así sucesivamente.

Así pues, nosotros aplicaremos un bucle para conocer el precio acumulado de los coches más potentes:

@Test
public void getSumOfPriceOfTheMostPowerfullCarsGiven_UsingGuava() {

	// given
	final Comparator<Car> comparatorHp = new Comparator<Car>() {
		@Override
		public int compare(final Car o1, final Car o2) {
			return o2.getHp() - o1.getHp();
		}
	};

	// when
	final List<Car> sortedListOfCars = FluentIterable.from(listOfCars).toSortedList(comparatorHp).subList(0, 2);

	int accumulatedPrice = 0;
	for (final Car coche : listOfCars) {
		accumulatedPrice += coche.getPrice();
	}

	// then
	assertTrue(accumulatedPrice > (listOfCars.get(0).getPrice() + listOfCars.get(1).getPrice()));
}

Al menos Guava nos ha hecho la vida más sencilla para ordenar y sacar los dos coches más potentes… pero no es una librería tan potente como la parte de streams de Java8 :(

.

6. Extra: Predicados y Funciones extendidos

Como hemos visto, al final Guava, y la programación funcional, nos permite desacoplar en funciones y predicados la lógica que se aplica a los elementos, lo cual es una gran ventaja frente a los bucles.

En esta sección “extra” vamos a ver algunas curiosidades de las funciones y predicados que nos pueden ser de utilidad.

6.1. Extra Predicados

La clase com.google.common.base.Predicates contiene algunos predicados generales prefabricados, como por ejemplo:

6.1.1. Predicados básicos: true, false

Serán utilizables por ejemplo en operaciones de composición booleana de predicados. Así tenemos:

final Predicate<String;> predicateFalse = Predicates.alwaysFalse();
final Predicate<String;> predicateTrue = Predicates.alwaysTrue();

6.1.2. Predicado para filtar por clase

Si tenemos la necesidad de, dada una lista, quedarnos con los elementos que son de una clase determinada (o sus descendientes), podemos aplicar Predicates.assignableFrom.

En este ejemplo filtramos por clases que extiendan la clase java.lang.Number:

@Test
public void predicateAssignableClass_UsingGuava() {
	// given
	final Predicate<Class<?>> predicateFromNumber = Predicates.assignableFrom(Number.class);

	final Class<?> integerClass = Integer.class;
	final Class<?> stringClass = String.class;

	// when
	final boolean resultFromInteger = predicateFromNumber.apply(integerClass);
	final boolean resultFromString = predicateFromNumber.apply(stringClass);

	// then
	assertTrue(resultFromInteger);
	assertTrue(!resultFromString);
}

También existe el predicado Predicate.instanceOf para una clase exacta.

6.1.3. Composición de Predicados

Ya sabemos que un predicado se aplica a una clase y devuelve un boolean. Parece lógico que se puedan combinar predicados simples para generar predicados más complejos con operaciones booleanas de composición: or, and, not.

A continuación vemos un ejemplo en el que hay dos predicados: uno que evalúa que un Integer sea mayor que 10 y otro que evalúa que sea menor que 20. La combinación de ambos con una operación “and” debería darnos un predicado que fuese verdadero cuando el número proporcionado esté entre 10 y 20:

@Test
public void predicateOr_UsingGuava() {
	// given

	final Predicate<Integer> predicateGreaterThan10 = new Predicate<Integer>() {
		@Override
		public boolean apply(final Integer input) {
			return input > 10;
		}
	};

	final Predicate<Integer> predicateSmallerThan20 = new Predicate<Integer>() {
	@Override
		public boolean apply(final Integer input) {
			return input < 20;
		}
	};

	final Predicate<Integer> predicateBetween10and20 = Predicates.and(predicateGreaterThan10,
	predicateSmallerThan20);

	final Integer numberBetween10and20 = 15;
	final Integer numberBiggerThan20 = 25;
	final Integer numberSmallerThan10 = 5;

	// when
	final boolean resultExpectedOk = predicateBetween10and20.apply(numberBetween10and20);
	final boolean resultExpectedKo1 = predicateBetween10and20.apply(numberBiggerThan20);
	final boolean resultExpectedKo2 = predicateBetween10and20.apply(numberSmallerThan10);

	// then
	assertTrue(resultExpectedOk);
	assertTrue(!resultExpectedKo1);
	assertTrue(!resultExpectedKo2);
}

De igual modo existe la generación de predicados a partir de las operaciones “or” y “not”.

Simplemente ten en cuenta que se puede ampliar el campo de cosas que se pueden hacer con los predicados y por tanto con Guava.

6.2. Extra Funciones.

Igual que los predicados, existe com.google.common.base.Functions, que tiene métodos estáticos de factoría para crear funciones a partir de parámetros.

Como las funciones reciben una clase de entrada y dan otra clase de salida, no tienen esas operaciones booleanas de los Predicados, pero tienen alguna función interesante:

6.2.1. Composición de Funciones

Como en las matemáticas: si F(A)->B y F(B)->C, hay una composición de funciones que nos dará F(A)->F(C). Esto se puede hacer con el método estático compose(Function<B,C> g, Function<A,? extends B> f).

Se tengo una función que elimina el primer caracter de una cadena de texto y otra que elimina el último caracter, con la composición tendré una función que elimina el primer y el último caracter de una cadena de texto. Aquí vemos un ejemplo:

@Test
public void functionsComposition_UsingGuava() {

	// Given
	final Function<String, String> functionRemoveFirstChar = new Function<String, String>() {
		@Override
		public String apply(final String input) {
			return input.substring(1);
		}
	};

	final Function<String, String> functionRemoveLastChar = new Function<String, String>() {
		@Override
		public String apply(final String input) {
			return input.substring(0, input.length() - 1);
		}	
	};

	final Function<String, String> functionComposite = Functions.compose(functionRemoveFirstChar,	functionRemoveLastChar);

	final String aStringToRemoveExtrems = "String";
	final String expectedResult = "trin";

	// when
	final String result = functionComposite.apply(aStringToRemoveExtrems);

	// then
	assertEquals(expectedResult, result);
}

6.2.2. Función asignación a través de un Mapa

Finalmente vamos a ver una función de la factoría de com.google.common.base.Functions que me parce muy interesante: permite transformar el objeto de entrada en uno de salida, pero la regla de transformación está determinada en un mapa. En este mapa, las claves (keys) son los obejtos de entrada, y los valores (values) los de salida.

Para ello se emplea forMap(Map<K,? extends V> map, V defaultValue), que recibe el mapa de corrspondencias por parámetro y un valor en el caso de que no se encuentre la entrada.

En el siguiente ejemplo se transforman los códigos de locale (en, es, rus) en el idioma correspondiente (inglés, español, ruso)…

@Test
	public void functionForMap_UsingGuava() {
	// given
	final Map<String, String> languageMap = new HashMap<>();
	languageMap.put("es", "Español");
	languageMap.put("en", "Inglés");
	languageMap.put("ru", "Ruso");
	languageMap.put("it", "Italiano");

	final List<String> listOfLocale = Arrays.asList("ru", "es", "ch");
	final String defaultValue = "Unknow";

	final Function<String, String> getElementFunction = Functions.forMap(languageMap, defaultValue);

	// when
	final List<String> listOfIdiom = FluentIterable.from(listOfLocale).transform(getElementFunction).toList();

	// thenassertTrue(resultGreaterThan10);
	assertEquals(listOfLocale.size(), listOfIdiom.size());
	assertEquals(listOfIdiom.get(0), "Ruso");

	assertEquals(listOfIdiom.get(listOfIdiom.size() - 1), "Unknow");
}

6.2.3. Función desde un predicado

Finalmente como hemos ido viendo a lo largo de este tutorial, sabemos que un predicado es una función que devuelve un boolean, así que es posible crear una función a partir de un predicado con Functions.forPredicate. Muy sencillo el ejemplo:

@Test
public void functionSameAsPredicate_UsingGuava() {

	// given
	final Predicate<Integer> isGreaterThan10 = new Predicate<Integer>() {
		@Override
		public boolean apply(final Integer input) {
			return input > 10;
		}
	};

	final Function<Integer, Boolean> functionIsGreaterThan10 = Functions.forPredicate(isGreaterThan10);

	final Integer numberGreaterThan10 = 25;
	final Integer numberSmallerThan10 = 8;

	// when
	final boolean resultGreaterThan10 = functionIsGreaterThan10.apply(numberGreaterThan10);
	final boolean resultSmallerThan10 = functionIsGreaterThan10.apply(numberSmallerThan10);

	// then
	assertTrue(resultGreaterThan10);
	assertTrue(!resultSmallerThan10);
}

7. Conclusiones

La programación funcional está cambiando la forma de programar en los últimos años. Como consecuencia Java ha tenido que implementar este tipo de programación aplicada a streams y lambdas en la versión Java8. Desafortundamente no todo el mundo tiene la posibilidad de trabajar con Java8 y tiene que conformarse con Java6 o Java7. Para estos casos se puede emplear la librería Guava de Google.

Guava nos proporciona algunas utilidades para iterar sobre colecciones de objetos (listas, conjuntos…) de una forma similar a la programación funcional. Para ello utiliza una API fluida en la que se indican funciones y predicados que se aplican a los elementos de las colecciones para poder transformar y filtrar respectivamente. De este modo podemos desarrollar nuestro código de una forma similar a Java8 con streams y lambdas, aunque no en todo su esplendor, ya que tiene algunas lagunas como por ejemplo la operación reduce.

Comentando el libro ‘Creatividad S.A.’ de Ed Catmull

$
0
0

Hoy comparto mi comentario del libro Creatividad S.A., donde el presidente de Pixar y Disney Animation nos cuenta su experiencia al frente de estas empresas. Su discurso se centra en cómo crear, preservar y evolucionar la cultura creativa en una organización.

Desde hace un tiempo mi cabeza le viene dando vuelta a un tema: Creatividad. Este interés lo ha motivado una nueva rama dedicada a materializar ideas en productos novedosos que está creciendo en la empresa en la que trabajo, Autentia.

Por este motivo empecé a plantearme una serie de preguntas:

  • ¿De dónde surgen las ideas?
  • ¿Cómo crece una idea hasta convertirse en algo real?
  • ¿Cualquiera tiene la capacidad de ser creativo?
  • ¿Qué dificultades tiene que afrontar un proceso creativo?
  • ¿Cómo funcionan las empresas dedicadas a crear lo que no existe?

Para responder estas preguntas me estoy ayudando de los libros con los que me topo en el camino. El que aquí nos ocupa, Creatividad, S.A., Cómo llevar la inspiración hasta el infinito y más allá de Ed Catmull , nos permite conocer la manera en la que la empresa presidida por el autor, Pixar Animation, ha creado, mantenido y ha hecho evolucionar una cultura creativa que les ha llevado a ser la referencia en el mundo de la animación digital.

Hay muchos motivos por los que os podría recomendar este libro:

  • Se plasma la experiencia de Ed Catmull al frente de una empresa de éxito.
  • Expone las diferentes acciones que se llevan a cabo en Pixar para proteger la cultura creativa.
  • Nos avisa de que los problemas van a aparecer constantemente y que un gran porcentaje de ellos serán problemas ocultos difíciles o imposibles de predecir.
  • Habla del cambio, muchas veces visto como el enemigo cuando por el contrario le deberíamos tender la mano como si de un hermano se tratase.
  • Nos muestra cómo conseguir hacer realidad lo que no existe, a través de una idea que nace, que tras un enorme trabajo colectivo madura.
  • Y si tienes curiosidad por conocer la visión que tiene de Steve Jobs una persona que ha sido cercana a él encontrarás detalles interesantes en este libro.

Desde el punto de vista práctico, el objetivo principal del libro es mostrar al lector cómo crear una cultura creativa sostenible en una empresa para lograr la excelencia en los productos creados. No por esto debemos pensar que los destinatarios de estos consejos sean únicamente aquellos que ocupen puestos en la cúpula de las empresas, también es recomendable para cualquiera que tenga la capacidad de proponer cambios en su lugar de trabajo, así como a aquellos emprendedores que quieran recibir un poco de inspiración.

Intentaré resumir y generalizar los consejos y metodologías que el autor plasma en Creatividad S.A. Veréis que estas técnicas se pueden aplicar en cualquier contexto en el que el objetivo final sea crear un producto novedoso y con elevados niveles de calidad.

Cómo crear cultura creativa sostenible en la empresa

Idea que podemos definir como el objetivo fundamental del libro. Estas son algunas de las medidas que nos propone el autor si queremos construir una empresa con una cultura creativa sana:

  • Rodearse de gente con talento y con autodisciplina que sean capaces de formar parte de un entorno colaborativo y con objetivos comunes.
  • Promover la innovación y creatividad tecnológica.
  • Organizar la empresa en base a una estructura horizontal, cualquiera debería expresarse libremente independientemente de su posición en la empresa.
  • Fomentar el intercambio de información.
  • Disponer de instalaciones que promuevan la creatividad.
  • Ser capaces de asumir riesgos.
  • Estudiar los fallos cometidos.
  • Resolver los problemas en cuanto aparezcan, sin clasificarlos por importancia.
  • Formación interna, también en disciplinas no relacionadas con la labor profesional, de esta manera se consigue recuperar la mente de principiante.
  • Pilares básicos de esta cultura: honestidad, excelencia, comunicación, originalidad y autoafirmación.

Materializar ideas

Estos son algunos de los consejos y afirmaciones recogidos en este libro acerca de la manera en la que se debe proteger una idea creativa para que madure y se convierta en un producto de éxito:

  • La idea madura como resultado del trabajo duro.
  • Proteger la idea de quien pueda tirarla abajo antes de tiempo.
  • Mantener el equilibrio entre las necesidades creativas y comerciales. Es importante rentabilizar, pero si se racionaliza en exceso se resiente el producto final.
  • No perder la confianza de tu gente, es mejor tomar un camino de manera confiada y si se detecta que es equivocado decirlo abiertamente y tomar otro distinto.
  • Estar preparado para la inevitabilidad de los problemas.

Cuando nace una idea, no se tiene definido el rumbo que tomará hasta convertirse en un producto de calidad, por lo que habrá que ir averiguando cuáles son los caminos que debe de ir tomando el equipo de desarrollo para lograrlo. Esto genera incertidumbre y miedo, para combatirlos, Ed Catmull nos sugiere el uso de modelos mentales, tales como la Pirámide, el Camaleón, el Túnel, etc. En el libro podréis ver en qué consiste cada uno.

Abrazar el cambio

El cambio es algo que no podemos evitar, está presente en todos los aspectos de nuestra vida, y tenemos no sólo que aprender a vivir con él, sino sentirnos afortunados de convivir con él. Ed Catmull es consciente de esto y por tanto, el cambio recibe muchas alusiones en Creatividad S.A. Sin el cambio ni se crece ni se consigue el éxito.

Uno de los obstáculos que toda empresa, independientemente del tipo que sea, tiene que afrontar, es la resistencia al cambio, tal y como señala el autor. La gente se siente cómoda tratando con lo que ya conoce y haciendo las cosas de la manera la en que siempre las ha hecho. Las ideas novedosas pueden generar la necesidad de realizar cambios en los procesos para conseguir un producto de alta calidad, y debemos de estar preparados para lidiar con la resistencia natural que el cambio produce en las personas.

En ocasiones se ve el cambio continuo como símbolo de debilidad, pero el autor ve más peligroso el no cambiar nunca de rumbo.

Éxito y fracaso

El autor recalca la importancia de reconocer los factores que han hecho conseguir el éxito, entre estos factores tiene en cuenta el azar, y dar importancia a las cosas que no hemos podido controlar. Cuando se tiene éxito no hay que caer en vanagloriarse, y pensar en lo bueno soy, hay que estudiar cuidadosamente lo que nos ha hecho llegar hasta él y las maneras en las que hay que evolucionar para volver a conseguirlo tras el siguiente proyecto.

Ed Catmull también dedica parte del libro a analizar el fracaso. Lo importante para él, es tomarlo como experiencias de aprendizaje, que a pesar de ser doloroso, debemos sacar la parte positiva, hemos intentando ser originales. Si no se cometen fallos y se continúa trabajando para lograr el objetivo: un producto excelente, no aprenderás, o aprenderás más lentamente. Aún así, también advierte que si se está trabajando en algo que da señales de que no acabará consiguiendo el éxito esperado, es mejor fracasar de manera temprana y abandonar el proyecto, que producir un producto de baja calidad y falto de originalidad.

Honestidad y franqueza

Uno de los pilares de Pixar, es la franqueza. El presidente de la compañía nos cuenta las diferentes maneras en las que se trabaja para mantenerla intacta. Uno de los ejercicios de franqueza que se realizan en la empresa es conocido como el Braintrust:

  • Reuniones en las que se muestran resultados parciales del producto.
  • Se intenta encontrar problemas y soluciones, estando en manos del director del proyecto aplicar los cambios que considere más oportunos.
  • Ayudan a mejorar el producto, con críticas constructivas en las que se pone el foco exclusivamente en el proyecto.
  • No gana ni pierde nadie, el objetivo es que siempre salga beneficiado el producto final.

Para evitar la disminución en la franqueza detectada por los directivos a medida que crecía la empresa, se creó lo que denominaron el Día de las Notas:

  • Día en el que no se trabaja, no hay visitas y los empleados dan su opinión de cómo mejorar la empresa.
  • Los organizadores de este día reciben multitud de correos de temas a debatir que luego clasifican y dividen en sesiones a las que se apuntan los interesados en ellas.
  • Las sesiones son presididas por un moderador.
  • Las propuestas se plasman en unos formularios de resultados.
  • Los máximos responsables de la empresa estudian los resultados y toman las decisiones oportunas para llevar a cabo las propuestas si procede.

Lo que he contado aquí es un resumen de algunas de las ideas que considero más relevantes del libro, de las que seguro me he dejado algunas. Pero esto tiene solución, os recomiendo de verdad la lectura de este libro, ya que, no sólo es tremendamente interesante, sino que la lectura es realmente amena.


Cómpralo en Amazon

En mi opinión, no debemos tomar estos consejos y metodologías al pie de la letra para luego forzar su implantación en nuestra empresa. No todo va a ser igual de bueno para todos, aunque sí que es cierto que estas ideas pueden servir de inspiración.

Para mí, la idea que subyace en el libro es que cada empresa debe encontrar su propio camino, aplicando las técnicas que mejor le funcionen para conseguir una cultura creativa sana, siendo estas técnicas objeto de un estudio continuo que las someta a una evolución constante. Lo primordial es mantener y reforzar los valores de honestidad, franqueza, colaboración, trabajo duro, humildad, originalidad y disciplina que harán alcanzar la meta de la excelencia, y con ella, el éxito, ¿de eso se trata no?

Seguiremos hablando de libros en los que la Creatividad sea el tema principal o uno de los importantes, así que ¡nos vemos pronto!

Dependencia de roles y gestión de errores con Ansible

$
0
0

En este tutorial veremos cómo gestionar las dependencias de los roles de un playbook de ansible, y como configurar una tarea para que sea idempotente en múltiples ejecuciones del playbook.

Índice de contenidos


1. Introducción

Recientemente he leído Ansible up and running: Automating configuration management and deployment the easy way de Lorin Hochstein en el que he aprendido algunas cosas chulas que quería compartir con vosotros. Vamos al lío 😀


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11.2
  • Vagrant 1.8.1
  • Ansible 1.9.3

3. Instalación del entorno

La instalación del entorno es muy simple: hay que instalar Vagrant y Ansible y elegir el directorio de trabajo en el que deseamos realizar la prueba. En este tutorial explico como instalar estas herramientas.


4. Gestión de dependencias en un rol

Al crear un playbook de Ansible en mi proyecto, aprovisionabamos la máquina con los roles ordenados, de forma que las dependencias fueran en un módulo anterior al módulo que las necesitaba. Esto puede suponer un problema ya que necesitas tener un conocimiento previo del funcionamiento del aprovisionamiento para ejecutar tareas específicas.

Por ejemplo, si instalamos Sonar con PostgreSQL y tú quieres probar (o reutilizar) el módulo de Sonar en otro proyecto, necesitarías saber que depende del rol que instala PostgreSQL para su funcionamiento.

Con la gestión de dependencias de los roles lo que se pretende es justo evitar eso. Si quieres ejecutar un rol, ese rol sabe todas las dependencias que necesita para que pueda ser ejecutado fácilmente. Vamos a realizar un ejemplo donde comprobamos la gestión de dependencias del rol sonar. Para ello nos creamos nuestra carpeta Ansible dónde van tanto nuestros playbooks como nuestros roles, y nos servimos de Vagrant para provisionar las máquinas.

En el directorio elegido ejecutamos el comando Vagrant init para configurar el aprovisionamiento de nuestra máquina virtual y lo dejamos de la siguiente manera:

Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu/trusty64"

  config.vm.network "forwarded_port", guest: 9000, host: 9000

  config.vm.provider "virtualbox" do |vb|
    # Customize the amount of memory on the VM:
    vb.memory = "2048"
  end

  config.vm.define "prueba" do |prueba|
    prueba.vm.hostname = "prueba"
    prueba.vm.provision "ansible" do |ansible|
      ansible.verbose = 'vvv'
      ansible.playbook = "ansible/playbook.yml"
      ansible.inventory_path = "ansible/environments/development/inventory"
    end
  end
end

En este fichero le decimos la box de la que queremos partir, hacemos la redirección al puerto 9000 de la máquina virtual y configuramos la memoria que va a tener la máquina virtual. Por último, definimos el aprovisionamiento indicando el playbook y el inventory que vamos a utilizar.

Comenzamos creando el playbook:

ansible/playbook.yml
---

# file: playbook.yml

- hosts: sonar
  sudo: yes
  gather_facts: no
  roles:
      - sonar

Este es un playbook sencillo en el que se ejecuta sólo un rol. Vamos a crear este rol ahora 😀

ansible/roles/sonar/tasks/main.yml
---

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

- name: download sonar
  get_url: url="{{sonar.download_url}}" dest="/tmp/{{sonar.archive}}"
  tags: sonar

- name: create sonar group
  group: name=sonar state=present
  tags: sonar

- name: create sonar user
  user: name=sonar comment="Sonar" group=sonar
  tags: sonar

- name: extract sonar
  unarchive: src="/tmp/{{sonar.archive}}" dest=/opt copy=no
  tags: sonar

- name: move sonar to its right place
  shell: mv /opt/{{sonar.version_dir}} {{sonar.home}} chdir=/opt
  tags: sonar

- name: change ownership of sonar dir
  file: path="{{sonar.home}}" owner=sonar group=sonar recurse=yes
  tags: sonar

- name: copy sonar properties
  template: src=sonar.properties dest="{{sonar.home}}/conf/sonar.properties"
  tags: sonar

- name: make sonar runned by sonar user
  replace: dest="{{sonar.home}}/bin/linux-x86-64/sonar.sh" regexp="#RUN_AS_USER=(.*)$" replace="RUN_AS_USER=sonar"
  tags: sonar

- name: add sonar links for service management
  file: src="{{sonar.home}}/bin/linux-x86-64/sonar.sh" dest="{{item}}" state=link
  with_items:
    - /usr/bin/sonar
    - /etc/init.d/sonar
  tags: sonar

- name: ensure sonar is running and enabled as service
  service: name=sonar state=restarted enabled=yes
  tags: sonar

- name: create database for sonar
  postgresql_db: name="{{datasource.sonar_dbname}}" encoding=UTF-8 lc_collate=es_ES.UTF-8 lc_ctype=es_ES.UTF-8
  sudo: yes
  sudo_user: postgres
  tags:
      - sonar
      - sonardb

- name: add user to database
  postgresql_user: db="{{datasource.sonar_dbname}}" name="{{datasource.sonar_dbuser}}" password="{{datasource.sonar_dbpassword}}" priv=ALL
  sudo: yes
  sudo_user: postgres
  tags:
      - sonar
      - sonardb

Este es un rol con el que descargamos sonar, creamos su usuario y su grupo y nos aseguramos de que esté funcionando como servicio. Este rol necesita de un fichero de configuración que tenemos definido en el siguiente fichero:

ansible/roles/sonar/templates/sonar.properties
sonar.jdbc.username={{datasource.dbuser}}
sonar.jdbc.password={{datasource.dbpassword}}

sonar.jdbc.url=jdbc:postgresql://localhost:5432/{{datasource.dbname}}

sonar.jdbc.maxActive=20
sonar.jdbc.maxIdle=5
sonar.jdbc.minIdle=2
sonar.jdbc.maxWait=5000
sonar.jdbc.minEvictableIdleTimeMillis=600000
sonar.jdbc.timeBetweenEvictionRunsMillis=30000

sonar.web.context=/sonar

sonar.web.port=9000

Este fichero contiene las propiedades del Sonar, así como la configuración para que la base de datos no se cree en memoria.

Pues ya tendríamos nuestro sonar montado, aunque si nosotros tratáramos de ejecutar este rol fallaría porque le falta la configuración del idioma, el programa unzip para descomprimir el sonar y PostgreSQL. Nosotros necesitamos crear estos roles pero no queremos modificar el playbook, ya que nosotros lo que queremos instalar es Sonar. Si Sonar necesita alguna dependencia debería de ser este quien las gestionara. ¿Cómo hacemos eso? añadiendo un nuevo fichero dentro del rol sonar que sea el que conozca la dependencia:

ansible/roles/sonar/meta/main.yml
---

# file: roles/sonar/meta/main.yml

dependencies:
    - {role: locales}
    - {role: unzip}
    - {role: java8}
    - {role: postgres}

Con este fichero le estamos indicando que para poder ejecutar el rol de sonar necesita ejecutar antes los siguientes roles. La ventaja es que esta información está dentro del playbook de sonar de forma que si no tiene ese rol creado no será capaz de ejecutar sonar por falta de dependencia.

Creamos el rol de locales encargado de actualizar los idiomas y aplicarlos:

ansible/roles/locales/tasks/main.yml
---
# file: roles/locales/tasks/main.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

Instalamos Java en la máquina virtual porque es un prerrequisito de Sonar:

ansible/roles/java8/tasks/main.yml
---

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

- name: add Java repository to sources
  apt_repository: repo='ppa:webupd8team/java'
  tags: java

- name: autoaccept license for Java
  debconf: name='oracle-java8-installer' question='shared/accepted-oracle-license-v1-1' value='true' vtype='select'
  tags: java

- name: update APT package cache
  apt: update_cache=yes
  tags: java

- name: install Java 8
  apt: name=oracle-java8-installer state=latest install_recommends=yes
  tags: java

- name: set default environment variable
  apt: name=oracle-java8-set-default
  tags: java

Creamos el rol de unzip para instalarlo:

ansible/roles/unzip/tasks/main.yml
---

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

- name: install unzip command
  apt: name=unzip state=present

Creamos también el rol de postgresql:

ansible/roles/postgres/tasks/main.yml
---
# file: /roles/postgres/task/main.yml

- name: ensure PostgreSQL are installed
  apt: name={{item}} update_cache=Yes
  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

# Only for develoment
- name: ensure PostgreSQL is accesible from other hosts
  lineinfile:
      dest: /etc/postgresql/{{postgres.version}}/main/postgresql.conf
      insertafter: "#listen_addresses = 'localhost'"
      line: "listen_addresses = '*'    # Development environment!!!"

- name: ensure user can access database from other hosts
  lineinfile:
      dest: /etc/postgresql/{{postgres.version}}/main/pg_hba.conf
      line: "host    {{datasource.dbname}}           {{datasource.dbuser}}           all                     md5    # Development environment!!!"

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

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

- name: restart postgresql
  service: name=postgresql state=restarted

¡Qué no se nos olvide almacenar las variables de ejecución del playbook!

ansible/environments/ndevelopment/group_vars/sonar
---

postgres:
    version: 9.3

sonar:
    download_url: http://downloads.sonarsource.com/sonarqube/sonarqube-5.1.1.zip
    archive: sonar.zip
    version_dir: sonarqube-5.1.1
    home: /opt/sonar
    jdbc_driver: postgres
    jdbc_host: localhost
    jdbc_port: 5432

datasource:
    sonar_dbuser: sonar
    sonar_dbpassword: sonarpassword
    sonar_dbname: sonardb

Si ahora levantamos la máquina virtual veremos como primero se ejecutan las dependencias del módulo y por último se ejecuta sonar. Después podremos acceder a través de nuestro navegador preferido ejecutando localhost:9000/sonar


5. Gestión del cambio de tareas

Ahora vamos a ver otra cosa chula de ansible. Normalmente cuando ejecutamos un playbook queremos que este sea idempotente, es decir, que deje la máquina exactamente en el mismo estado independientemente de las veces que se ejecute ese playbook en el host remoto. La mayoría de los módulos de ansible soporta la idempotencia, aunque si nosotros ejecutamos comandos directamente en el servidor remoto estos no suelen ser idempotentes. Como prueba, si nosotros volvemos a provisionar la máquina que acabamos de crear, nos daría un error en una tarea de sonar precisamente porque no es idempotente. Esta tarea ejecuta el comando mv a un directorio y cuando lo realiza por segunda vez, como el directorio ya se encuentra allí, nos da un error.

Ansible también nos ofrece una solución a esto por medio de las cláusulas “changed_when” y “failed_when” donde puedes especificar de forma exacta cuando consideras que esa tarea ha fallado o ha cambiado el estado de la máquina remota. Este método es mucho menos intrusivo que poner directamente un “ignore_errors: yes” ya que nosotros decidimos qué es cambio y qué es fallo.

Para verlo en funcionamiento cambiemos la tarea “move sonar to its right place” del rol de sonar para que quede la siguiente manera:

ansible/roles/sonar/tasks/main.yml
- name: move sonar to its right place
  shell: mv /opt/{{sonar.version_dir}} {{sonar.home}} chdir=/opt
  register: result
  failed_when: '"Directory not empty" not in result.stderr'
  tags: sonar

En primer lugar necesitamos registrar la salida del comando, en nuestro caso en una variable result. Luego indicamos como condición del fallo que la tarea sea considerado un error si en la salida pone algo diferente a que el directorio no esté vacío, porque esto quiere decir que la tarea ya se realizó previamente.

Volvemos a ejecutar el aprovisionamiento de ansible (vagrant provision) y comprobamos como ahora no falla.


6. Conclusiones

Espero que este tutorial sirva para organizar mejor nuestros playbook y para gestionar los errores en tareas no idempotentes, como ejecutar comandos directamente en la máquina remota. Puedes obtener el código del ejemplo desde mi repositorio de github.


7. Referencias

Los 8 pasos de Kotter para gestionar el cambio

$
0
0

John Kotter definió una estrategia en 8 pasos para gestionar el cambio de una organización. Hoy por hoy, este sistema es ampliamente aceptado por multitud de profesionales. Este post es un resumen y explicación de las etapas que propone John Kotter para liderar la gestión del cambio empresarial.

Índice de contenidos


1. Introducción

Es indudable que los cambios se producen a nuestro alrededor. No nos queda más remedio que aceptar esta premisa como verdadera, y en ese caso hay dos caminos que podemos tomar: esperar a que los cambios se produzcan o ser impulsor de los mismos. Si eliges liderar el proceso del cambio, seguro que te interesa conocer lo que Kotter enunció hace veinte años sobre este tema.

En el ámbito del mundo empresarial, el profesor Kotter dio nombre a lo que hoy es conocido por “los 8 pasos de Kotter” para gestionar el cambio. Es objeto de este artículo explicar las fases planteadas por Kotter hasta alcanzar la implantación del cambio propuesto. Puesto que las reglas que propone, se infieren de cómo se comportan los individuos dentro del modelo de una organización, veremos que el sistema es extrapolable a otros entornos formados por colectivos estructurados jerárquicamente, aunque alejados del mundo empresarial.

John Kotter es profesor de Escuela de Negocios de Harvard y considerado el gurú en esta materia, sobre todo, desde que publicó en 1995 “Liderando el cambio” (“Leading Change”), donde exponía estas tesis.

Los 8 pasos que propone son:

  • Crear sentido de urgencia
  • Formar una coalición
  • Crear visión para el cambio
  • Comunique la visión
  • Eliminar los obstáculos
  • Asegurarse triunfos a corto plazo
  • Construir sobre el cambio
  • Anclar el cambio en la cultura de la empresa
  • Veamos qué quiere decir con cada uno.
Los 8 pasos de Kotter para gestión del cambio

1. Crear sentido de urgencia

Este punto es sin duda el más importante. No sé debe intentar iniciar el cambio si sólo nosotros vemos las ventajas del mismo. Hay que intentar prever lo que sucederá a futuro y como el cambio que proponemos puede salvar las dificultades que se avecinan, o cómo explota nuevas oportunidades de negocio que se van a presentar y para las que el cambio preparará a nuestra empresa. Todo ésto hay que planificarlo bien, pues debemos presentarlo a directivos y gerentes, y que ellos mismos, con los datos, se den cuenta que sería un error no acometer el cambio. A veces no debe ser sólo una exposición de datos, si no abrir un debate sobre la situación venidera, para que la gente piense, refute, y llegue a las mismas conclusiones. Sólo si contamos con el apoyo de los que toman decisiones se puede intentar implantar el cambio con éxito.


2. Formar una coalición

Se trata de identificar a aquellos líderes dentro de la empresa, que han compartido la misma visión y hacerles partícipes del cambio, involucrarles, estableciendo un frente común. Hay que trabajar juntos para llevar a cabo el cambio. Pero antes debemos asegurarnos que el grupo seleccionado tiene la suficiente representatividad dentro del colectivo, y que la mezcla sea extensa. Es conveniente que no todas las personas sean del mismo departamento.


3. Crear visión para el cambio

La resistencia al cambio es nuestro enemigo, y por eso hay que elaborar una visión que sea fácil de transmitir, y contar en un periodo breve de tiempo, que no lleve más de cinco minutos. Hay que identificar los puntos claves por los que es necesario el cambio, tener una reseña de cómo vemos el futuro de la empresa si aplicamos el cambio, y describir la estrategia que se seguiría para alcanzar los beneficios que nos reportaría el cambio. Una vez acordado con nuestro equipo “la visión” y que ésta es la forma de contarla, hay que practicar para que no haya fisuras ni divergencias entre los miembros de la coalición, pues probablemente a cada uno le toque evangelizar en sus respectivos departamentos y contar lo acordado.


4. Comunicar la visión

Ahora ya tenemos definida la visión, y su finalidad es comunicarla a toda la empresa. Sin duda, encontraremos resistencia, por lo que será determinante para el éxito, transmitirla una y otra vez hasta que penetre a todas las capas organizativas. Debemos predicar con el ejemplo y responder honestamente a las cuestiones y temores que se susciten en la plantilla. Hay que hablar a menudo de la visión del cambio y aplicarla en todos los aspectos.


5. Eliminar los obstáculos

A estas alturas de la película, ya todo el mundo es consciente del cambio que se quiere imponer en la empresa, y cuáles son los beneficios de imponerlos. Habrá quienes viendo las ventajas que supone se hayan lanzado a aplicarlos ya en su trabajo diario. A estas personas hay que recompensarlas, ya sea a través del organigrama o incluyéndolas en el grupo promotor del cambio. Pero también aparecerán quienes se resisten al cambio. No costará mucho identificarlos, y en este caso habrá que hacer que tomen consciencia de lo que supone para la empresa no aplicar los cambios.


6. Asegurarse triunfos a corto plazo

Estos procesos pueden ser largos. Por eso conviene asegurarse de definir una serie de hitos que tengan un éxito asegurado y que sirvan para reforzar el avance del proceso de cambio. Estos hitos pueden ser proyectos, que no requieran demasiados recursos y que se puedan llevar a cabo sin involucrar a aquellos que se oponen al cambio. Deben ser proyectos económicamente viables, pues deseamos poder esgrimir la rentabilidad del proyecto como un argumento a favor de nuestra propuesta de cambio. Y por último, hay que agradecer al equipo el esfuerzo y dedicación, que ha llevado a alcanzar con éxito la meta fijada.


7. Construir sobre el cambio

No hay que adelantarse a la victoria. Creer que el cambio se ha producido por alcanzar un éxito, sería un error. Con el primer éxito hay que seguir buscando qué mejorar, para que el segundo caso vaya más holgado. Y así en un pequeño proceso iterativo que se aprovecha de la inercia del cambio. Aún no se ha consolidado, por lo que la gente está abierta a mejoras continuas sobre la misma visión. Así nuestro cambio, puede acabar refinándose hasta alcanzar un estado en el que debemos detener el proceso para consolidar el cambio.


8. Anclar el cambio a la cultura de la empresa

Nuestra propuesta de cambio se ha consolidado y ya es la forma habitual en que la empresa trabaja. Pero eso no significa otra cosa, que volver a empezar, volver a anticiparnos al futuro de la empresa, y volver a proponer un cambio que prepare a la organización para lo que viene. Como en tantas cosas en la vida, para mantenerse en el mismo sitio, hay que evolucionar constantemente. Y esto no puede ser distinto en el mundo de la empresa, que debe abrazar la filosofía del cambio constante como un mantra que la ayude a una mejora continua.


Enlaces y referencias

Me parecía que este artículo se quedaba cojo sin incluir el estupendo vídeo de Carlos García Calvi sobre “los 8 pasos para la Gestión del Cambio”. Así que me decidí a tramitar los correspondientes permisos necesarios. Agradezco a Carlos su gentileza y buena disposición.


TLS Pinning con Java y Spring

$
0
0

En este tutorial vamos a ver qué es el TLS Pinning y cómo se puede implementar en Java mediante Spring.

Índice de contenidos


1. Introducción

Muchas veces, en las aplicaciones que desarrollamos, es necesario que nos comuniquemos con un servicio de terceros para realizar ciertas operaciones. Este tipo de comunicación podemos realizarla, típicamente, a través de servicios web securizados mediante https. Este tipo de comunicación, aunque segura ya que la comunicación se encuentra cifrada, no nos asegura que el servidor con el que nos estamos comunicando sea el que debe ser (pueden existir ataques en el que redirijan nuestra petición a otro servidor). TLS Pinning es un mecanismo mediante el cual lucharemos contra este tipo de ataques.

TLS Pinning, o también llamado Certificate Pinning, es un mecanismo de seguridad que permite autenticar al servidor con el que nos estamos comunicando. De esta forma, sabremos que el servidor con el que nos estamos comunicando es el servidor con el que nos queremos comunicar.

Normalmente, cuando se establece la comunicación con algún servidor, y ésta se realiza mediante SSL, se comprueba si el certificado del servidor es un certificado de confianza. Pero, ¿qué es un certificado de confianza?, un certificado de confianza es un certificado en el cual nosotros (como lado cliente) confiamos o un certificado que está firmado por una autoridad certificadora (CA) en la que nosotros también confiamos. Una autoridad certificadora (CA) es una entidad de confianza que emite certificados digitales. Se puede, por tanto, establecer que, si confiamos en la autoridad certificadora, confiaremos en todos los certificados que hayan sido emitidos por dicha entidad. Así, simplemente admitiendo el certificado de la autoridad certificadora admitiremos todos los certificados de cada uno de los servidores cuyo certificado haya sido emitido por dicha autoridad certificadora sin necesidad de confiar en todos los certificados uno a uno.

El objetivo de TLS Pinning es no confiar en las entidades certificadoras sino únicamente en el certificado propio del servidor. De esta forma, podremos saber que, efectivamente, estamos estableciendo la conexión con el servidor correcto en lugar de con otro servidor (a no ser que le hayan robado el certificado al servidor).


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 X El Capitan 10.10
  • Entorno de desarrollo: Eclipse Mars
  • Java 7
  • Spring 3.2.2

3. Implementando TLS Pinning


3.1. Generando el almacén de claves de confianza

Lo primero que deberemos hacer es obtener el certificado público del servidor con el que queremos comunicarnos de forma segura (nos lo deberán proporcionar). Una vez obtenido, tendremos que crear un almacén de certificados de confianza (conocido en Java como trustStore) que contenga únicamente dicho certificado. Normalmente, el almacén de certificados de confianza, por defecto, tiene una serie de certificados de las autoridades certificadoras más comunes. Como nosotros lo que queremos hacer es confiar única y exclusivamente en un único certificado (en el del servidor al que nos conectemos) tendremos que dejar este almacén con sólo dicho certificado.

Para gestionar todo el tema de almacenes, tanto keyStore como trustStore, emplearemos la herramienta keytool. En este caso, para generar un almacén de claves de confianza a partir de un certificado emplearemos el siguiente comando (importante no incluir { ni } en la ejecución del comando):

keytool -import -file {serverCertificate.cer} -alias {name} -keystore {trustStoreName}

Donde:

  • serverCertificate.cer: es el certificado público del servidor al cual nos queremos conectar.
  • name: es el nombre que le queremos dar a la entrada de ese certificado dentro de nuestro almacén de certificados de confianza.
  • trustStoreName: nombre del almacén de certificados de confianza al que añadiremos la nueva entrada. En caso de no existir se creará uno nuevo. Durante el proceso de creación se nos pedirá introducir una contraseña para el almacén de claves.

3.2. Generando nuestro PinningSSLSocketFactory

Como la comunicación que vamos a establecer con el servidor es mediante RestTemplate, tenemos que saber que RestTemplate por debajo llama a una factoría de creación de sockets SSL para la comunicación vía https. Por defecto, se usa una factoría que obtiene el almacén de certificados de confianza por defecto. Nosotros no queremos ese comportamiento, nosotros lo que queremos es usar una factoría que use el almacén de claves que hemos creado en el paso anterior. Para ello, extenderemos la funcionalidad de SSLSocketFactory para indicarle una ruta donde encontrar el almacén de certificados de confianza que tiene que utilizar y la contraseña para acceder a dicho almacén.

public class PinningSSLSocketFactory extends SSLSocketFactory {

    private static final Logger LOGGER = LoggerFactory.getLogger(PinningSSLSocketFactory.class);

    public PinningSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException,
            KeyStoreException, UnrecoverableKeyException {
        super(truststore);
    }

    public static SSLSocketFactory getSocketFactory(String trustStorePath, String trustStorePassword) {
        try {
            final SSLContext sslContext = SSLContext.getInstance("TLS");
            final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            final FileInputStream trustStoreFile = new FileInputStream(trustStorePath);
            final KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());

            trustStore.load(trustStoreFile, trustStorePassword.toCharArray());
            trustStoreFile.close();

            tmf.init(trustStore);

            X509TrustManager trustManager = null;
            for (TrustManager tm : tmf.getTrustManagers()) {
                if (tm instanceof X509TrustManager) {
                    trustManager = (X509TrustManager)tm;
                    break;
                }
            }
            sslContext.init(null, new TrustManager[] { trustManager }, null);
            return new SSLSocketFactory(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException
                | KeyManagementException e) {
            LOGGER.error("Error creating SSLSocketFactory: {}", e);
        }

        return null;
    }
}

Con esto, tendremos ya nuestra factoría a la que le indicaremos una ruta y una contraseña y nos cargará los certificados de dicho almacén de certificados de confianza.

Si existiese alguna duda con este comportamiento se recomienda acceder a la implementación propia de SSLSocketFactory de Apache.


3.3. Generando nuestro PinningHttpComponentsClientHttpRequest

RestTemplate en lugar de comunicarse directamente con la factoría de creación de sockets, se comunica mediante una clase de componentes. Por lo tanto, necesitaremos extender también esta clase para que, en su creación, como factoría de creación de sockets SSL use la que hemos creado anteriormente.

public class PinningHttpComponentsClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {

    private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100;

    private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;

    private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);

    public PinningHttpComponentsClientHttpRequestFactory(String trustStorePath, String trustStorePassword) {
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
        schemeRegistry.register(new Scheme("https", 443, PinningSSLSocketFactory.getSocketFactory(trustStorePath,
                trustStorePassword)));

        PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(schemeRegistry);
        connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
        connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);

        this.setHttpClient(new DefaultHttpClient(connectionManager));
        setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
    }
}

3.4. Uniendo las piezas

Ya tenemos todas las piezas que necesitamos para realizar TLS Pinning, lo único que nos queda es unirlas para poder crear nuestro RestTemplate con esta funcionalidad. Esta configuración la realizaremos mediante fichero .xml:

<bean name="pinningHttpComponentClientHttpRequestFactory" class="com.autentia.example.PinningHttpComponentsClientHttpRequestFactory">
    <constructor-arg value="${trustStorePath}" />
    <constructor-arg value="${trustStorePassword}" />
</bean>

<bean id="pinningRestTemplate" class="org.springframework.web.client.RestTemplate">
    <constructor-arg ref="pinningHttpComponentClientHttpRequestFactory" />
</bean>

Como podemos observar, tanto la ruta al fichero de almacén de certificados de confianza como la contraseña las indicamos mediante propiedades.


3.5. Usando nuestro PinningRestTemplate

Para usar nuestro RestTemplate lo único que necesitaremos será instanciar nuestro pinningRestTemplate.

@Qualifier("pinningRestTemplate") RestTemplate restTemplate;

4. Conclusiones

Como hemos visto, de esta forma se puede implementar TLS Pinning en nuestras aplicaciones Java con Spring. Así, se conseguirá un plus más de seguridad al autenticar el servidor al que estamos llamando con nuestros servicios.

Empezando con Ionic 2

$
0
0

En este tutorial vamos a dar los primeros pasos con Ionic 2, un framework que de la mano de Angular 2 y Apache Cordova nos permite crear aplicaciones híbridas multiplataforma respetando la guía de estilo de cada plataforma (Android, IOS y Windows).

Í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
  • Ionic 2.0.0-beta.24
  • Cordova 5.4.1 (cordova-lib@6.1.1)

2. Introducción

Actualmente la mejora en los navegadores internos de los dispositivos está haciendo posible el auge de las aplicaciones híbridas multiplaforma, acercándolas cada día más a la experiencia y rendimiento de las aplicaciones nativas pero con la ventaja de ser implementadas una única vez.

Este tipo de tecnologías nos permiten crear aplicaciones móviles con tecnología web (CSS, HTML y JS) que pueden ser ejecutadas y distribuidas en el market de cada plataforma.

Lo que aporta Ionic es un SDK que facilita la construcción de pantallas (botones, listas, …) respetando la guía de estilo de cada plataforma, de forma transparente al desarrollador, es decir, que inicialmente no tenemos que añadir una sola línea de código para conseguirlo.

Esta tecnología no es nueva y siempre ha estado ligada con el framework AngularJS y Apache Cordova, aquí tenéis un tutorial en el que ya hablábamos de estas tecnologías.

Así que este trinomio se ha seguido manteniendo con la versión 2 de Angular, que como ya vimos en este otro tutorial mejora significativamente la productividad de los equipos a la hora de desarrollar cualquier tipo de aplicación.

La versión 1 de Ionic ofrecía su SDK como un conjunto de directivas de AngularJS, así que está versión 2 hace lo mismo pero con componentes, lo que mejora significativamente el rendimiento de su antecesor.

Toda la documentación oficial la podéis encontrar aquí.

Como ejemplo significativo de la potencia de esta tecnología, os aconsejo que pinchéis aquí, para comprobar como se visualizaría un Action Sheet en las distintas plataformas desarrollando un único código y ahora imaginad teniendo que hacer esto a mano. 😉


3. Compatibilidad con versiones y plataformas

Un punto crítico a la hora de adoptar este tipo de tecnologías es la compatibilidad con los distintos dispositivos en sus distintas versiones y plataformas; sobre todo si tiene o no compatibilidad con dispositivos antiguos, por aquello de no dejar “colgados” a un número significante de usuarios.

En este punto os puedo decir, después de muchas pruebas empíricas, que este tipo de tecnologías funcionan perfectamente (sin hacer ninguna configuración adicional) en versiones iguales o superiores a la 4.4 de Android, a la 8.0 de IOS y en Windows 10.

En caso de necesitar compatibilidad con versiones 4.2 y 4.3 de Android, se puede añadir el plugin de Crosswalk a través de Cordova, que añade un navegador “moderno” a nuestra aplicación para que pueda ejecutarse en estas versiones antiguas. La única pega es que el tamaño de la aplicación aumenta considerablemente, hemos probado con una aplicación de 4 Mb, y el APK ha pasado a 25 Mb y una vez instalada ocupa 35 Mb; así que puede ser una buena solución si el espacio no es un problema y avisamos fehacientemente al usuario que descargue la aplicación a través de una red Wifi. Pero ya os digo esto solo afectaría a dispositivos 4.2 y 4.3 de Android. Aquí tenéis más información sobre el proyecto crosswalk.

Para Windows el equipo de Ionic considera que no merece la pena mantener compatibilidad con versiones anteriores a Windows 10. Esto viene motivado por la poca cuota de mercado y a la mejora drástica, en rendimiento y compatibilidad, del navegador en su versión 10.

En conclusión esta tecnología es muy valida siempre que se impongan al cliente ciertas restricciones en las versiones de los dispositivos soportados. Android 4.4+, IOS 8+ y Windows 10+ (Phone y Desktop).


4. Primeros pasos

Para dar los primeros pasos con Ionic 2 tenemos que instalar la aplicación que se distribuye como un paquete npm:

$> npm install -g ionic@beta

Nota: a día de hoy, al igual que Angular 2, es una tecnología que está en beta, pero nuestra experiencia con ella ha sido muy positiva.

Otra herramienta necesaria para poder crear este tipo de aplicaciones es Apache Cordova, que también instalamos a través de npm:

$> npm install -g cordova

Ahora estamos en disposición de crear nuestro primer proyecto. Para ello hacemos uso del potente CLI que tiene la herramienta y ejecutamos el comando start pasándole el nombre del proyecto, el template que puede ser (tabs, sidemenu, blank o tutorial), en este caso vamos a seleccionar tutorial y una serie de modificadores (podéis verlos todos ejecutando ionic help):

$> ionic start poc-ionic tutorial --v2 --typescript

Esta sentencia nos va a crear el proyecto, con el template de tutorial, en la versión 2 y utilizando la sintaxis de TypeScript.

Y no nos tenemos que preocupar de nada más. Simplemente ejecutamos el comando:

$> ionic serve

Y directamente se mostrará la aplicación de ejemplo con el tutorial en el navegador que tengamos por defecto.

ionic2-1

Como veis nos crea una aplicación con un menú lateral y un listado con sus detalles. Si queremos ver como quedaría en IOS y Android simplemente tenemos que ejecutar el mismo comando pero con el modificador –lab.

ionic2-2

Aquí se puede apreciar los cambios de estilo de cada plataforma y todavía no hemos abierto el código fuente. ¡Espectacular! :-)

Para probar la aplicación en cualquier dispositivo móvil que tengamos, el CLI de ionic también nos ofrece comandos muy útiles que hacen uso de los comandos típicos de Apache Cordova.

De tal forma que podamos añadir las plataformas que queramos soportar con el comando:

$> ionic platform add ios android windows

Nota: necesitas tener el entorno de desarrollo preparado para cada plataforma. En mi caso al tratarse de un Mac solo puedo añadir las plataformas de ios y android. La plataforma de windows la tengo que añadir dentro de una máquina virtual con Windows 10. Eso sí todos los pasos y comandos que estamos viendo son iguales en todas las plataformas.

Ahora para ejecutar la aplicación en el emulador de la plataforma que tengamos, simplemente:

$> ionic run android

Nota: En caso de no tener ningún dispositivo conectado, abrirá el emulador que tengamos configurado por defecto, recordad que si no usáis crosswalk este tiene que tener una versión igual o superior a la 4.4 (KitKat). Hay que ver lo que ha mejorado el emulador de Android en las últimas versiones, ya hace innecesario el uso de Genymotion :-)

ionic2-3

Si la aplicación la ejecutáis en Windows 10 tenéis que añadir antes las siguientes preferencias en el fichero config.xml del proyecto.

<preference name="windows-target-version" value="10.0"/>
<preference name="windows-phone-target-version" value="10.0"/>
<preference name="Windows.Universal-MinVersion" value="10.0.10069"/>
<preference name="Windows.Universal-MaxVersionTested" value="10.0.10166.0"/>

5. Conclusiones

Como habéis podido ver esta tecnología es realmente productiva a la hora crear aplicaciones híbridas multiplaforma, ofreciéndonos una herramienta donde con unos pocos comandos ya podemos empezar a trabajar.

Ahora ya no tienes excusas para no plasmar tus ideas en aplicaciones móviles multiplataforma y que todos podamos disfrutarlas independientemente del dispositivo que tengamos.

¡Manos a la obra!

Cualquier duda o sugerencia en la zona de comentarios.

Saludos.

Comentando “El libro negro del programador”, de Rafael G. Blanes

$
0
0

Comentamos un libro en el que un programador español, Rafael G. Blanes, relata su dilatada experiencia en el mundo del desarrollo, para ilustrar consejos destinados a los nuevos programadores.

El mundo del desarrollo de software, sobre todo en España, podría decirse que se divide en dos grandes grupos. Mejor dicho, en un gran grupo y en otro desgraciadamente más pequeño:

  • Desarrolladores bajo el yugo de organizaciones que no se preocupan del cómo, y tienden a un qué de mínimos.
  • Desarrolladores bajo el paraguas de organizaciones que ponen mucha atención al qué y al cómo.

Naturalmente hay otras opciones, pero no me parecen excesivamente representativas. El primer grupo desafortunadamente es el más grande, y se nutre de desarrolladores que probablemente no han tenido la oportunidad de cruzarse con integrantes del segundo grupo. No hace falta más que coincidir con las típicas grandes empresas de consultoría donde contratan programadores “al peso”, sin importarles ni su formación ni sus habilidades, que son revendidos a los clientes como expertos.

El fruto de esta política empresarial no es otro que productos de mala calidad, clientes cabreados y sobre todo, desde el punto de vista del gremio de los desarrolladores, compañeros frustrados y malogrados que se repiten constantemente a sí mismos lo arrepentidos que están de haber elegido esta profesión.

Los desarrolladores del segundo grupo, por lo general, parecen más felices. Disfrutan del día a día de su profesión, de hacer bien las cosas, y de comprobar cómo aportan un valor diferencial respecto al primer grupo, que cada día les es más reconocido. O al menos es lo que experimentamos los que formamos parte de Autentia.

¿Cuál es la diferencia entre un desarrollador de un grupo y otro? Para mí es una cuestión de actitud, pero sobre todo de conocimiento. Al menos es lo que me dice mi experiencia: hay buenos desarrolladores entre los del primer grupo. Gente capaz y con actitud, pero que no siguen el camino adecuado porque no se han topado con él.

Desde sus estudios en la universidad o en los módulos han recibido la formación básica, que han ampliado en diferentes puestos de trabajo, pero nunca han dado con la tecla de seguir ciertos caminos del conocimiento ni a ciertos autores. ¿Qué habría sido de muchos si álguien les hubiera hablado de los grupos de MeetUp?¿Y del testing, integración contínua o los principios del Clean Code?¿Y de metodologías ágiles o patrones de diseño? Quizá no todos, pero unos cuantos del primer triste grupo podrían haber pasado al segundo y feliz grupo…

Pues bien, este libro, El Libro Negro del Programador: Cómo conseguir una carrera de éxito desarrollando software y cómo evitar los errores habituales, es un buen resumen de introducción para aquellos que forman parte del primer grupo y creen que la profesión de desarrollador de software es un lugar tan lúgubre y triste.

El autor, Rafael G. Blanes, es un desarrollador con varios lustros a sus espaldas en diferentes proyectos, tanto como consultor como freelance. Y para mí, lo más importante, es que lleva tiempo desarrollando como programador del segundo grupo: testing, integración continua, clean code, metodologías ágiles… Esto es lo que realmente aporta valor: la visión de la vida del desarrollador como una profesión a dignificar a través de una disciplina y una serie de prácticas que deberían ser más comunes de lo que lo son hoy en día.

El libro está estructurado como una obra más bien dídáctica (¡Ojo! No es técnico, no verás código) sobre diferentes puntos en los que un desarrollador debería centrarse para cambiar el modo de desempeñar su profesión. A través de una serie de breves pero útiles capítulos (y resumidos en unos pocos puntos al final de cada capítulo), el autor expone su experiencia a lo largo de diferentes proyectos para ilustrar tanto consejos desde el punto de vista técnico como organizativos, tanto de filosfía que podemos aplicar en nuestro día a día como en la gestión de proyectos. Algunos puntos que me han resultado llamativos:

  • El manifiesto incial sobre la profesión del desarrollo de software.
  • Ciclo de refactoring basado en la filosofía de Martin Fowler: desarrollo, test y refactorización.
  • Dificultades que surgen en el desarrollo de software por un mal ambiente, presiones, falta de atención a la calidad.
  • Principios irrenunciables a la hora de desarrollar: inversión de control, SOLID, KISS…
  • Cambios constantes y la importancia de la mantenibilidad.
  • Gestión de equipos: en qué debe centrarse un gestor y qué peligros tienen las nuevas incorporaciones.
  • Las metodologías ágiles y el papel, cada vez menos importante, de los arquitectos de software: cambio continuo y necesidad de adaptación.
  • No perder el punto de vista del usuario: a veces es más relevante un pequeño cambio en la interfaz de usuario que un gran cambio en el backend.
  • La importancia de pararse a pensar y reflexionar sobre el paso siguiente. Mucho más que avanzar sin una dirección clara.
  • Prestar atención al OpenSource para delegar en proyectos de la comunidad, desarrollados y probados y no reinventar la rueda.
  • La importancia de desarrollar no sólo algo que funcione, sino algo que sea mantenible, que sea código limpio que cualquiera pueda entender y mantener o mejorar.
  • No estancarse, ni en tecnologías ni dentro de proyectos. Cambiar de proyectos y de tecnologías a corto plazo puede parecer perjudicial porque renunciamos a nuestra cuota de poder que hemos adquirido, pero a la larga es beneficioso porque nos enriquece.
  • Desarrollando se aprende unos de otros: leyendo código de otras personas, participando en proyectos open source o aquí añadiría yo, formando parte de comunidades tecnológicas.
  • Uso de sistemas de integración continua y de una correcta gestión de la configuración. No se trata sólo de programar sino de establecer el entorno correcto en el que programar.
  • El emprendimiento como futuro del empleo: las empresas tienden a contratar el menor número de trabajadores posibles. El resto deberá aportar valor de modo temporal, siendo más freelances que trabajadores clásicos como entendíamos hasta ahora.

El libro concluye con un cuestionario de 78 preguntas para descubrir si aplicas o no lo que el autor considera los puntos necesarios para ser un desarrollador de software altamente productivo. También se incluye una mínima bibliografía que el autor considera indispensable. Se echa en falta algunos títulos, sobre todo de testing como el Test-Driven Development de Kent Beck para cerrar el círculo con Refactoring de Martin Fowler y con Clean code de Uncle Bob o alguno de Integración continua como Continuous Delivery: Reliable Software Releases Through Build, Test, and Deployment Automation (Addison Wesley Signature Series), pero no deja de ser una breve introducción.

Bajo mi punto de vista se trata de un libro destinado a aquellos que no tienen conocimiento en el mundo de “la artesanía del software (software craftmanship)”. Es una buena introducción a este mundo, breve y amena, con consejos útiles desde la experiencia y con referencias a los clásicos de la temática. Así que si los capítulos más importantes que he puesto antes no te resultan familiares, merece la pena que le eches un vistazo a este libro.

Si por el contrario eres un fiel seguidor de Uncle Bob, Fowler, Kent Beck, o Sandro Mancuso (entre otros…), estás familiarizado con el manifiesto ágil, no te pierdes ningún meetup tecnológico y en tu trabajo estás todo el día mirando los informes de SonarQube, entonces no creo que este libro te aporte mucho la verdad. Gira entorno a conceptos que seguro que ya conoces y lo único que hará es confirmarte que no estás solo en este mundo :). No obstante, la version electrónica vale lo que un par de botes de Pringles y se lee en un periquete. Igual te vale para algo.

En definitiva, es un libro que recomendaría a la gente que está comenzando en el mundo del desarrollo, o a aquellos que están estancados en unas prácticas nada sanas, como apertura a un mundo mejor: el de la artesanía del software.

Aquí tienes el enlace por si quieres comprar la versión electrónica por 2.99€:

libroNegroBig
Cómpralo en Amazon

Instalación de Kubernetes en Ubuntu con Ansible

$
0
0

En este tutorial comprobaremos como instalar Kubernetes de forma sencilla con Ansible.

Índice de contenidos


1. Introducción

Una de las cosas más importantes a la hora de tener una aplicación en producción es que esta esté disponible siempre para los usuarios. Kubernetes nos ofrece una buena infraestructura para aumentar la disponibilidad de nuestra aplicación.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11.2
  • Virtual Box
  • Vagrant 1.8.1
  • Ansible 1.9.3

3. Preparación y configuración del entorno

En tutoriales anteriores ya se ha explicado como instalar Vagrant y Ansible (ver aquí), así que partimos de que ambas herramientas ya están instaladas.

Recordamos que Vagrant es una herramienta que nos permite gestionar máquinas virtuales (usando en nuestro caso VirtualBox por debajo) y que Ansible nos permite automatizar tareas en host remotos via ssh. Vagrant y ansible están muy bien integrados, de forma que utilizaremos Vagrant para crear el entorno con un sistema operativo Ubuntu en el cual instalaremos y jugaremos con Kubernetes. Para ello nos creamos un directorio dónde se encontrará nuestro Vagrantfile, fichero de configuración de Vagrant. Para que se genere el fichero por defecto ejecutamos el comando vagrant init.

Nuestro fichero Vagrantfile debe quedar de la siguiente manera:

Vagrantfile
Vagrant.configure(2) do |config|

  config.vm.box = "ubuntu/trusty64"

  config.vm.network "forwarded_port", guest: 8080, host: 8080
  config.vm.network "forwarded_port", guest: 32066, host: 32066

  config.vm.network "private_network", ip: "192.168.90.20"

  config.vm.provider "virtualbox" do |v|
      v.memory = 4096
      v.cpus = 2
  end

  config.vm.define "prueba" do |prueba|
    prueba.vm.provision "ansible" do |ansible|
      ansible.verbose = 'vvv'
      ansible.playbook = "ansible/infraestructure.yml"
    end
  end
end

Analicemos que realizará este fichero de configuración:

  • config.vm.box = “ubuntu/trusty64” : en este apartado indicamos la box que va a utilizar vagrant para crear la máquina virtual.
  • config.vm.network “forwarded_port”, guest: 8080, host: 8080 : aquí redireccionamos el puerto de la máquina anfitrión a la máquina virtual de forma que cuando se llame al puerto 8080 de la máquina real esto será redireccionado al puerto 8080 de la máquina virtual. Esto nos sirve para ver la interfaz gráfica de Kubernetes.
  • config.vm.network “private_network”, ip: “192.168.90.20” : en este apartado indicamos la ip que tendrá la máquina virtual.
  • v.memory = 4096 : en este apartado la memoria que va a tenr la máquina virtual.
  • Por último definimos el provisionamiento que Vagrant va a realizar en la máquina una vez esté levantada. Para ello uso el ya mencionado Ansible, pero notad que no le especifico en ningún lugar el inventory. Esto está hecho a propósito ya que Vagrant te lo genera automáticamente si no lo creas tú.

Ahora tenemos que crear el directorio Ansible dónde vamos a tener la siguiente estructura:

  • infraestructure.yml: es el playbook principal que se encarga de instalar kubernetes.
  • roles/docker/tasks/docker_install.yml : aquí se encuentran las tareas propias de la instalación docker.
  • roles/docker/tasks/docker_prerequisites.yml : aquí están las tareas que necesitan ser ejecutadas para que se instale docker.
  • roles/docker/tasks/docker_setup.yml : aquí están tareas propias de la configuración.
  • roles/docker/tasks/main.yml : aquí se encuentran las tareas que se van a ejecutar en el rol de docker.
  • roles/kubernetes/files/config-default.sh : es el fichero de configuración dónde se especifican las propiedades del cluster.
  • roles/kubernetes/meta/main.yml : en este fichero se encuentran las dependencias del rol kubernetes.
  • roles/kubernetes/tasks/main.yml : aquí definimos las tareas que se van a ejecutar en el rol kubernetes.
  • roles/kubernetes/vars/main.yml : por ultimo definimos las variables propias del rol aquí.

3.1 Kubernetes

A continuación veremos el contenido del rol Kubernetes.

roles/kubernetes/files/config-default.sh
#!/bin/bash

# Copyright 2015 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

## Contains configuration values for the Ubuntu cluster

# Define all your cluster nodes, MASTER node comes first"
# And separated with blank space like   
export nodes=${nodes:-"root@127.0.0.1"}

# Define all your nodes role: a(master) or i(minion) or ai(both master and minion), must be the order same
role={roles:-"ai"}
# If it practically impossible to set an array as an environment variable
# from a script, so assume variable is a string then convert it to an array
export roles=($role)

# Define minion numbers
export NUM_NODES=${NUM_NODES:-1}
# define the IP range used for service cluster IPs.
# according to rfc 1918 ref: https://tools.ietf.org/html/rfc1918 choose a private ip range here.
export SERVICE_CLUSTER_IP_RANGE=${SERVICE_CLUSTER_IP_RANGE:-192.168.3.0/24}  # formerly PORTAL_NET
# define the IP range used for flannel overlay network, should not conflict with above SERVICE_CLUSTER_IP_RANGE

# The Ubuntu scripting supports two ways of networking: Flannel and
# CNI.  To use CNI: (1) put a CNI configuration file, whose basename
# is the configured network type plus ".conf", somewhere on the driver
# machine (the one running `kube-up.sh`) and set CNI_PLUGIN_CONF to a
# pathname of that file, (2) put one or more executable binaries on
# the driver machine and set CNI_PLUGIN_EXES to a space-separated list
# of their pathnames, and (3) set CNI_KUBELET_TRIGGER to identify an
# appropriate service on which to trigger the start and stop of the
# kubelet on non-master machines.  For (1) and (2) the pathnames may
# be relative, in which case they are relative to kubernetes/cluster.
# If either of CNI_PLUGIN_CONF or CNI_PLUGIN_EXES is undefined or has
# a zero length value then Flannel will be used instead of CNI.

export CNI_PLUGIN_CONF CNI_PLUGIN_EXES CNI_KUBELET_TRIGGER
CNI_PLUGIN_CONF=${CNI_PLUGIN_CONF:-""}
CNI_PLUGIN_EXES=${CNI_PLUGIN_EXES:-""}
CNI_KUBELET_TRIGGER=${CNI_KUBELET_TRIGGER:-networking}

# Flannel networking is used if CNI networking is not.  The following
# variable defines the CIDR block from which cluster addresses are
# drawn.
export FLANNEL_NET=${FLANNEL_NET:-172.16.0.0/16}

# Optionally add other contents to the Flannel configuration JSON
# object normally stored in etcd as /coreos.com/network/config.  Use
# JSON syntax suitable for insertion into a JSON object constructor
# after other field name:value pairs.  For example:
# FLANNEL_OTHER_NET_CONFIG=', "SubnetMin": "172.16.10.0", "SubnetMax": "172.16.90.0"'

export FLANNEL_OTHER_NET_CONFIG
FLANNEL_OTHER_NET_CONFIG=''

# Admission Controllers to invoke prior to persisting objects in cluster
export ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,SecurityContextDeny

# Path to the config file or directory of files of kubelet
export KUBELET_CONFIG=${KUBELET_CONFIG:-""}

# A port range to reserve for services with NodePort visibility
SERVICE_NODE_PORT_RANGE=${SERVICE_NODE_PORT_RANGE:-"30000-32767"}

# Optional: Enable node logging.
ENABLE_NODE_LOGGING=false
LOGGING_DESTINATION=${LOGGING_DESTINATION:-elasticsearch}

# Optional: When set to true, Elasticsearch and Kibana will be setup as part of the cluster bring up.
ENABLE_CLUSTER_LOGGING=false
ELASTICSEARCH_LOGGING_REPLICAS=${ELASTICSEARCH_LOGGING_REPLICAS:-1}

# Optional: When set to true, heapster, Influxdb and Grafana will be setup as part of the cluster bring up.
ENABLE_CLUSTER_MONITORING="${KUBE_ENABLE_CLUSTER_MONITORING:-true}"

# Extra options to set on the Docker command line.  This is useful for setting
# --insecure-registry for local registries.
DOCKER_OPTS=${DOCKER_OPTS:-""}

# Extra options to set on the kube-proxy command line.  This is useful
# for selecting the iptables proxy-mode, for example.
KUBE_PROXY_EXTRA_OPTS=${KUBE_PROXY_EXTRA_OPTS:-""}

# Optional: Install cluster DNS.
ENABLE_CLUSTER_DNS="${KUBE_ENABLE_CLUSTER_DNS:-true}"
# DNS_SERVER_IP must be a IP in SERVICE_CLUSTER_IP_RANGE
DNS_SERVER_IP=${DNS_SERVER_IP:-"192.168.3.10"}
DNS_DOMAIN=${DNS_DOMAIN:-"cluster.local"}
DNS_REPLICAS=${DNS_REPLICAS:-1}

# Optional: Install Kubernetes UI
ENABLE_CLUSTER_UI="${KUBE_ENABLE_CLUSTER_UI:-true}"

# Optional: Enable setting flags for kube-apiserver to turn on behavior in active-dev
#RUNTIME_CONFIG=""

# Optional: Add http or https proxy when download easy-rsa.
# Add envitonment variable separated with blank space like "http_proxy=http://10.x.x.x:8080 https_proxy=https://10.x.x.x:8443"
PROXY_SETTING=${PROXY_SETTING:-""}

DEBUG=${DEBUG:-"false"}

El fichero config-default se crea por defecto cuando descargas la versión de Kubernetes (se encuentra dentro del directorio kubernetes/cluster/ubuntu). Este fichero se encarga de establecer la configuración por defecto del cluster, como el número de nodos, sus respectivas ip y el número de minions y de maestros del cluster. Las propiedades que hemos modificado del fichero son:

  • export nodes=${nodes:-“root@127.0.0.1”}: Con esta propiedad le indicamos los nodos que va a tener nuestro cluster. Como este es un ejemplo muy sencillo, tenemos un cluster de un único nodo, pero si tuviéramos más deberían de ir las ip aquí separadas por espacios.
  • role={roles:-“ai”}: Aquí se separan los nodos por espacio y van en el mismo orden en el que hemos definido las ips en la propiedad anterior. a significa master, mientras que i significa minion de forma que estamos indicando que nuestro único nodo es master y minion a la vez.
  • export NUM_NODES=${NUM_NODES:-1}: con esta propiedad indicamos que el número de minions que tiene nuestro cluster es 1.

En el siguiente fichero están descritas las tareas que se van a realizar en el playbook Kubernetes:

roles/kubernetes/tasks/main.yml
---

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

- name: Create ssh key for this machine (Necesary to start kubernetes)
  user: name=root generate_ssh_key=yes
  register: user_var

- name: Add key to authorized keys
  shell: "cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys"

- name: Download Kubernetes release bundle
  get_url: >
    url="https://github.com/GoogleCloudPlatform/kubernetes/releases/download/v1.2.0/kubernetes.tar.gz"
    dest="{{kubernetes.temporal_path}}/kubernetes.tar.gz"

- name: Untar the kubernetes bundle
  unarchive: >
    src="{{kubernetes.temporal_path}}/kubernetes.tar.gz"
    dest={{kubernetes.path}} copy=no

- name: Unarchive salbase tar to configure the cluster.
  unarchive: >-
    src={{kubernetes.path}}/kubernetes/server/kubernetes-salt.tar.gz
    dest={{kubernetes.path}}/kubernetes/cluster
    copy=no

- name: move saltbase directory outside the unarchive directory
  shell: mv {{kubernetes.path}}/kubernetes/cluster/kubernetes/saltbase {{kubernetes.path}}/kubernetes/cluster
  register: result
  failed_when: "'Directory not empty' not in result.stderr and 'true' in result.failed"

- name: Delete empty directory
  file: >-
    path={{kubernetes.path}}/kubernetes/cluster/kubernetes
    state=absent

- name: Setup default configuration
  copy: >
    src=config-default.sh
    dest={{kubernetes.path}}/kubernetes/cluster/ubuntu
    mode="0755"
    force=yes

- name: Start cluster
  shell: KUBERNETES_PROVIDER=ubuntu ./kube-up.sh chdir={{kubernetes.path}}/kubernetes/cluster
  register: result
  failed_when: "'Text file busy' not in result.stdout and 'true' in result.failed"

Los pasos que sigue este playbook son:

  • Las dos primeras tareas son necesarias para arrancar el servicio de Kubernetes. Este servicio necesita de acceso por ssh, y como lo estamos automatizando con Ansible, no queremos que se detenga la ejecución. Para ello creamos una par clave pública/privada que identifica a la máquina, y añadimos la clave pública al fichero authorized_keys.
  • Nos descargamos la última release de Kubernetes y la descomprimimos en la variable {{kubernetes.path}}, que se encuentra en el fichero roles/kubernetes/defaults/main.yml.
  • Una vez hecho eso, para el arranque del cluster necesitamos un certificado para que el master del cluster pueda actuar como un servidor https. Para ello generamos un certificado general y lo usamos para firmar los certifcicados de la máquina con el script make-ca-cert.sh que se encuentra en el directorio saltabse, que está comprimido por defecto. Lo descomprimimos y lo movemos al directorio correspondiente.
  • Ya estaríamos listos para levantar el cluster, pero necesitamos la configuración que tenemos en el fichero config-default.sh dónde indicamos que solo se nos cree un nodo en la máquina virtual que sea master y minion a la vez.
  • Dejando lo mejor para el final, ejecutamos el comando para levantar el cluster. Comprobamos como antes del comando tenemos que indicarle previamente el provider a utilizar, ubuntu en nuestro caso. En esta tarea usamos la gestión de errores que nos ofrece Ansible. Esta característica está explicada en este tutorial.

Ahora vemos el fichero defaults/main.yml que contiene las variables por defecto que se van a utilizar en el rol:

roles/kubernetes/defaults/main.yml
kubernetes:
    path: /opt
    temporal_path: /tmp

Por último, en el fichero main.yml del directorio meta dentro del rol, tenemos las dependencias que tiene el este rol. En el tutorial mencionado anteriormente también hablo sobre la gestión de dependencias de los roles de ansible.

roles/kubernetes/meta/main.yml
---

# file: roles/kubernetes/kubernetes/meta/main.yml

dependencies:
    - {role: docker}

3.2. Docker

Nos queda crear las tareas pertenecientes al rol docker, que instalarán la última versión de Docker.

roles/docker/tasks/main.yml
---

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

- include: docker_prerequisites.yml
- include: docker_setup.yml
- include: docker_install.yml

Como bien dice, estos ficheros contienen las tareas encargadas de los prerequisitos de la instalacción, la configuración y la instalación propiamente dicha.

roles/docker/tasks/docker_prerequisites.yml
---

# file: roles/docker/tasks/docker_prerequisites.yml

- name: install Ensure that apt works with https method and CA certificates are installed
  apt: >
    name="{{ item }}"
    state=latest
    update_cache=yes
  with_items:
      - apt-transport-https
      - ca-certificates

- name: Add GPG key
  command: sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

- name: Copy docker.list
  copy: >
    src=docker.list
    dest=/etc/apt/sources.list.d
    force=yes

- name: Install appsrmor package
  apt: >
    name=apparmor
    update_cache=yes
roles/docker/tasks/docker_setup.yml
---

# file: roles/docker/tasks/docker_setup.yml

- name: create docker group
  group: name=docker state=present

- name: create docker user
  user: name=docker group=docker state=present
roles/docker/tasks/docker_install.yml
---

# file: roles/docker/tasks/docker_install.yml

- name: install docker
  apt: >
    name="docker-engine"
    state=latest
    update_cache=yes


- name: ensure docker is running and enabled as service
  become_user: docker
  service: name=docker state=started enabled=yes

Con esto ya tendríamos preparado el entorno y la configuración.


4. Comprobación

Una vez realizada la configuración, basta con ejecutar el comando vagrant up para comprobar como se despliega. Una vez termine vagrant, comprobamos como accediendo a la url localhost:8080 tenemos la indicaciones del api rest. El dashboard viene deshabilitado por defecto, pero veremos como habilitarlo en el próximo tutorial.


5. Conclusiones

Comprobamos una vez más como gracias a Ansible crear una infraestructura compleja como instalar Kubernetes en ubuntu puede ser hecho con un solo comando. Puedes ver los ficheros creados en el tutorial en mi repositorio de github.


6. Referencias

Primeros pasos con Kubernetes

$
0
0

En este tutorial veremos una pequeña introducción a la gestión de servicios que nos ofrece la herramienta Kubernetes.

Índice de contenidos


1. Introducción

Kubernetes es una herramienta muy potente para manejar los servicios y que nos ofrece a tiro de piedra la experiencia de google a la hora de gestionar múltiples host remotos para el despliegue y mantenimiento de aplicaciones. En este tutorial aprenderemos los conceptos básicos al trabajar con kubernetes así como la exposición de las aplicaciones fuera del cluster.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11.2
  • Virtual Box
  • Vagrant 1.8.1
  • Ansible 1.9.3

3. Conceptos básicos

Kubernetes es una plataforma opensource para el automatizar el despliegue, escalado de las aplicaciones, así como las operaciones con los contenedores de aplicaciones. Antes de ponernos manos a la obra, tenemos que tener una serie de conceptos claros:

  • Cluster: Conjunto de másquinas físicas o virtuales y otros recursos utilizados por kubernetes.
  • Nodo: Una máquina física o virtual ejecutándose en kubernetes donde pods pueden ser programados.
  • Pod: Son la unidad más pequeña desplegable que puede ser creada, programada y manejada por kubernetes.
  • Replication Controller: Se asegura de que el número específicado de réplicas del pod estén ejecutándose. Permite escalar de forma facil los sistemas y maneja la re-creación de un pod cuando ocurre un fallo.
  • Service: Es una abstracción que define un conjunto de pods y la lógica para acceder a los mismos.

4. Instalación de kubernetes

Para realizar la instalación de kubernetes seguiremos el tutorial de instalación de Kubernetes en ubuntu. A partir de este punto entendemos que tienes Kubernetes instalado en una máquina virtual de Ubuntu.


5. Operaciones básicas

A continuación se indican las operaciones básicas que se pueden realizar con Kubernetes como crear y eliminar pods, crear y eliminar replication controllers y gestionar estos con los servicios, exponiéndolos dentro y fuera del cluster.


5.1. Pods


5.1.1 ¿Qué es un pod?

Como hemos definido antes, un pod es la unidad mínima que es manejada por kubernetes. Este pod es un grupo de uno o más contenedores (normalmente de Docker), con almacenamiento compartido entre ellos y las opciones específicas de cada uno para ejecutarlos. Un modelo de pods específico de una aplicación contiene uno o más contenedores que normalmente irían en la misma máquina.

Varios contenedores que pertenezcan al mismo pod son visibles unos de otros vía localhost. Los contenedores que se encuentran en distintos pods no pueden comunicarse de esta manera.

En términos de Docker, un pod es un conjunto de contenedores de Docker con namespace y volúmenes compartidos.

Hay que tener en cuenta que los pods son entidades efímeras. En el ciclo de vida de un pod estos se crean y se les asigna un UID hasta que terminen o se borren. Si un nodo que contiene un pod es eliminado, todos los pods que contenía ese nodo se pierden. Este pod puede ser reemplazado en otro nodo, aunque el UID será diferente. Esto es importante porque un pod no debería de tener información almacenada que pueda ser utilizada después por otro pod en caso de que a este le pasara algo. Para compartir información entre pods están los volúmenes (no hablamos de ellos en este tutorial, pero si tienes curiosidad puedes mirarlo en este enlace).


5.1.2 Usos de un pod

Los pods pueden utilizarse para realizar escalado horizontal, aunque fomentan el trabajo con microservicios puestos en contenedores diferentes para crear un sistema distribuido mucho más robusto. Puedes encontrar más información sobre los patrones utilizados aquí.


5.1.3 Creación de un pod

Los pods se pueden crear de dos maneras: directamente por línea de comandos o a través de un fichero de tipo YAML.

NOTA: Para ejecutar los comandos tal y como se muestran en el tutorial tenemos el comando que contiene el comando kubectl añadido al path. Para realizarlo, solo hay que introducir al final del fichero .bashrc la siguiente línea: “export PATH=$PATH:/opt/kubernetes/cluster/ubuntu/binaries” (teniendo en cuenta que se siga la instalación del tutorial previo).

Para crearlo directamente por línea de comandos ejecutamos lo siguiente:

Creación de replication controller por línea de comandos:
kubectl run my-nginx --image=nginx --port=80

Kubectl es el programa que vamos a utilizar para interactuar con el api de kubernetes.

  • El primer parámetro indica la acción, que sirve para arrancar un pod.
  • Después el nombre que va a recibir, en este caso my-nginx.
  • Después el nombre que va a recibir, en este caso my-nginx.
  • Luego la imagen a partir de la que se va a construir el pod (la imagens e llama nginx).
  • Por último el puerto en el que escucha.

Una vez ejecutado este comando obtendremos:

Lo que estamos viendo por pantalla no es el pod, sino el replication controller que se ha creado que se encarga de gestionarlo (y veremos más adelante). Para ver tanto el pod como el replication controller ejecutamos los siguientes comandos:

kubectl get pods
kubectl get rc

Comprobamos como el nombre del pod es igual al nombre del replication controller nombrado en el comando (my-nginx) seguido de un identificador único para cada pod.

Creación de pod a través de un fichero YAML

Para crearlo directamente por fichero de tipo YAML nos crearemos el archivo nginx-pod.yaml dentro del directorio /opt/kubernetes/example/nginx:

/opt/kubernetes/examples/nginx/nginx-pod.yaml
# Número de versión del api que se quiere utilizar
apiVersion: v1
# Tipo de fichero que se va a crear.
kind: Pod
# Aquí van los datos propios del pod como el nombre y los labels que tiene asociados para seleccionarlo
metadata:
    name: my-nginx
    # Especificamos que el pod tenga un label con clave "app" y valor "nginx"
    labels:
        app: nginx
# Contiene la especificación del pod
spec:
    # Aquí se nombran los contenedores que forman parte de este pod. Todos estos contenedores serían visibles por localhost
    containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
    # Aquí se define la política de restauració en caso de que el pod se detenga o deje de ejecutarse debido a un fallo interno.
    restartPolicy: Always

Antes de ejecutar este pod vamos a eliminar el pod creado anteriormente. para ello usamos el comando:

kubectl delete rc my-nginx

¿Por qué borramos un replication controller en lugar del pod específico? Cuando creas un pod desde línea de comando implícitamente se crea un replication controller que se encarga de restaurar el pod cuando este es borrado ya que su política de restauración por defecto siempre es Always. Por tanto ahora procedemos a crear el mismo pod pero desde el fichero que acabamos de crear:

kubectl create -f /opt/kubernetes/examples/nginx/nginx-pod.yaml

Como estamos creando el pod directamente, vemos como ahora no se crea un replication controller.

Y ¿qué es un replication controller? vamos a verlo justo a continuación 😀


5.2. Replication Controllers


5.2.1 ¿Qué es un replication controller?

Un replication controller se asegura de que grupo de uno o más pods esté siempre disponible. Si hay muchos pods, eliminará algunos. Si hay pocos, creará nuevos. Por este motivo, se recomienda siempre crear un replication controller aunque solo tengas un único pod (este es el motivo por el cual cuando creamos un pod por comando automáticamente se crea un replication controller para el pod que acabamos de crear). Un replication controller es al fin y al cabo un supervisor de un grupo de uno o más pods a través de un conjunto de nodos.


5.2.2 Creación de un replication controller

A continuación crearemos un replication contorller llamado nginx-rc.yaml en el directorio /opt/kubernetes/examples/nginx a partir de la siguiente plantilla, encargado de levantar un servidor nginx:

/opt/kubernetes/examples/nginx/nginx-rc.yaml
# Número de versión del api que se quiere utilizar
apiVersion: v1
# Tipo de fichero que se va a crear.
kind: ReplicationController
# Datos propios del replication controller
metadata:
    # Nombre del Replication Controller
    name: my-nginx
# La especificación del estado deseado que queremos que tenga el pod.
spec:
    # Número de réplicas que queremos que se encargue de mantener el rc. (Esto creará un pod)
    replicas: 1
    # En esta propiedad se indican todos los pods que se va a encargar de gestionar este replication controller. En este caso, se va a encargar de todos los que tengan el valor "nginx" en el label "app"
    selector:
        app: nginx
    # Esta propipedad tiene exactamente el mismo esquema interno que un pod , excepto que como está anidado no necesita ni un "apiVersion" ni un "kind"
    template:
        metadata:
            name: nginx
            labels:
                app: nginx
        spec:
            containers:
                - name: nginx
                  image: nginx
                  ports:
                    - containerPort: 80

Antes de ejecutar este pod vamos a eliminar el pod creado en el paso anterior. para ello usamos el comando:

kubectl delete pod my-nginx

Ahora creamos el replication controller con el comando:

kubectl create -f /opt/kubernetes/examples/nginx/nginx-rc.yaml

Vemos como ahora se nos ha creado un pod con un UID. Para ver para que sirve cada línea del replication controller miramos los comentarios de código.

También podemos comprobar el estado de nuestro replication controller (nombre, número de pods y sus respectivos estados,..) a través del comando:

kubectl describe rc my-nginx


5.2.3 Trabajando con replication controllers

Una vez creado un rc te permite:

  • Escalarlo: puedes escoger el número de réplicas que tiene un pod de forma dinámica.
  • Borrar el replication controller: puedes borrar solo el replication controller o borrarlo junto a todos los pods de los que se encarga
  • Aislar al Pod del replication controller:Los pods pueden no pertenecer a un replication controller cambiando los labels. El pod que ha sido removido de está manera será reemplazado por un pod nuevo, que será creado por le replication controller.

Para añadir un pod que sea controlado por el replication controller ejecutamos:

kubectl scale rc my-nginx --replicas=2

Tras listar el número de pods comprobaríamos como se ha añadido uno nuevo:

Para borrar un replication controller ejecutaríamos el comando:

kubectl delete rc my-nginx

Si después listamos los pods comprobaremos que han desaparecido todos.


5.3. Services


5.3.1 ¿Qué es un service?

Como ya sabemos, los pods son volátiles. Son creados y destruidos, de forma que no pueden recuperarse. De hecho, los replication controllers son los encargados de manejar su ciclo de vida, y de definir sus políticas de restauración. Como decíamos anteriormente, cada pod tiene su propia dirección IP (que podría incluso no ser constante en el mismo pod a lo largo del tiempo). Esto nos supone un problema en caso de que un pod necesite comunicarse con otro pod. ¿Qué manera tienen de comunicarse ambos, si las ip de cada pod son variables, o si uno de los dos se cae y lo sustituye otro? De esto justo se encargan los services.

Un service es una abstracción que define un grupo lógico de pods y una política de acceso a los mismos. Los pods apuntan a un servicio normalmente por la propiedad label. Pongamos como ejemplo nuestro caso anterior, dónde tenemos un replication controller encargado de ejecutar un pod con un contenedor nginx. Si algo causara la destrucción de este pod, el replication controller crearía uno nuevo con una ip diferente, de forma que el resto de la infraestructura que dependiera de ese pod por esa ip fija dejaría de funcionar. El servicio lo que hace es que ese pod siempre sea accesible de la misma manera, de forma que aunque el pod se destruya o se modifique siempre sea accesible por la abstracción. A continuación crearemos un servicio y también comprobaremos como podemos exponerlo desde fuera del cluster.


5.3.2 Creación de un Service

A continuación vamos a crear un service:

/opt/kubernetes/examples/nginx/nginx-svc.yaml
# Número de versión del api que se quiere utilizar
apiVersion: v1
# Tipo de fichero que se va a crear.
kind: Service
# Aquí van los datos propios del pod como el nombre y los labels que tiene asociados para seleccionarlo
metadata:
    name: my-nginx-service
# Contiene la especificación del pod
spec:
    # En esta propiedad se indican todos los pods que apuntan a este servkice. En este caso, se va a encargar de todos los que tengan el valor "nginx" en el label "app"
    selector:
        app: nginx
    ports:
      # Indica el puerto en el que se debería de servir este servicio
      - port: 80

Una vez creado el fichero levantamos el service con el comando:

kubectl create -f /opt/kubernetes/examples/nginx/nginx-svc.yaml

Ahora habilitamos el replication controller de este servicio, que es el que hemos creado anteriormente:

kubectl create -f /opt/kubernetes/examples/nginx/nginx-rc.yaml

Ahora vamos a acceder a la aplicación del pod desde dentro del cluster. Para ello necesitamos saber la ip que tiene actualmente el pod, para lo que usamos el comando:

kubectl get -o template pod my-nginx-m5mni --template={{.status.podIP}}

Ahora accedemos al servicio por medio de esa ip usando el comando curl.

Actualmente este pod solo es accesible dentro del cluster. El service nos sirve para abstraernos del pod en cuestión que estamos utilizando. Este tipo de servicios dentro del cluster se suele utilizar para tener múltiples aplicaciones de un backend detrás del mismo frontend.


5.3.3 Acceder a un servicio desde fuera del cluster

Por último vamos a acceder al servicio desde fuera de cluster, de modo que accederemos al servidor nginx de nuestra máquina virtual desde el navegador. Para ello necesitamos modificar el servicio que estamos utilizando al que le añadimos la propiedad type: NodePort, que lo que hace es exponer el servicio en cada nodo del cluster de forma que serás capaz de contactar con el servicio desde cualquier ip de los nodos. Nuestro servicio qeudaría de la siguiente manera:

/opt/kubernetes/examples/nginx/nginx-svc.yaml
# Número de versión del api que se quiere utilizar
apiVersion: v1
# Tipo de fichero que se va a crear.
kind: Service
# Aquí van los datos propios del pod como el nombre y los labels que tiene asociados para seleccionarlo
metadata:
    name: my-nginx-service
# Contiene la especificación del pod
spec:
    type: NodePort
    # En esta propiedad se indican todos los pods que apuntan a este servkice. En este caso, se va a encargar de todos los que tengan el valor "nginx" en el label "app"
    selector:
        app: nginx
    ports:
      # Indica el puerto en el que se debería de servir este servicio
      - port: 80

Eliminamos el anterior service y añadimos el nuevo con los comandos que ya sabemos y comprobamos en que puerto externo está mapeeada la conexión:

Vemos que tenemos la conexión mapeada en el puerto 32138 (que es el puerto que se elije de forma automática). Ahora tenemos que cambiar la configuración de vagrant para acceder a este puerto desde localhost:

/opt/kubernetes/examples/nginx/nginx-svc.yaml
Vagrant.configure(2) do |config|

  config.vm.box = "ubuntu/trusty64"

  config.vm.network "forwarded_port", guest: 8080, host: 8080
  config.vm.network "forwarded_port", guest: 32138, host: 32138

  config.vm.network "private_network", ip: "192.168.90.20"

  config.vm.define "prueba" do |prueba|
    prueba.vm.provision "ansible" do |ansible|
      ansible.verbose = 'vvv'
      ansible.playbook = "ansible/infraestructure.yml"
    end
  end
end

Ahora reiniciamos la máquina virtual para que aplique los nuevos cambios con el comando vagrant reload. Accedemos a la máquina con el comando vagrant ssh y una vez dentro, arrancamos el cluster de kubernetes con el comando:

/opt/kubernetes/examples/nginx/nginx-svc.yaml
cd /opt/kubernetes/cluster
KUBERNETES_PROVIDER=ubuntu ./kube-up.sh

NOTA: Esto implica que has seguido el tutorial de instalación de kubernetes en ubuntu para crear un cluster de kubernetes.

Por último accedemos desde el navegador a la url localhost:32138 (tarda un poco en mostrarse debido a la configuración de la máquina virtual, asíque se puede aumentar desde el fichero Vagrantfile)


6. Acceder a la interfaz gráfica desde el navegador

Kubernetes cuenta con una serie de add-ons, qué son un conjunto de Services y Replication Controller (con sus correspondientes pods) que son considerados una parte interna del cluster de Kubernetes. Estos add-ons son visibles a través del API. Vamos a habilitar la interfaz que nos ofrece kubernetes: Para ello comenzamos habilitando el namespace kube-system, que viene en el fichero /opt/kubernetes/cluster/ubuntu/namespace.yaml para lo que ejecutamos el comando:

/opt/kubernetes/cluster/ubuntu/namespace.yaml
kubectl create -f /opt/kubernetes/cluster/ubuntu/namespace.yaml

Ahora habilitamos la interfaz gráfica, creando el Replication controller y el Service que vienen en el directorio /opt/kubernetes/cluster/addons/dashboard pero antes de eso, en el servicio exponemos el servicio fuera del cluster como hicimos anteriormente para poder acceder desde el navegador. Para ello simplemente añadimos type: NodePort justo después del spec.

/opt/kubernetes/cluster/addons/dashboard/dashboard-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubernetes-dashboard
  namespace: kube-system
  labels:
    k8s-app: kubernetes-dashboard
    kubernetes.io/cluster-service: "true"
spec:
  type: NodePort
  selector:
    k8s-app: kubernetes-dashboard
  ports:
  - port: 80
    targetPort: 9090

Ahora ya estamos listos para levantar la interfaz gráfica, para lo que usamos los siguientes comandos:

shell
kubectl create -f dashboard-controller.yaml
kubectl create -f dashboard-service.yaml

Al igual que antes, para poder acceder desde fuera tenemos que mapear el puerto en el fichero de configuración de Vagrant y hacer el comando vagrant reload. ¡Después accedemos por el navegador, y ya tenemos el dashboard preparado!.


7. Conclusiones

Espero que hayamos cubierto algunas de las características principales de la gestión de kubernetes, así como que seamos capaces de crear nuestros propios pods, replication controllers y services para ser capaces una buena infraestructura que nos permita manejar nuestros servicios de una forma amigable.


8. Referencias

Google Guava: colecciones con programación funcional en Java 6/7

$
0
0

No hace falta disponer de Java8 para que puedas usar la Programación Funcional a las colecciones. Usando la librería Guava de Google es posible. En este tutorial te lo contamos.

0.Índice de contenidos


1.Introducción

Como seguro que ya sabes, una de las novedades de Java8 es la posibilidad de emplear la programación funcional para tratar con, entre otras cosas, listas en Java.

(Si quieres saber más sobre Java8 te recomiendo que te consultes este tutorial sobre lambdas en Java8, o mucho mejor, te apuntes a nuestro curso de Java 8)

Pero no todo el mundo tiene la posiblidad de trabajar sobre un proyecto Java8. Lo más normal es que los que toman las decisiones no se arriesguen a utilizar la última versión de Java (mal hecho) y nos tengamos que conformar con versiones anteriores como Java6 o Java7 que no tienen estas capacidades. Si estás en esta situacion no te preocupes, hay salida para todo :).

Google puso hace tiempo a disposición de la comunidad una librería de utilidades para Java llamada Guava (sí, G(J)ava), que permite, entre otras cosas, recorrer y tratar Colecciones de una forma más o menos funcional: sin usar bucles para entendernos. Y claro está, es compatible con Java6 y Java7, asi que no tienes excusa para pasarte al lado funcional, aunque sea de forma un poco artificial.

2. Entorno

Para realizar este tutorial se ha empleado el siguiente entorno de desarrollo:

  • Hardware: Mac Book Pro 15″ Intel Core i7 2,8 GHz, 16 GB RAM.
  • Sistema Operativo: Mac OS X El Capitán.
  • Software:Java: 1.7.43; maven 3 y Git si quieres probar los ejemplos.

No obstante, cualquier máquina en la que ejecutes con Java es suficiente.

3. Instalación

Vamos a partir de un proyecto de Maven, así que simplemente deberemos expresar la dependencia de Guava en el pom.xml

Para encontrar la dependencia vamos a MavenCentral y buscamos la librería de Guava. Nos indicará este artefacto: http://mvnrepository.com/artifact/com.google.guava/guava. En el momento de escribir este tutorial, la última versión era la 19, así que la elegimos. El código que habrá que incluir en el pom.xml en el apartado de dependencies es el siguiente:

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>19.0</version>
</dependency>

Para esta demostración también vamos a usar test, asi que aprovechamos y buscamos la dependencia de JUnit4 para poder insertarla. La puedes sacar de aquí:http://mvnrepository.com/artifact/junit/junit/4.12. No obstante todo el código que se expone en este tutorial lo puedes encontrar en un repositorio de mi cuenta de GitHub: https://github.com/4lberto/guavaLists por si quieres experimentar por tu cuenta.

4. Conceptos iniciales: Predicados y Funciones

Antes de ponernos a recorrer listas tenemos que conocer dos conceptos básicos que maneja esta librería y que le permiten modelar el paradigma de la programación funcional: los predicados y las funciones.

Básicamente lo que hace Guava es tomar una lista y aplicar o bien predicados o bien funciones que nosotros hemos diseñado. Es como una encapsulación de operación básica que se aplica a cada elemento de la lista para ver que cumple una condición o transformarlo.

Si estás familiarizado con los patrones de diseño de la programación orientada a objetos, podríamos decir que es una especie de patrón strategy en el cual se extrae la operación a una clase específica (la función o procedimiento), que un iterador especial se dedica a aplicar a cada elemento.

Mejor lo vemos en código:

4.1. Predicados

Un predicado es un método que recibe un objeto por parámetro y devuelve un valor de tipo boolean (true o false). Así de simple.

Esta es su interfaz:

Interface Predicate boolean apply(T)

Y esto es un predicado que dado un número entero dice si es mayor que 10:

final Predicate<Integer> predicateGreaterThan10 = new Predicate<Integer>() {
	@Override
	public boolean apply(final Integer input) {
		return input > 10;
	}
};

¿Para qué se utilizan los predicados?

Se aplican a los elementos de una colección o algo que sea iterable, y decidir si cumplen una condición o no. De este modo se puede decidir sobre si se incluyen en el resultado de la operación o no. Por ejemplo:

  • Si un número es par.
  • Si un cadena de texto tiene más de una longitud determinada.
  • Si un objeto tiene un parámetro que cumple la condición.
  • Si un elemento es nulo.
  • Si un elemento está en una lista

Así, si tenemos una lista con objetos, podremos filtrarlos por el cumplimiento o no de un atributo. Nos será muy útil para descartar elementos y crear listas nuevas que solamente tengan los objetos que nos interesan.

4.2. Funciones

Las funciones son métodos que toman un objeto de entrada y producen un objeto de salida.

Interface Function T apply(F)

Esto es una función que dada una cadena de texto, elimina el último caracter, devolviendo una nueva cadena de texto a la salida:

final Function functionRemoveLastChar = new Function() {
	@Override
	public String apply(final String input) {
		return input.substring(0, input.length() - 1);
	}
};

¿Para qué se utilizan las funciones?

Son las operaciones de transformación de una lista de objetos en otra lista de objetos. Así se puede por ejemplo:

  • Sumar una determinada cantidad a cada elemento.
  • Simplificar un objeto para convertir la lista en una lista de identificadores de objetos o un campo que nos interse para hacer una operación posterior.
  • Completar objetos o convertirlos en otros, generando una lista nueva más completa.
  • O incluso si la salida de la función es un Boolean, tendremos una especie de predicado.

Una lista a la que se le aplica una función genera una lista nueva de igual longitud. Ya podremos aplicarles predicados u otras funciones especiales para recortarlas si fuera necesario.

Más adelante veremos las funciones y los predicados en más detalle:

5. Aplicando Programación Funcional a las colecciones: FluentIterable

Ya sabemos de qué van los predicados y las funciones. Ahora sólo queda aplicarlas.

Hay diferentes formas de hacerlo en Guava, empleando los diferentes mecanismos como las clases de utilidades com.google.common.collect.Lists o com.google.common.collect.Collections2 por ejemplo.

Pero para facilitar el manejo en Guava tenemos com.google.common.collect.FluentIterable<E> que nos da una interfaz común a los métodos más usados (realmente en su código luego hace referencia a estas librerías). Además, esta clase está preparada para operar como una Interfaz fluida para que el código sea más legible

A FluenIterable simplemente se le indica a través del método .from() el iterable sobre el que se va a operar, y a continuación las operaciones que se le aplican, siempre siguiente su API fluida. En su propia documentación nos indican un ejemplo:

FluentIterable
.from(database.getClientList())
.filter(activeInLastMonth())
.transform(Functions.toStringFunction())
.limit(10)
.toList();

Vamos a ver con unos test de ejemplo cómo podemos usarla. Antes unas advertencias:

* Puedes descargar los ejemplos y jugar con ellos desde: https://github.com/4lberto/guavaLists.

** Antes de cada test hay una lista de Strings generada con clases de utilidad llamada listOfStrings.

*** Uso test para las pruebas por simpleza a la hora de probar los ejemplos y comprobrar resultado. Obviamente el código explicado se puede emplear en cualquier clase de Java.

5.1. Dada una lista desordenada de Strings, devuelve una lista con los elementos que comienza por “A”

@Test
public void selectStarsWithA_UsingGuava() {

	// given
	final Predicate<? super String> selectStartsWithA = new Predicate<String>() {
		@Override
		public boolean apply(final String word) {
			return word.toLowerCase().startsWith("a");
		}
	};

	// when
	final List<String> stringsStartsWithA = FluentIterable.from(listOfStrings).filter(selectStartsWithA).toList();

	// then
	assertTrue(stringsStartsWithA.get(0).toLowerCase().startsWith("a"));
	assertTrue(stringsStartsWithA.get(stringsStartsWithA.size() - 1).toLowerCase().startsWith("a"));
}

Como podrás ver se ha generado un Predicado que se aplica a cada elemento de entrada, que es de tipo String. El predicado devuelve un boolean, que será true si la palabra empieza por “a” y falso en otro caso.

final Predicate<? super String> selectStartsWithA = new Predicate<String>() {
	@Override
	public boolean apply(final String word) {
		return word.toLowerCase().startsWith("a");
	}
};

A continuación, y aquí está la potencia de la programación funcional, se aplica sobre la entrada, que es “listOfStrings” y que ha sido generada anteriormente (bajate los fuentes para verlo).

Gracias a la API Fluida de FluenIterable, cualquiera puede ver leyendo en inglés lo que hace:

final List<String> stringsStartsWithA = FluentIterable.from(listOfStrings).filter(selectStartsWithA).toList();

Es decir, partiendo de listOfStrings, aplica un filtro, que será el predicado y la salida la convierte en una lista, que además será de tipo com.google.common.collect.ImmutableList, que es un tipo de lista inmutable de alto rendimiento (mejor para el sincronismo y la programación funcional).

Luego aplico unos asserts para comprobar que da el resultado que quiero. Como he dicho antes, uso test para que cuadre en la demo, pero se podría usar en cualquier lugar.

5.2. Devolver la primera palabra de una lista que empiece por la letra “j”

Gracias a la API fluida de FluentIterable podemos hacerlo fácilmente. Simplemente creamos un predicado y lo aplicamos. A la salida en vez de una lista le decimos que nos devuelva un único elemento con .get

@Test
public void firstWithJ_UsingGuava() {
	final Predicate<? super String> startsWithJPredicate = new Predicate<String>() {
		@Override
		public boolean apply(final String word) {
			return word.toLowerCase().startsWith("j");
		}
	};

	final String firstStartsWithJ = FluentIterable.from(listOfStrings).firstMatch(startsWithJPredicate).get();

	assertTrue(firstStartsWithJ.toLowerCase().startsWith("j"));
}

Una de las ventajas de tener un predicado (o función) fuera del recorrido de los elementos, es poder reutilizar el código del predicado. Imagina que puedes tener una factoría de predicados o funciones separada y utilizarla a lo largo de tu código en cualquier lugar.

5.3. Añadir un sufijo a cada palabra de una lista

Simplemente creando una función que dado un String nos devuelva el String más el sufijo podemos hacerlo fácilmente. Luego la función se aplica a una lista de Strings que tenemos preparada:

@Test
public void addSufix_UsingGuava() {

	//given
	final Function<String, String> addSufix = new Function<String, String>() {
		@Override
		public String apply(final String word) {
			return word.concat(SUFIX);
		}
	};

	//when
	final List<String> listOfStringsWithSuffix = FluentIterable.from(listOfStrings).transform(addSufix).toList();

	//then
	assertTrue(listOfStringsWithSuffix.get(0).endsWith(SUFIX));
}

Fijate que ahora se ha empleado la operacion .transform(function) para que transforme los elementos de la lista, y la salida se ha pedido que sea una lista con .toList().

5.4. Ordernar una lista de palabras

No puede ser más fácil y expresivo con la operación .toSortedList pasando por parámetro otra utilidad de Guava, en este caso Ordering.natural(), que devuelve un Comparable preparado para devolver los elementos ordenados de modo natural (así no tenemos que implementar una clase anónima al vuelo)

@Test
public void order_UsingGuava() {
	//given

	//when
	final List<String> result = FluentIterable.from(listOfStrings).toSortedList(Ordering.natural());

	//then
	assertTrue(result.get(0).compareTo(result.get(listOfStrings.size() - 1)) < 0);
}

Como el resultado es de tipo ImmutableList<E> entonces podemos aplicar otro método de la API fluida de este miembros de Guava, como por ejemplo subList(0,10) para seleccionar los 10 primeros elementos.

final List result = FluentIterable.from(listOfStrings).toSortedList(Ordering.natural()).subList(0, 10);

Si lo hubiéramos hecho dentro de un fluentIterable, podríamos haber empleado .limit(10) para obtener sólo 10 elementos de la lista.

5.5. Predicado parametrizado en clase anónima: Intersección de listas

Quizá estos ejemplos te hayan contentado, pero puede que haya una cosa que no te termine de cuadrar: los predicados sólo admiten elementos de la lista como entrada, y las funciones igual. Si queremos complicar las condiciones admitiendo otros parámetros… ¿cómo podemos hacerlo?

En este ejemplo queremos que, dada una lista de Strings, nos devuelva otra lista con aquellos elementos que están en otra lista. Para ello definiremos el predicado para que tenga acceso a las variables del scope del método en el que se define.

De este modo, dentro de la definición del predicado y de su método apply, haremos uso de variables que no están definidas de forma explícita en el predicado. Mejor con código:

@Test
public void getElementsMatchingInAList_UsingGuava() {

	// given
	final List<String> listOne = Arrays.asList("Uno", "dos", "tres", "cuatro");
	final List<String> listTwo = Arrays.asList("dos", "tres");

	final Predicate<String> predicado = new Predicate<String>() {
		@Override
		public boolean apply(final String input) {
			return listTwo.contains(input);
		}
	};

	// when
	final List<String> listMatching = FluentIterable.from(listOne).filter(predicado).toList();

	// then
	assertEquals(listMatching.size(), 2);
	assertEquals(listMatching.get(0), "dos");
	assertEquals(listMatching.get(1), "tres");
}

En este ejemplo, dentro del predicado se hace referencia a listTwo, que es una variable a nivel de método del test. Al definirse la clase anónima que implementa la interfaz Predicate de Guava, por la especificación del scope del Java Language Specification, se puede hacer uso de ello.

@Override
public boolean apply(final String input) {
	return listTwo.contains(input);
}

De nuevo, gracias a FluenIterable de Guava, aplicando un predicado con filter y pidiendo que el resultado sea una lista con toList, se simplifica bastante el matching de ambas listas para encontrarr la intersección.

Pero siempre hay algo que objetar. Quizá eres de la idea inicial que comentaba más atrás se crear unas funciones/predicados que fuesen más o menos reutilizables. En este caso, nuestro predicado únicamente se puede emplear dentro de ese test, ya que tiene una dependencia clara de la lista anterior. Vamos a ver alguna alternativa.

5.6. Predicado parametrizado en nueva clase: Intersección de listas

Un predicado en Guava se trata simplemente de implementar la interfaz Predicate, asi que no hay nada que nos impida darle un poco más de funcionalidad con tal de que el apply siga funcionando tal y como figura en el contrato de la interfaz.

Ahora creamos una clase estática dentro de la clase de tests. No hace que falta que sea estática per se, sino que para simplificar no voy a crear un fichero Java nuevo, de modo que la introduzco dentro de la clase de test. Sácala a un fichero nuevo si crees conveniente:

public static class PredicateMatchingList implements Predicate<String> {

	public PredicateMatchingList(final List<String> listWithElementsToBeMatched) {
		super();
		this.listWithElementsToBeMatched = listWithElementsToBeMatched;
	}

	private final List<String> listWithElementsToBeMatched;

	@Override
	public boolean apply(final String input) {
		return listWithElementsToBeMatched.contains(input);
	}
}

Ásí tenemos nuestro nuevo predicado creado llamado PredicateMatchingList. Así podremos reutilizarlo en otros desarrollos de iterable. El test ahora quedaría así:

@Test
public void getElementsMatchingInAListPredicateOutSide_UsingGuava() {

	// given
	final List<String> listOne = Arrays.asList("Uno", "dos", "tres", "cuatro");
	final List<String> listTwo = Arrays.asList("dos", "tres");

	final Predicate<String> predicado = new PredicateMatchingList(listTwo);

	// when
	final List<String> listMatching = FluentIterable.from(listOne).filter(predicado).toList();

	// then
	assertEquals(listMatching.size(), 2);
	assertEquals(listMatching.get(0), "dos");
	assertEquals(listMatching.get(1), "tres");
}

Aún podemos darle una vuelta de tuerca más generando predicados con un método Factory, más acorde con las buenas prácticas de desarrollo.

Por cierto, en la clase com.google.common.base.Predicates hay factorías para este tipo de predicados, como por ejemplo in(Collection<? extends T> target), que nos fabricaría un predicado similar al anterior.

Simplemente cambiaríamos la forma de obtener el predicado por:

final Predicate<String> predicado = Predicates.in(listTwo);

5.7. Predicado parametrizado desde un método Factory: números mayor que el parámetro.

Este ejemplo es similar al anterior. Queremos un predicado que nos diga si el elemento sobre el que se aplica es mayor que un número dado por parámetro. Ya sabes las otras dos alternativas anterior. Vamos ahora con un método fáctory que nos genere el predicado.

public static Predicate<Integer> getPredicateBiggerThanFactory(final int number) {
	return new Predicate<Integer>() {
		@Override
		public boolean apply(final Integer input) {
			return input > number;
		}
	};
}

Si te fijas bien, es una especie de mezcla entre crear una clase con un constructor con el parámetro y otra de tomar la variable del scope: el valor “number” se aplica dentro del nuevo predicado generado porque forma parte del scope del método factory.

Aplicarlo es tan fácil como esperamos:

@Test
public void predicateBasedOnFactory_UsingGuava() {
	// given
	final Predicate<Integer> predicateBiggerThan73 = getPredicateBiggerThanFactory(73);

	final int numberBiggerThan73 = 89;

	// when
	final boolean isBiggerThan73 = predicateBiggerThan73.apply(numberBiggerThan73);

	// then
	assertTrue(isBiggerThan73);
}

Por cierto, estos ejemplos para predicados también son perfectamente aplicables a las funciones de Guava (y a cualquier cosa que implemente una interfaz, claro) :).

5.8. Suma de precios de los 2 coches más potentes

Seguro que has oído hablar de map-reduce en bases de datos NoSQL, que al final aplican funciones a listados de datos.

  • Map: se encarga de seleccionar y transformar los elementos buscados. Por ejemplo ordena los coches por potencia y devuelve los dos primeros de la lista.
  • Reduce: realiza una operación de “resumen”, como por ejemplo la media o una suma. En nuestro caso será la suma del precio.

Ya sabes que lo que estamos haciendo hasta ahora se corresponde con el Map, pero nos falta una operación que haga “reduce”, es decir, que recorra los elementos de una lista y haga una operación acumulativa que devuelva un único resultado. ¿Cómo se hace en Guava?.

La respuesta es… No se puede hacer en Guava la operacion reduce a menos que la implementemos nosotros.

Y la forma de implementarlo nosotros es a través de un tradicional bucle que recorra el resultado del Map. O si somos más sofisticados hagamos nuestra propia operación reduce que tome una lista y una operación de dos elementos como parámetro y lo aplique en parejas: el 1 con el 2, el resultado al 3, y así sucesivamente.

Así pues, nosotros aplicaremos un bucle para conocer el precio acumulado de los coches más potentes:

@Test
public void getSumOfPriceOfTheMostPowerfullCarsGiven_UsingGuava() {

	// given
	final Comparator<Car> comparatorHp = new Comparator<Car>() {
		@Override
		public int compare(final Car o1, final Car o2) {
			return o2.getHp() - o1.getHp();
		}
	};

	// when
	final List<Car> sortedListOfCars = FluentIterable.from(listOfCars).toSortedList(comparatorHp).subList(0, 2);

	int accumulatedPrice = 0;
	for (final Car coche : listOfCars) {
		accumulatedPrice += coche.getPrice();
	}

	// then
	assertTrue(accumulatedPrice > (listOfCars.get(0).getPrice() + listOfCars.get(1).getPrice()));
}

Al menos Guava nos ha hecho la vida más sencilla para ordenar y sacar los dos coches más potentes… pero no es una librería tan potente como la parte de streams de Java8 :(

.

6. Extra: Predicados y Funciones extendidos

Como hemos visto, al final Guava, y la programación funcional, nos permite desacoplar en funciones y predicados la lógica que se aplica a los elementos, lo cual es una gran ventaja frente a los bucles.

En esta sección “extra” vamos a ver algunas curiosidades de las funciones y predicados que nos pueden ser de utilidad.

6.1. Extra Predicados

La clase com.google.common.base.Predicates contiene algunos predicados generales prefabricados, como por ejemplo:

6.1.1. Predicados básicos: true, false

Serán utilizables por ejemplo en operaciones de composición booleana de predicados. Así tenemos:

final Predicate<String;> predicateFalse = Predicates.alwaysFalse();
final Predicate<String;> predicateTrue = Predicates.alwaysTrue();

6.1.2. Predicado para filtar por clase

Si tenemos la necesidad de, dada una lista, quedarnos con los elementos que son de una clase determinada (o sus descendientes), podemos aplicar Predicates.assignableFrom.

En este ejemplo filtramos por clases que extiendan la clase java.lang.Number:

@Test
public void predicateAssignableClass_UsingGuava() {
	// given
	final Predicate<Class<?>> predicateFromNumber = Predicates.assignableFrom(Number.class);

	final Class<?> integerClass = Integer.class;
	final Class<?> stringClass = String.class;

	// when
	final boolean resultFromInteger = predicateFromNumber.apply(integerClass);
	final boolean resultFromString = predicateFromNumber.apply(stringClass);

	// then
	assertTrue(resultFromInteger);
	assertTrue(!resultFromString);
}

También existe el predicado Predicate.instanceOf para una clase exacta.

6.1.3. Composición de Predicados

Ya sabemos que un predicado se aplica a una clase y devuelve un boolean. Parece lógico que se puedan combinar predicados simples para generar predicados más complejos con operaciones booleanas de composición: or, and, not.

A continuación vemos un ejemplo en el que hay dos predicados: uno que evalúa que un Integer sea mayor que 10 y otro que evalúa que sea menor que 20. La combinación de ambos con una operación “and” debería darnos un predicado que fuese verdadero cuando el número proporcionado esté entre 10 y 20:

@Test
public void predicateOr_UsingGuava() {
	// given

	final Predicate<Integer> predicateGreaterThan10 = new Predicate<Integer>() {
		@Override
		public boolean apply(final Integer input) {
			return input > 10;
		}
	};

	final Predicate<Integer> predicateSmallerThan20 = new Predicate<Integer>() {
	@Override
		public boolean apply(final Integer input) {
			return input < 20;
		}
	};

	final Predicate<Integer> predicateBetween10and20 = Predicates.and(predicateGreaterThan10,
	predicateSmallerThan20);

	final Integer numberBetween10and20 = 15;
	final Integer numberBiggerThan20 = 25;
	final Integer numberSmallerThan10 = 5;

	// when
	final boolean resultExpectedOk = predicateBetween10and20.apply(numberBetween10and20);
	final boolean resultExpectedKo1 = predicateBetween10and20.apply(numberBiggerThan20);
	final boolean resultExpectedKo2 = predicateBetween10and20.apply(numberSmallerThan10);

	// then
	assertTrue(resultExpectedOk);
	assertTrue(!resultExpectedKo1);
	assertTrue(!resultExpectedKo2);
}

De igual modo existe la generación de predicados a partir de las operaciones “or” y “not”.

Simplemente ten en cuenta que se puede ampliar el campo de cosas que se pueden hacer con los predicados y por tanto con Guava.

6.2. Extra Funciones.

Igual que los predicados, existe com.google.common.base.Functions, que tiene métodos estáticos de factoría para crear funciones a partir de parámetros.

Como las funciones reciben una clase de entrada y dan otra clase de salida, no tienen esas operaciones booleanas de los Predicados, pero tienen alguna función interesante:

6.2.1. Composición de Funciones

Como en las matemáticas: si F(A)->B y F(B)->C, hay una composición de funciones que nos dará F(A)->F(C). Esto se puede hacer con el método estático compose(Function<B,C> g, Function<A,? extends B> f).

Se tengo una función que elimina el primer caracter de una cadena de texto y otra que elimina el último caracter, con la composición tendré una función que elimina el primer y el último caracter de una cadena de texto. Aquí vemos un ejemplo:

@Test
public void functionsComposition_UsingGuava() {

	// Given
	final Function<String, String> functionRemoveFirstChar = new Function<String, String>() {
		@Override
		public String apply(final String input) {
			return input.substring(1);
		}
	};

	final Function<String, String> functionRemoveLastChar = new Function<String, String>() {
		@Override
		public String apply(final String input) {
			return input.substring(0, input.length() - 1);
		}
	};

	final Function<String, String> functionComposite = Functions.compose(functionRemoveFirstChar,	functionRemoveLastChar);

	final String aStringToRemoveExtrems = "String";
	final String expectedResult = "trin";

	// when
	final String result = functionComposite.apply(aStringToRemoveExtrems);

	// then
	assertEquals(expectedResult, result);
}

6.2.2. Función asignación a través de un Mapa

Finalmente vamos a ver una función de la factoría de com.google.common.base.Functions que me parce muy interesante: permite transformar el objeto de entrada en uno de salida, pero la regla de transformación está determinada en un mapa. En este mapa, las claves (keys) son los obejtos de entrada, y los valores (values) los de salida.

Para ello se emplea forMap(Map<K,? extends V> map, V defaultValue), que recibe el mapa de corrspondencias por parámetro y un valor en el caso de que no se encuentre la entrada.

En el siguiente ejemplo se transforman los códigos de locale (en, es, rus) en el idioma correspondiente (inglés, español, ruso)…

@Test
	public void functionForMap_UsingGuava() {
	// given
	final Map<String, String> languageMap = new HashMap<>();
	languageMap.put("es", "Español");
	languageMap.put("en", "Inglés");
	languageMap.put("ru", "Ruso");
	languageMap.put("it", "Italiano");

	final List<String> listOfLocale = Arrays.asList("ru", "es", "ch");
	final String defaultValue = "Unknow";

	final Function<String, String> getElementFunction = Functions.forMap(languageMap, defaultValue);

	// when
	final List<String> listOfIdiom = FluentIterable.from(listOfLocale).transform(getElementFunction).toList();

	// thenassertTrue(resultGreaterThan10);
	assertEquals(listOfLocale.size(), listOfIdiom.size());
	assertEquals(listOfIdiom.get(0), "Ruso");

	assertEquals(listOfIdiom.get(listOfIdiom.size() - 1), "Unknow");
}

6.2.3. Función desde un predicado

Finalmente como hemos ido viendo a lo largo de este tutorial, sabemos que un predicado es una función que devuelve un boolean, así que es posible crear una función a partir de un predicado con Functions.forPredicate. Muy sencillo el ejemplo:

@Test
public void functionSameAsPredicate_UsingGuava() {

	// given
	final Predicate<Integer> isGreaterThan10 = new Predicate<Integer>() {
		@Override
		public boolean apply(final Integer input) {
			return input > 10;
		}
	};

	final Function<Integer, Boolean> functionIsGreaterThan10 = Functions.forPredicate(isGreaterThan10);

	final Integer numberGreaterThan10 = 25;
	final Integer numberSmallerThan10 = 8;

	// when
	final boolean resultGreaterThan10 = functionIsGreaterThan10.apply(numberGreaterThan10);
	final boolean resultSmallerThan10 = functionIsGreaterThan10.apply(numberSmallerThan10);

	// then
	assertTrue(resultGreaterThan10);
	assertTrue(!resultSmallerThan10);
}

7. Conclusiones

La programación funcional está cambiando la forma de programar en los últimos años. Como consecuencia Java ha tenido que implementar este tipo de programación aplicada a streams y lambdas en la versión Java8. Desafortundamente no todo el mundo tiene la posibilidad de trabajar con Java8 y tiene que conformarse con Java6 o Java7. Para estos casos se puede emplear la librería Guava de Google.

Guava nos proporciona algunas utilidades para iterar sobre colecciones de objetos (listas, conjuntos…) de una forma similar a la programación funcional. Para ello utiliza una API fluida en la que se indican funciones y predicados que se aplican a los elementos de las colecciones para poder transformar y filtrar respectivamente. De este modo podemos desarrollar nuestro código de una forma similar a Java8 con streams y lambdas, aunque no en todo su esplendor, ya que tiene algunas lagunas como por ejemplo la operación reduce.

Comentando el libro ‘Creatividad S.A.’ de Ed Catmull

$
0
0

Hoy comparto mi comentario del libro Creatividad S.A., donde el presidente de Pixar y Disney Animation nos cuenta su experiencia al frente de estas empresas. Su discurso se centra en cómo crear, preservar y evolucionar la cultura creativa en una organización.

Desde hace un tiempo mi cabeza le viene dando vuelta a un tema: Creatividad. Este interés lo ha motivado una nueva rama dedicada a materializar ideas en productos novedosos que está creciendo en la empresa en la que trabajo, Autentia.

Por este motivo empecé a plantearme una serie de preguntas:

  • ¿De dónde surgen las ideas?
  • ¿Cómo crece una idea hasta convertirse en algo real?
  • ¿Cualquiera tiene la capacidad de ser creativo?
  • ¿Qué dificultades tiene que afrontar un proceso creativo?
  • ¿Cómo funcionan las empresas dedicadas a crear lo que no existe?

Para responder estas preguntas me estoy ayudando de los libros con los que me topo en el camino. El que aquí nos ocupa, Creatividad, S.A., Cómo llevar la inspiración hasta el infinito y más allá de Ed Catmull , nos permite conocer la manera en la que la empresa presidida por el autor, Pixar Animation, ha creado, mantenido y ha hecho evolucionar una cultura creativa que les ha llevado a ser la referencia en el mundo de la animación digital.

Hay muchos motivos por los que os podría recomendar este libro:

  • Se plasma la experiencia de Ed Catmull al frente de una empresa de éxito.
  • Expone las diferentes acciones que se llevan a cabo en Pixar para proteger la cultura creativa.
  • Nos avisa de que los problemas van a aparecer constantemente y que un gran porcentaje de ellos serán problemas ocultos difíciles o imposibles de predecir.
  • Habla del cambio, muchas veces visto como el enemigo cuando por el contrario le deberíamos tender la mano como si de un hermano se tratase.
  • Nos muestra cómo conseguir hacer realidad lo que no existe, a través de una idea que nace, que tras un enorme trabajo colectivo madura.
  • Y si tienes curiosidad por conocer la visión que tiene de Steve Jobs una persona que ha sido cercana a él encontrarás detalles interesantes en este libro.

Desde el punto de vista práctico, el objetivo principal del libro es mostrar al lector cómo crear una cultura creativa sostenible en una empresa para lograr la excelencia en los productos creados. No por esto debemos pensar que los destinatarios de estos consejos sean únicamente aquellos que ocupen puestos en la cúpula de las empresas, también es recomendable para cualquiera que tenga la capacidad de proponer cambios en su lugar de trabajo, así como a aquellos emprendedores que quieran recibir un poco de inspiración.

Intentaré resumir y generalizar los consejos y metodologías que el autor plasma en Creatividad S.A. Veréis que estas técnicas se pueden aplicar en cualquier contexto en el que el objetivo final sea crear un producto novedoso y con elevados niveles de calidad.

Cómo crear cultura creativa sostenible en la empresa

Idea que podemos definir como el objetivo fundamental del libro. Estas son algunas de las medidas que nos propone el autor si queremos construir una empresa con una cultura creativa sana:

  • Rodearse de gente con talento y con autodisciplina que sean capaces de formar parte de un entorno colaborativo y con objetivos comunes.
  • Promover la innovación y creatividad tecnológica.
  • Organizar la empresa en base a una estructura horizontal, cualquiera debería expresarse libremente independientemente de su posición en la empresa.
  • Fomentar el intercambio de información.
  • Disponer de instalaciones que promuevan la creatividad.
  • Ser capaces de asumir riesgos.
  • Estudiar los fallos cometidos.
  • Resolver los problemas en cuanto aparezcan, sin clasificarlos por importancia.
  • Formación interna, también en disciplinas no relacionadas con la labor profesional, de esta manera se consigue recuperar la mente de principiante.
  • Pilares básicos de esta cultura: honestidad, excelencia, comunicación, originalidad y autoafirmación.

Materializar ideas

Estos son algunos de los consejos y afirmaciones recogidos en este libro acerca de la manera en la que se debe proteger una idea creativa para que madure y se convierta en un producto de éxito:

  • La idea madura como resultado del trabajo duro.
  • Proteger la idea de quien pueda tirarla abajo antes de tiempo.
  • Mantener el equilibrio entre las necesidades creativas y comerciales. Es importante rentabilizar, pero si se racionaliza en exceso se resiente el producto final.
  • No perder la confianza de tu gente, es mejor tomar un camino de manera confiada y si se detecta que es equivocado decirlo abiertamente y tomar otro distinto.
  • Estar preparado para la inevitabilidad de los problemas.

Cuando nace una idea, no se tiene definido el rumbo que tomará hasta convertirse en un producto de calidad, por lo que habrá que ir averiguando cuáles son los caminos que debe de ir tomando el equipo de desarrollo para lograrlo. Esto genera incertidumbre y miedo, para combatirlos, Ed Catmull nos sugiere el uso de modelos mentales, tales como la Pirámide, el Camaleón, el Túnel, etc. En el libro podréis ver en qué consiste cada uno.

Abrazar el cambio

El cambio es algo que no podemos evitar, está presente en todos los aspectos de nuestra vida, y tenemos no sólo que aprender a vivir con él, sino sentirnos afortunados de convivir con él. Ed Catmull es consciente de esto y por tanto, el cambio recibe muchas alusiones en Creatividad S.A. Sin el cambio ni se crece ni se consigue el éxito.

Uno de los obstáculos que toda empresa, independientemente del tipo que sea, tiene que afrontar, es la resistencia al cambio, tal y como señala el autor. La gente se siente cómoda tratando con lo que ya conoce y haciendo las cosas de la manera la en que siempre las ha hecho. Las ideas novedosas pueden generar la necesidad de realizar cambios en los procesos para conseguir un producto de alta calidad, y debemos de estar preparados para lidiar con la resistencia natural que el cambio produce en las personas.

En ocasiones se ve el cambio continuo como símbolo de debilidad, pero el autor ve más peligroso el no cambiar nunca de rumbo.

Éxito y fracaso

El autor recalca la importancia de reconocer los factores que han hecho conseguir el éxito, entre estos factores tiene en cuenta el azar, y dar importancia a las cosas que no hemos podido controlar. Cuando se tiene éxito no hay que caer en vanagloriarse, y pensar en lo bueno soy, hay que estudiar cuidadosamente lo que nos ha hecho llegar hasta él y las maneras en las que hay que evolucionar para volver a conseguirlo tras el siguiente proyecto.

Ed Catmull también dedica parte del libro a analizar el fracaso. Lo importante para él, es tomarlo como experiencias de aprendizaje, que a pesar de ser doloroso, debemos sacar la parte positiva, hemos intentando ser originales. Si no se cometen fallos y se continúa trabajando para lograr el objetivo: un producto excelente, no aprenderás, o aprenderás más lentamente. Aún así, también advierte que si se está trabajando en algo que da señales de que no acabará consiguiendo el éxito esperado, es mejor fracasar de manera temprana y abandonar el proyecto, que producir un producto de baja calidad y falto de originalidad.

Honestidad y franqueza

Uno de los pilares de Pixar, es la franqueza. El presidente de la compañía nos cuenta las diferentes maneras en las que se trabaja para mantenerla intacta. Uno de los ejercicios de franqueza que se realizan en la empresa es conocido como el Braintrust:

  • Reuniones en las que se muestran resultados parciales del producto.
  • Se intenta encontrar problemas y soluciones, estando en manos del director del proyecto aplicar los cambios que considere más oportunos.
  • Ayudan a mejorar el producto, con críticas constructivas en las que se pone el foco exclusivamente en el proyecto.
  • No gana ni pierde nadie, el objetivo es que siempre salga beneficiado el producto final.

Para evitar la disminución en la franqueza detectada por los directivos a medida que crecía la empresa, se creó lo que denominaron el Día de las Notas:

  • Día en el que no se trabaja, no hay visitas y los empleados dan su opinión de cómo mejorar la empresa.
  • Los organizadores de este día reciben multitud de correos de temas a debatir que luego clasifican y dividen en sesiones a las que se apuntan los interesados en ellas.
  • Las sesiones son presididas por un moderador.
  • Las propuestas se plasman en unos formularios de resultados.
  • Los máximos responsables de la empresa estudian los resultados y toman las decisiones oportunas para llevar a cabo las propuestas si procede.

Lo que he contado aquí es un resumen de algunas de las ideas que considero más relevantes del libro, de las que seguro me he dejado algunas. Pero esto tiene solución, os recomiendo de verdad la lectura de este libro, ya que, no sólo es tremendamente interesante, sino que la lectura es realmente amena.


Cómpralo en Amazon

En mi opinión, no debemos tomar estos consejos y metodologías al pie de la letra para luego forzar su implantación en nuestra empresa. No todo va a ser igual de bueno para todos, aunque sí que es cierto que estas ideas pueden servir de inspiración.

Para mí, la idea que subyace en el libro es que cada empresa debe encontrar su propio camino, aplicando las técnicas que mejor le funcionen para conseguir una cultura creativa sana, siendo estas técnicas objeto de un estudio continuo que las someta a una evolución constante. Lo primordial es mantener y reforzar los valores de honestidad, franqueza, colaboración, trabajo duro, humildad, originalidad y disciplina que harán alcanzar la meta de la excelencia, y con ella, el éxito, ¿de eso se trata no?

Seguiremos hablando de libros en los que la Creatividad sea el tema principal o uno de los importantes, así que ¡nos vemos pronto!


Dependencia de roles y gestión de errores con Ansible

$
0
0

En este tutorial veremos cómo gestionar las dependencias de los roles de un playbook de ansible, y como configurar una tarea para que sea idempotente en múltiples ejecuciones del playbook.

Índice de contenidos


1. Introducción

Recientemente he leído Ansible up and running: Automating configuration management and deployment the easy way de Lorin Hochstein en el que he aprendido algunas cosas chulas que quería compartir con vosotros. Vamos al lío 😀


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11.2
  • Vagrant 1.8.1
  • Ansible 1.9.3

3. Instalación del entorno

La instalación del entorno es muy simple: hay que instalar Vagrant y Ansible y elegir el directorio de trabajo en el que deseamos realizar la prueba. En este tutorial explico como instalar estas herramientas.


4. Gestión de dependencias en un rol

Al crear un playbook de Ansible en mi proyecto, aprovisionabamos la máquina con los roles ordenados, de forma que las dependencias fueran en un módulo anterior al módulo que las necesitaba. Esto puede suponer un problema ya que necesitas tener un conocimiento previo del funcionamiento del aprovisionamiento para ejecutar tareas específicas.

Por ejemplo, si instalamos Sonar con PostgreSQL y tú quieres probar (o reutilizar) el módulo de Sonar en otro proyecto, necesitarías saber que depende del rol que instala PostgreSQL para su funcionamiento.

Con la gestión de dependencias de los roles lo que se pretende es justo evitar eso. Si quieres ejecutar un rol, ese rol sabe todas las dependencias que necesita para que pueda ser ejecutado fácilmente. Vamos a realizar un ejemplo donde comprobamos la gestión de dependencias del rol sonar. Para ello nos creamos nuestra carpeta Ansible dónde van tanto nuestros playbooks como nuestros roles, y nos servimos de Vagrant para provisionar las máquinas.

En el directorio elegido ejecutamos el comando Vagrant init para configurar el aprovisionamiento de nuestra máquina virtual y lo dejamos de la siguiente manera:

Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu/trusty64"

  config.vm.network "forwarded_port", guest: 9000, host: 9000

  config.vm.provider "virtualbox" do |vb|
    # Customize the amount of memory on the VM:
    vb.memory = "2048"
  end

  config.vm.define "prueba" do |prueba|
    prueba.vm.hostname = "prueba"
    prueba.vm.provision "ansible" do |ansible|
      ansible.verbose = 'vvv'
      ansible.playbook = "ansible/playbook.yml"
      ansible.inventory_path = "ansible/environments/development/inventory"
    end
  end
end

En este fichero le decimos la box de la que queremos partir, hacemos la redirección al puerto 9000 de la máquina virtual y configuramos la memoria que va a tener la máquina virtual. Por último, definimos el aprovisionamiento indicando el playbook y el inventory que vamos a utilizar.

Comenzamos creando el playbook:

ansible/playbook.yml
---

# file: playbook.yml

- hosts: sonar
  sudo: yes
  gather_facts: no
  roles:
      - sonar

Este es un playbook sencillo en el que se ejecuta sólo un rol. Vamos a crear este rol ahora 😀

ansible/roles/sonar/tasks/main.yml
---

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

- name: download sonar
  get_url: url="{{sonar.download_url}}" dest="/tmp/{{sonar.archive}}"
  tags: sonar

- name: create sonar group
  group: name=sonar state=present
  tags: sonar

- name: create sonar user
  user: name=sonar comment="Sonar" group=sonar
  tags: sonar

- name: extract sonar
  unarchive: src="/tmp/{{sonar.archive}}" dest=/opt copy=no
  tags: sonar

- name: move sonar to its right place
  shell: mv /opt/{{sonar.version_dir}} {{sonar.home}} chdir=/opt
  tags: sonar

- name: change ownership of sonar dir
  file: path="{{sonar.home}}" owner=sonar group=sonar recurse=yes
  tags: sonar

- name: copy sonar properties
  template: src=sonar.properties dest="{{sonar.home}}/conf/sonar.properties"
  tags: sonar

- name: make sonar runned by sonar user
  replace: dest="{{sonar.home}}/bin/linux-x86-64/sonar.sh" regexp="#RUN_AS_USER=(.*)$" replace="RUN_AS_USER=sonar"
  tags: sonar

- name: add sonar links for service management
  file: src="{{sonar.home}}/bin/linux-x86-64/sonar.sh" dest="{{item}}" state=link
  with_items:
    - /usr/bin/sonar
    - /etc/init.d/sonar
  tags: sonar

- name: ensure sonar is running and enabled as service
  service: name=sonar state=restarted enabled=yes
  tags: sonar

- name: create database for sonar
  postgresql_db: name="{{datasource.sonar_dbname}}" encoding=UTF-8 lc_collate=es_ES.UTF-8 lc_ctype=es_ES.UTF-8
  sudo: yes
  sudo_user: postgres
  tags:
      - sonar
      - sonardb

- name: add user to database
  postgresql_user: db="{{datasource.sonar_dbname}}" name="{{datasource.sonar_dbuser}}" password="{{datasource.sonar_dbpassword}}" priv=ALL
  sudo: yes
  sudo_user: postgres
  tags:
      - sonar
      - sonardb

Este es un rol con el que descargamos sonar, creamos su usuario y su grupo y nos aseguramos de que esté funcionando como servicio. Este rol necesita de un fichero de configuración que tenemos definido en el siguiente fichero:

ansible/roles/sonar/templates/sonar.properties
sonar.jdbc.username={{datasource.dbuser}}
sonar.jdbc.password={{datasource.dbpassword}}

sonar.jdbc.url=jdbc:postgresql://localhost:5432/{{datasource.dbname}}

sonar.jdbc.maxActive=20
sonar.jdbc.maxIdle=5
sonar.jdbc.minIdle=2
sonar.jdbc.maxWait=5000
sonar.jdbc.minEvictableIdleTimeMillis=600000
sonar.jdbc.timeBetweenEvictionRunsMillis=30000

sonar.web.context=/sonar

sonar.web.port=9000

Este fichero contiene las propiedades del Sonar, así como la configuración para que la base de datos no se cree en memoria.

Pues ya tendríamos nuestro sonar montado, aunque si nosotros tratáramos de ejecutar este rol fallaría porque le falta la configuración del idioma, el programa unzip para descomprimir el sonar y PostgreSQL. Nosotros necesitamos crear estos roles pero no queremos modificar el playbook, ya que nosotros lo que queremos instalar es Sonar. Si Sonar necesita alguna dependencia debería de ser este quien las gestionara. ¿Cómo hacemos eso? añadiendo un nuevo fichero dentro del rol sonar que sea el que conozca la dependencia:

ansible/roles/sonar/meta/main.yml
---

# file: roles/sonar/meta/main.yml

dependencies:
    - {role: locales}
    - {role: unzip}
    - {role: java8}
    - {role: postgres}

Con este fichero le estamos indicando que para poder ejecutar el rol de sonar necesita ejecutar antes los siguientes roles. La ventaja es que esta información está dentro del playbook de sonar de forma que si no tiene ese rol creado no será capaz de ejecutar sonar por falta de dependencia.

Creamos el rol de locales encargado de actualizar los idiomas y aplicarlos:

ansible/roles/locales/tasks/main.yml
---
# file: roles/locales/tasks/main.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

Instalamos Java en la máquina virtual porque es un prerrequisito de Sonar:

ansible/roles/java8/tasks/main.yml
---

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

- name: add Java repository to sources
  apt_repository: repo='ppa:webupd8team/java'
  tags: java

- name: autoaccept license for Java
  debconf: name='oracle-java8-installer' question='shared/accepted-oracle-license-v1-1' value='true' vtype='select'
  tags: java

- name: update APT package cache
  apt: update_cache=yes
  tags: java

- name: install Java 8
  apt: name=oracle-java8-installer state=latest install_recommends=yes
  tags: java

- name: set default environment variable
  apt: name=oracle-java8-set-default
  tags: java

Creamos el rol de unzip para instalarlo:

ansible/roles/unzip/tasks/main.yml
---

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

- name: install unzip command
  apt: name=unzip state=present

Creamos también el rol de postgresql:

ansible/roles/postgres/tasks/main.yml
---
# file: /roles/postgres/task/main.yml

- name: ensure PostgreSQL are installed
  apt: name={{item}} update_cache=Yes
  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

# Only for develoment
- name: ensure PostgreSQL is accesible from other hosts
  lineinfile:
      dest: /etc/postgresql/{{postgres.version}}/main/postgresql.conf
      insertafter: "#listen_addresses = 'localhost'"
      line: "listen_addresses = '*'    # Development environment!!!"

- name: ensure user can access database from other hosts
  lineinfile:
      dest: /etc/postgresql/{{postgres.version}}/main/pg_hba.conf
      line: "host    {{datasource.dbname}}           {{datasource.dbuser}}           all                     md5    # Development environment!!!"

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

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

- name: restart postgresql
  service: name=postgresql state=restarted

¡Qué no se nos olvide almacenar las variables de ejecución del playbook!

ansible/environments/ndevelopment/group_vars/sonar
---

postgres:
    version: 9.3

sonar:
    download_url: http://downloads.sonarsource.com/sonarqube/sonarqube-5.1.1.zip
    archive: sonar.zip
    version_dir: sonarqube-5.1.1
    home: /opt/sonar
    jdbc_driver: postgres
    jdbc_host: localhost
    jdbc_port: 5432

datasource:
    sonar_dbuser: sonar
    sonar_dbpassword: sonarpassword
    sonar_dbname: sonardb

Si ahora levantamos la máquina virtual veremos como primero se ejecutan las dependencias del módulo y por último se ejecuta sonar. Después podremos acceder a través de nuestro navegador preferido ejecutando localhost:9000/sonar


5. Gestión del cambio de tareas

Ahora vamos a ver otra cosa chula de ansible. Normalmente cuando ejecutamos un playbook queremos que este sea idempotente, es decir, que deje la máquina exactamente en el mismo estado independientemente de las veces que se ejecute ese playbook en el host remoto. La mayoría de los módulos de ansible soporta la idempotencia, aunque si nosotros ejecutamos comandos directamente en el servidor remoto estos no suelen ser idempotentes. Como prueba, si nosotros volvemos a provisionar la máquina que acabamos de crear, nos daría un error en una tarea de sonar precisamente porque no es idempotente. Esta tarea ejecuta el comando mv a un directorio y cuando lo realiza por segunda vez, como el directorio ya se encuentra allí, nos da un error.

Ansible también nos ofrece una solución a esto por medio de las cláusulas “changed_when” y “failed_when” donde puedes especificar de forma exacta cuando consideras que esa tarea ha fallado o ha cambiado el estado de la máquina remota. Este método es mucho menos intrusivo que poner directamente un “ignore_errors: yes” ya que nosotros decidimos qué es cambio y qué es fallo.

Para verlo en funcionamiento cambiemos la tarea “move sonar to its right place” del rol de sonar para que quede la siguiente manera:

ansible/roles/sonar/tasks/main.yml
- name: move sonar to its right place
  shell: mv /opt/{{sonar.version_dir}} {{sonar.home}} chdir=/opt
  register: result
  failed_when: '"Directory not empty" not in result.stderr'
  tags: sonar

En primer lugar necesitamos registrar la salida del comando, en nuestro caso en una variable result. Luego indicamos como condición del fallo que la tarea sea considerado un error si en la salida pone algo diferente a que el directorio no esté vacío, porque esto quiere decir que la tarea ya se realizó previamente.

Volvemos a ejecutar el aprovisionamiento de ansible (vagrant provision) y comprobamos como ahora no falla.


6. Conclusiones

Espero que este tutorial sirva para organizar mejor nuestros playbook y para gestionar los errores en tareas no idempotentes, como ejecutar comandos directamente en la máquina remota. Puedes obtener el código del ejemplo desde mi repositorio de github.


7. Referencias

Los 8 pasos de Kotter para gestionar el cambio

$
0
0

John Kotter definió una estrategia en 8 pasos para gestionar el cambio de una organización. Hoy por hoy, este sistema es ampliamente aceptado por multitud de profesionales. Este post es un resumen y explicación de las etapas que propone John Kotter para liderar la gestión del cambio empresarial.

Índice de contenidos


1. Introducción

Es indudable que los cambios se producen a nuestro alrededor. No nos queda más remedio que aceptar esta premisa como verdadera, y en ese caso hay dos caminos que podemos tomar: esperar a que los cambios se produzcan o ser impulsor de los mismos. Si eliges liderar el proceso del cambio, seguro que te interesa conocer lo que Kotter enunció hace veinte años sobre este tema.

En el ámbito del mundo empresarial, el profesor Kotter dio nombre a lo que hoy es conocido por “los 8 pasos de Kotter” para gestionar el cambio. Es objeto de este artículo explicar las fases planteadas por Kotter hasta alcanzar la implantación del cambio propuesto. Puesto que las reglas que propone, se infieren de cómo se comportan los individuos dentro del modelo de una organización, veremos que el sistema es extrapolable a otros entornos formados por colectivos estructurados jerárquicamente, aunque alejados del mundo empresarial.

John Kotter es profesor de Escuela de Negocios de Harvard y considerado el gurú en esta materia, sobre todo, desde que publicó en 1995 “Liderando el cambio” (“Leading Change”), donde exponía estas tesis.

Los 8 pasos que propone son:

  • Crear sentido de urgencia
  • Formar una coalición
  • Crear visión para el cambio
  • Comunique la visión
  • Eliminar los obstáculos
  • Asegurarse triunfos a corto plazo
  • Construir sobre el cambio
  • Anclar el cambio en la cultura de la empresa
  • Veamos qué quiere decir con cada uno.
Los 8 pasos de Kotter para gestión del cambio

1. Crear sentido de urgencia

Este punto es sin duda el más importante. No sé debe intentar iniciar el cambio si sólo nosotros vemos las ventajas del mismo. Hay que intentar prever lo que sucederá a futuro y como el cambio que proponemos puede salvar las dificultades que se avecinan, o cómo explota nuevas oportunidades de negocio que se van a presentar y para las que el cambio preparará a nuestra empresa. Todo ésto hay que planificarlo bien, pues debemos presentarlo a directivos y gerentes, y que ellos mismos, con los datos, se den cuenta que sería un error no acometer el cambio. A veces no debe ser sólo una exposición de datos, si no abrir un debate sobre la situación venidera, para que la gente piense, refute, y llegue a las mismas conclusiones. Sólo si contamos con el apoyo de los que toman decisiones se puede intentar implantar el cambio con éxito.


2. Formar una coalición

Se trata de identificar a aquellos líderes dentro de la empresa, que han compartido la misma visión y hacerles partícipes del cambio, involucrarles, estableciendo un frente común. Hay que trabajar juntos para llevar a cabo el cambio. Pero antes debemos asegurarnos que el grupo seleccionado tiene la suficiente representatividad dentro del colectivo, y que la mezcla sea extensa. Es conveniente que no todas las personas sean del mismo departamento.


3. Crear visión para el cambio

La resistencia al cambio es nuestro enemigo, y por eso hay que elaborar una visión que sea fácil de transmitir, y contar en un periodo breve de tiempo, que no lleve más de cinco minutos. Hay que identificar los puntos claves por los que es necesario el cambio, tener una reseña de cómo vemos el futuro de la empresa si aplicamos el cambio, y describir la estrategia que se seguiría para alcanzar los beneficios que nos reportaría el cambio. Una vez acordado con nuestro equipo “la visión” y que ésta es la forma de contarla, hay que practicar para que no haya fisuras ni divergencias entre los miembros de la coalición, pues probablemente a cada uno le toque evangelizar en sus respectivos departamentos y contar lo acordado.


4. Comunicar la visión

Ahora ya tenemos definida la visión, y su finalidad es comunicarla a toda la empresa. Sin duda, encontraremos resistencia, por lo que será determinante para el éxito, transmitirla una y otra vez hasta que penetre a todas las capas organizativas. Debemos predicar con el ejemplo y responder honestamente a las cuestiones y temores que se susciten en la plantilla. Hay que hablar a menudo de la visión del cambio y aplicarla en todos los aspectos.


5. Eliminar los obstáculos

A estas alturas de la película, ya todo el mundo es consciente del cambio que se quiere imponer en la empresa, y cuáles son los beneficios de imponerlos. Habrá quienes viendo las ventajas que supone se hayan lanzado a aplicarlos ya en su trabajo diario. A estas personas hay que recompensarlas, ya sea a través del organigrama o incluyéndolas en el grupo promotor del cambio. Pero también aparecerán quienes se resisten al cambio. No costará mucho identificarlos, y en este caso habrá que hacer que tomen consciencia de lo que supone para la empresa no aplicar los cambios.


6. Asegurarse triunfos a corto plazo

Estos procesos pueden ser largos. Por eso conviene asegurarse de definir una serie de hitos que tengan un éxito asegurado y que sirvan para reforzar el avance del proceso de cambio. Estos hitos pueden ser proyectos, que no requieran demasiados recursos y que se puedan llevar a cabo sin involucrar a aquellos que se oponen al cambio. Deben ser proyectos económicamente viables, pues deseamos poder esgrimir la rentabilidad del proyecto como un argumento a favor de nuestra propuesta de cambio. Y por último, hay que agradecer al equipo el esfuerzo y dedicación, que ha llevado a alcanzar con éxito la meta fijada.


7. Construir sobre el cambio

No hay que adelantarse a la victoria. Creer que el cambio se ha producido por alcanzar un éxito, sería un error. Con el primer éxito hay que seguir buscando qué mejorar, para que el segundo caso vaya más holgado. Y así en un pequeño proceso iterativo que se aprovecha de la inercia del cambio. Aún no se ha consolidado, por lo que la gente está abierta a mejoras continuas sobre la misma visión. Así nuestro cambio, puede acabar refinándose hasta alcanzar un estado en el que debemos detener el proceso para consolidar el cambio.


8. Anclar el cambio a la cultura de la empresa

Nuestra propuesta de cambio se ha consolidado y ya es la forma habitual en que la empresa trabaja. Pero eso no significa otra cosa, que volver a empezar, volver a anticiparnos al futuro de la empresa, y volver a proponer un cambio que prepare a la organización para lo que viene. Como en tantas cosas en la vida, para mantenerse en el mismo sitio, hay que evolucionar constantemente. Y esto no puede ser distinto en el mundo de la empresa, que debe abrazar la filosofía del cambio constante como un mantra que la ayude a una mejora continua.


Enlaces y referencias

Me parecía que este artículo se quedaba cojo sin incluir el estupendo vídeo de Carlos García Calvi sobre “los 8 pasos para la Gestión del Cambio”. Así que me decidí a tramitar los correspondientes permisos necesarios. Agradezco a Carlos su gentileza y buena disposición.

TLS Pinning con Java y Spring

$
0
0

En este tutorial vamos a ver qué es el TLS Pinning y cómo se puede implementar en Java mediante Spring.

Índice de contenidos


1. Introducción

Muchas veces, en las aplicaciones que desarrollamos, es necesario que nos comuniquemos con un servicio de terceros para realizar ciertas operaciones. Este tipo de comunicación podemos realizarla, típicamente, a través de servicios web securizados mediante https. Este tipo de comunicación, aunque segura ya que la comunicación se encuentra cifrada, no nos asegura que el servidor con el que nos estamos comunicando sea el que debe ser (pueden existir ataques en el que redirijan nuestra petición a otro servidor). TLS Pinning es un mecanismo mediante el cual lucharemos contra este tipo de ataques.

TLS Pinning, o también llamado Certificate Pinning, es un mecanismo de seguridad que permite autenticar al servidor con el que nos estamos comunicando. De esta forma, sabremos que el servidor con el que nos estamos comunicando es el servidor con el que nos queremos comunicar.

Normalmente, cuando se establece la comunicación con algún servidor, y ésta se realiza mediante SSL, se comprueba si el certificado del servidor es un certificado de confianza. Pero, ¿qué es un certificado de confianza?, un certificado de confianza es un certificado en el cual nosotros (como lado cliente) confiamos o un certificado que está firmado por una autoridad certificadora (CA) en la que nosotros también confiamos. Una autoridad certificadora (CA) es una entidad de confianza que emite certificados digitales. Se puede, por tanto, establecer que, si confiamos en la autoridad certificadora, confiaremos en todos los certificados que hayan sido emitidos por dicha entidad. Así, simplemente admitiendo el certificado de la autoridad certificadora admitiremos todos los certificados de cada uno de los servidores cuyo certificado haya sido emitido por dicha autoridad certificadora sin necesidad de confiar en todos los certificados uno a uno.

El objetivo de TLS Pinning es no confiar en las entidades certificadoras sino únicamente en el certificado propio del servidor. De esta forma, podremos saber que, efectivamente, estamos estableciendo la conexión con el servidor correcto en lugar de con otro servidor (a no ser que le hayan robado el certificado al servidor).


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 X El Capitan 10.10
  • Entorno de desarrollo: Eclipse Mars
  • Java 7
  • Spring 3.2.2

3. Implementando TLS Pinning


3.1. Generando el almacén de claves de confianza

Lo primero que deberemos hacer es obtener el certificado público del servidor con el que queremos comunicarnos de forma segura (nos lo deberán proporcionar). Una vez obtenido, tendremos que crear un almacén de certificados de confianza (conocido en Java como trustStore) que contenga únicamente dicho certificado. Normalmente, el almacén de certificados de confianza, por defecto, tiene una serie de certificados de las autoridades certificadoras más comunes. Como nosotros lo que queremos hacer es confiar única y exclusivamente en un único certificado (en el del servidor al que nos conectemos) tendremos que dejar este almacén con sólo dicho certificado.

Para gestionar todo el tema de almacenes, tanto keyStore como trustStore, emplearemos la herramienta keytool. En este caso, para generar un almacén de claves de confianza a partir de un certificado emplearemos el siguiente comando (importante no incluir { ni } en la ejecución del comando):

keytool -import -file {serverCertificate.cer} -alias {name} -keystore {trustStoreName}

Donde:

  • serverCertificate.cer: es el certificado público del servidor al cual nos queremos conectar.
  • name: es el nombre que le queremos dar a la entrada de ese certificado dentro de nuestro almacén de certificados de confianza.
  • trustStoreName: nombre del almacén de certificados de confianza al que añadiremos la nueva entrada. En caso de no existir se creará uno nuevo. Durante el proceso de creación se nos pedirá introducir una contraseña para el almacén de claves.

3.2. Generando nuestro PinningSSLSocketFactory

Como la comunicación que vamos a establecer con el servidor es mediante RestTemplate, tenemos que saber que RestTemplate por debajo llama a una factoría de creación de sockets SSL para la comunicación vía https. Por defecto, se usa una factoría que obtiene el almacén de certificados de confianza por defecto. Nosotros no queremos ese comportamiento, nosotros lo que queremos es usar una factoría que use el almacén de claves que hemos creado en el paso anterior. Para ello, extenderemos la funcionalidad de SSLSocketFactory para indicarle una ruta donde encontrar el almacén de certificados de confianza que tiene que utilizar y la contraseña para acceder a dicho almacén.

public class PinningSSLSocketFactory extends SSLSocketFactory {

    private static final Logger LOGGER = LoggerFactory.getLogger(PinningSSLSocketFactory.class);

    public PinningSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException,
            KeyStoreException, UnrecoverableKeyException {
        super(truststore);
    }

    public static SSLSocketFactory getSocketFactory(String trustStorePath, String trustStorePassword) {
        try {
            final SSLContext sslContext = SSLContext.getInstance("TLS");
            final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            final FileInputStream trustStoreFile = new FileInputStream(trustStorePath);
            final KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());

            trustStore.load(trustStoreFile, trustStorePassword.toCharArray());
            trustStoreFile.close();

            tmf.init(trustStore);

            X509TrustManager trustManager = null;
            for (TrustManager tm : tmf.getTrustManagers()) {
                if (tm instanceof X509TrustManager) {
                    trustManager = (X509TrustManager)tm;
                    break;
                }
            }
            sslContext.init(null, new TrustManager[] { trustManager }, null);
            return new SSLSocketFactory(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException
                | KeyManagementException e) {
            LOGGER.error("Error creating SSLSocketFactory: {}", e);
        }

        return null;
    }
}

Con esto, tendremos ya nuestra factoría a la que le indicaremos una ruta y una contraseña y nos cargará los certificados de dicho almacén de certificados de confianza.

Si existiese alguna duda con este comportamiento se recomienda acceder a la implementación propia de SSLSocketFactory de Apache.


3.3. Generando nuestro PinningHttpComponentsClientHttpRequest

RestTemplate en lugar de comunicarse directamente con la factoría de creación de sockets, se comunica mediante una clase de componentes. Por lo tanto, necesitaremos extender también esta clase para que, en su creación, como factoría de creación de sockets SSL use la que hemos creado anteriormente.

public class PinningHttpComponentsClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {

    private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100;

    private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;

    private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);

    public PinningHttpComponentsClientHttpRequestFactory(String trustStorePath, String trustStorePassword) {
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
        schemeRegistry.register(new Scheme("https", 443, PinningSSLSocketFactory.getSocketFactory(trustStorePath,
                trustStorePassword)));

        PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(schemeRegistry);
        connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
        connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);

        this.setHttpClient(new DefaultHttpClient(connectionManager));
        setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
    }
}

3.4. Uniendo las piezas

Ya tenemos todas las piezas que necesitamos para realizar TLS Pinning, lo único que nos queda es unirlas para poder crear nuestro RestTemplate con esta funcionalidad. Esta configuración la realizaremos mediante fichero .xml:

<bean name="pinningHttpComponentClientHttpRequestFactory" class="com.autentia.example.PinningHttpComponentsClientHttpRequestFactory">
    <constructor-arg value="${trustStorePath}" />
    <constructor-arg value="${trustStorePassword}" />
</bean>

<bean id="pinningRestTemplate" class="org.springframework.web.client.RestTemplate">
    <constructor-arg ref="pinningHttpComponentClientHttpRequestFactory" />
</bean>

Como podemos observar, tanto la ruta al fichero de almacén de certificados de confianza como la contraseña las indicamos mediante propiedades.


3.5. Usando nuestro PinningRestTemplate

Para usar nuestro RestTemplate lo único que necesitaremos será instanciar nuestro pinningRestTemplate.

@Qualifier("pinningRestTemplate") RestTemplate restTemplate;

4. Conclusiones

Como hemos visto, de esta forma se puede implementar TLS Pinning en nuestras aplicaciones Java con Spring. Así, se conseguirá un plus más de seguridad al autenticar el servidor al que estamos llamando con nuestros servicios.

Empezando con Ionic 2

$
0
0

En este tutorial vamos a dar los primeros pasos con Ionic 2, un framework que de la mano de Angular 2 y Apache Cordova nos permite crear aplicaciones híbridas multiplataforma respetando la guía de estilo de cada plataforma (Android, IOS y Windows).

Í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
  • Ionic 2.0.0-beta.24
  • Cordova 5.4.1 (cordova-lib@6.1.1)

2. Introducción

Actualmente la mejora en los navegadores internos de los dispositivos está haciendo posible el auge de las aplicaciones híbridas multiplaforma, acercándolas cada día más a la experiencia y rendimiento de las aplicaciones nativas pero con la ventaja de ser implementadas una única vez.

Este tipo de tecnologías nos permiten crear aplicaciones móviles con tecnología web (CSS, HTML y JS) que pueden ser ejecutadas y distribuidas en el market de cada plataforma.

Lo que aporta Ionic es un SDK que facilita la construcción de pantallas (botones, listas, …) respetando la guía de estilo de cada plataforma, de forma transparente al desarrollador, es decir, que inicialmente no tenemos que añadir una sola línea de código para conseguirlo.

Esta tecnología no es nueva y siempre ha estado ligada con el framework AngularJS y Apache Cordova, aquí tenéis un tutorial en el que ya hablábamos de estas tecnologías.

Así que este trinomio se ha seguido manteniendo con la versión 2 de Angular, que como ya vimos en este otro tutorial mejora significativamente la productividad de los equipos a la hora de desarrollar cualquier tipo de aplicación.

La versión 1 de Ionic ofrecía su SDK como un conjunto de directivas de AngularJS, así que está versión 2 hace lo mismo pero con componentes, lo que mejora significativamente el rendimiento de su antecesor.

Toda la documentación oficial la podéis encontrar aquí.

Como ejemplo significativo de la potencia de esta tecnología, os aconsejo que pinchéis aquí, para comprobar como se visualizaría un Action Sheet en las distintas plataformas desarrollando un único código y ahora imaginad teniendo que hacer esto a mano. 😉


3. Compatibilidad con versiones y plataformas

Un punto crítico a la hora de adoptar este tipo de tecnologías es la compatibilidad con los distintos dispositivos en sus distintas versiones y plataformas; sobre todo si tiene o no compatibilidad con dispositivos antiguos, por aquello de no dejar “colgados” a un número significante de usuarios.

En este punto os puedo decir, después de muchas pruebas empíricas, que este tipo de tecnologías funcionan perfectamente (sin hacer ninguna configuración adicional) en versiones iguales o superiores a la 4.4 de Android, a la 8.0 de IOS y en Windows 10.

En caso de necesitar compatibilidad con versiones 4.2 y 4.3 de Android, se puede añadir el plugin de Crosswalk a través de Cordova, que añade un navegador “moderno” a nuestra aplicación para que pueda ejecutarse en estas versiones antiguas. La única pega es que el tamaño de la aplicación aumenta considerablemente, hemos probado con una aplicación de 4 Mb, y el APK ha pasado a 25 Mb y una vez instalada ocupa 35 Mb; así que puede ser una buena solución si el espacio no es un problema y avisamos fehacientemente al usuario que descargue la aplicación a través de una red Wifi. Pero ya os digo esto solo afectaría a dispositivos 4.2 y 4.3 de Android. Aquí tenéis más información sobre el proyecto crosswalk.

Para Windows el equipo de Ionic considera que no merece la pena mantener compatibilidad con versiones anteriores a Windows 10. Esto viene motivado por la poca cuota de mercado y a la mejora drástica, en rendimiento y compatibilidad, del navegador en su versión 10.

En conclusión esta tecnología es muy valida siempre que se impongan al cliente ciertas restricciones en las versiones de los dispositivos soportados. Android 4.4+, IOS 8+ y Windows 10+ (Phone y Desktop).


4. Primeros pasos

Para dar los primeros pasos con Ionic 2 tenemos que instalar la aplicación que se distribuye como un paquete npm:

$> npm install -g ionic@beta

Nota: a día de hoy, al igual que Angular 2, es una tecnología que está en beta, pero nuestra experiencia con ella ha sido muy positiva.

Otra herramienta necesaria para poder crear este tipo de aplicaciones es Apache Cordova, que también instalamos a través de npm:

$> npm install -g cordova

Ahora estamos en disposición de crear nuestro primer proyecto. Para ello hacemos uso del potente CLI que tiene la herramienta y ejecutamos el comando start pasándole el nombre del proyecto, el template que puede ser (tabs, sidemenu, blank o tutorial), en este caso vamos a seleccionar tutorial y una serie de modificadores (podéis verlos todos ejecutando ionic help):

$> ionic start poc-ionic tutorial --v2 --typescript

Esta sentencia nos va a crear el proyecto, con el template de tutorial, en la versión 2 y utilizando la sintaxis de TypeScript.

Y no nos tenemos que preocupar de nada más. Simplemente ejecutamos el comando:

$> ionic serve

Y directamente se mostrará la aplicación de ejemplo con el tutorial en el navegador que tengamos por defecto.

ionic2-1

Como veis nos crea una aplicación con un menú lateral y un listado con sus detalles. Si queremos ver como quedaría en IOS y Android simplemente tenemos que ejecutar el mismo comando pero con el modificador –lab.

ionic2-2

Aquí se puede apreciar los cambios de estilo de cada plataforma y todavía no hemos abierto el código fuente. ¡Espectacular! :-)

Para probar la aplicación en cualquier dispositivo móvil que tengamos, el CLI de ionic también nos ofrece comandos muy útiles que hacen uso de los comandos típicos de Apache Cordova.

De tal forma que podamos añadir las plataformas que queramos soportar con el comando:

$> ionic platform add ios android windows

Nota: necesitas tener el entorno de desarrollo preparado para cada plataforma. En mi caso al tratarse de un Mac solo puedo añadir las plataformas de ios y android. La plataforma de windows la tengo que añadir dentro de una máquina virtual con Windows 10. Eso sí todos los pasos y comandos que estamos viendo son iguales en todas las plataformas.

Ahora para ejecutar la aplicación en el emulador de la plataforma que tengamos, simplemente:

$> ionic run android

Nota: En caso de no tener ningún dispositivo conectado, abrirá el emulador que tengamos configurado por defecto, recordad que si no usáis crosswalk este tiene que tener una versión igual o superior a la 4.4 (KitKat). Hay que ver lo que ha mejorado el emulador de Android en las últimas versiones, ya hace innecesario el uso de Genymotion :-)

ionic2-3

Si la aplicación la ejecutáis en Windows 10 tenéis que añadir antes las siguientes preferencias en el fichero config.xml del proyecto.

<preference name="windows-target-version" value="10.0"/>
<preference name="windows-phone-target-version" value="10.0"/>
<preference name="Windows.Universal-MinVersion" value="10.0.10069"/>
<preference name="Windows.Universal-MaxVersionTested" value="10.0.10166.0"/>

5. Conclusiones

Como habéis podido ver esta tecnología es realmente productiva a la hora crear aplicaciones híbridas multiplaforma, ofreciéndonos una herramienta donde con unos pocos comandos ya podemos empezar a trabajar.

Ahora ya no tienes excusas para no plasmar tus ideas en aplicaciones móviles multiplataforma y que todos podamos disfrutarlas independientemente del dispositivo que tengamos.

¡Manos a la obra!

Cualquier duda o sugerencia en la zona de comentarios.

Saludos.

Comentando “El libro negro del programador”, de Rafael G. Blanes

$
0
0

Comentamos un libro en el que un programador español, Rafael G. Blanes, relata su dilatada experiencia en el mundo del desarrollo, para ilustrar consejos destinados a los nuevos programadores.

El mundo del desarrollo de software, sobre todo en España, podría decirse que se divide en dos grandes grupos. Mejor dicho, en un gran grupo y en otro desgraciadamente más pequeño:

  • Desarrolladores bajo el yugo de organizaciones que no se preocupan del cómo, y tienden a un qué de mínimos.
  • Desarrolladores bajo el paraguas de organizaciones que ponen mucha atención al qué y al cómo.

Naturalmente hay otras opciones, pero no me parecen excesivamente representativas. El primer grupo desafortunadamente es el más grande, y se nutre de desarrolladores que probablemente no han tenido la oportunidad de cruzarse con integrantes del segundo grupo. No hace falta más que coincidir con las típicas grandes empresas de consultoría donde contratan programadores “al peso”, sin importarles ni su formación ni sus habilidades, que son revendidos a los clientes como expertos.

El fruto de esta política empresarial no es otro que productos de mala calidad, clientes cabreados y sobre todo, desde el punto de vista del gremio de los desarrolladores, compañeros frustrados y malogrados que se repiten constantemente a sí mismos lo arrepentidos que están de haber elegido esta profesión.

Los desarrolladores del segundo grupo, por lo general, parecen más felices. Disfrutan del día a día de su profesión, de hacer bien las cosas, y de comprobar cómo aportan un valor diferencial respecto al primer grupo, que cada día les es más reconocido. O al menos es lo que experimentamos los que formamos parte de Autentia.

¿Cuál es la diferencia entre un desarrollador de un grupo y otro? Para mí es una cuestión de actitud, pero sobre todo de conocimiento. Al menos es lo que me dice mi experiencia: hay buenos desarrolladores entre los del primer grupo. Gente capaz y con actitud, pero que no siguen el camino adecuado porque no se han topado con él.

Desde sus estudios en la universidad o en los módulos han recibido la formación básica, que han ampliado en diferentes puestos de trabajo, pero nunca han dado con la tecla de seguir ciertos caminos del conocimiento ni a ciertos autores. ¿Qué habría sido de muchos si álguien les hubiera hablado de los grupos de MeetUp?¿Y del testing, integración contínua o los principios del Clean Code?¿Y de metodologías ágiles o patrones de diseño? Quizá no todos, pero unos cuantos del primer triste grupo podrían haber pasado al segundo y feliz grupo…

Pues bien, este libro, El Libro Negro del Programador: Cómo conseguir una carrera de éxito desarrollando software y cómo evitar los errores habituales, es un buen resumen de introducción para aquellos que forman parte del primer grupo y creen que la profesión de desarrollador de software es un lugar tan lúgubre y triste.

El autor, Rafael G. Blanes, es un desarrollador con varios lustros a sus espaldas en diferentes proyectos, tanto como consultor como freelance. Y para mí, lo más importante, es que lleva tiempo desarrollando como programador del segundo grupo: testing, integración continua, clean code, metodologías ágiles… Esto es lo que realmente aporta valor: la visión de la vida del desarrollador como una profesión a dignificar a través de una disciplina y una serie de prácticas que deberían ser más comunes de lo que lo son hoy en día.

El libro está estructurado como una obra más bien dídáctica (¡Ojo! No es técnico, no verás código) sobre diferentes puntos en los que un desarrollador debería centrarse para cambiar el modo de desempeñar su profesión. A través de una serie de breves pero útiles capítulos (y resumidos en unos pocos puntos al final de cada capítulo), el autor expone su experiencia a lo largo de diferentes proyectos para ilustrar tanto consejos desde el punto de vista técnico como organizativos, tanto de filosfía que podemos aplicar en nuestro día a día como en la gestión de proyectos. Algunos puntos que me han resultado llamativos:

  • El manifiesto incial sobre la profesión del desarrollo de software.
  • Ciclo de refactoring basado en la filosofía de Martin Fowler: desarrollo, test y refactorización.
  • Dificultades que surgen en el desarrollo de software por un mal ambiente, presiones, falta de atención a la calidad.
  • Principios irrenunciables a la hora de desarrollar: inversión de control, SOLID, KISS…
  • Cambios constantes y la importancia de la mantenibilidad.
  • Gestión de equipos: en qué debe centrarse un gestor y qué peligros tienen las nuevas incorporaciones.
  • Las metodologías ágiles y el papel, cada vez menos importante, de los arquitectos de software: cambio continuo y necesidad de adaptación.
  • No perder el punto de vista del usuario: a veces es más relevante un pequeño cambio en la interfaz de usuario que un gran cambio en el backend.
  • La importancia de pararse a pensar y reflexionar sobre el paso siguiente. Mucho más que avanzar sin una dirección clara.
  • Prestar atención al OpenSource para delegar en proyectos de la comunidad, desarrollados y probados y no reinventar la rueda.
  • La importancia de desarrollar no sólo algo que funcione, sino algo que sea mantenible, que sea código limpio que cualquiera pueda entender y mantener o mejorar.
  • No estancarse, ni en tecnologías ni dentro de proyectos. Cambiar de proyectos y de tecnologías a corto plazo puede parecer perjudicial porque renunciamos a nuestra cuota de poder que hemos adquirido, pero a la larga es beneficioso porque nos enriquece.
  • Desarrollando se aprende unos de otros: leyendo código de otras personas, participando en proyectos open source o aquí añadiría yo, formando parte de comunidades tecnológicas.
  • Uso de sistemas de integración continua y de una correcta gestión de la configuración. No se trata sólo de programar sino de establecer el entorno correcto en el que programar.
  • El emprendimiento como futuro del empleo: las empresas tienden a contratar el menor número de trabajadores posibles. El resto deberá aportar valor de modo temporal, siendo más freelances que trabajadores clásicos como entendíamos hasta ahora.

El libro concluye con un cuestionario de 78 preguntas para descubrir si aplicas o no lo que el autor considera los puntos necesarios para ser un desarrollador de software altamente productivo. También se incluye una mínima bibliografía que el autor considera indispensable. Se echa en falta algunos títulos, sobre todo de testing como el Test-Driven Development de Kent Beck para cerrar el círculo con Refactoring de Martin Fowler y con Clean code de Uncle Bob o alguno de Integración continua como Continuous Delivery: Reliable Software Releases Through Build, Test, and Deployment Automation (Addison Wesley Signature Series), pero no deja de ser una breve introducción.

Bajo mi punto de vista se trata de un libro destinado a aquellos que no tienen conocimiento en el mundo de “la artesanía del software (software craftmanship)”. Es una buena introducción a este mundo, breve y amena, con consejos útiles desde la experiencia y con referencias a los clásicos de la temática. Así que si los capítulos más importantes que he puesto antes no te resultan familiares, merece la pena que le eches un vistazo a este libro.

Si por el contrario eres un fiel seguidor de Uncle Bob, Fowler, Kent Beck, o Sandro Mancuso (entre otros…), estás familiarizado con el manifiesto ágil, no te pierdes ningún meetup tecnológico y en tu trabajo estás todo el día mirando los informes de SonarQube, entonces no creo que este libro te aporte mucho la verdad. Gira entorno a conceptos que seguro que ya conoces y lo único que hará es confirmarte que no estás solo en este mundo :). No obstante, la version electrónica vale lo que un par de botes de Pringles y se lee en un periquete. Igual te vale para algo.

En definitiva, es un libro que recomendaría a la gente que está comenzando en el mundo del desarrollo, o a aquellos que están estancados en unas prácticas nada sanas, como apertura a un mundo mejor: el de la artesanía del software.

Aquí tienes el enlace por si quieres comprar la versión electrónica por 2.99€:

libroNegroBig
Cómpralo en Amazon

Viewing all 989 articles
Browse latest View live