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

Integración de un proyecto de GitHub con Travis y Codacy

$
0
0

Índice de contenidos

1. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3)
  • Sistema Operativo: Mac OS High Sierra 10.13.3
  • Apache Maven 3.5.0

2. Creación del proyecto de ejemplo

En primer lugar entraremos en nuestro perfil de GitHub y crearemos un proyecto, en este tutorial yo usaré el proyecto creado en mi perfil travis-codacy. Una vez creado nuestro proyecto en GitHub, añadiremos las fuentes de un proyecto de prueba (con test unitarios y de integración), usaremos un arquetipo de Spring Boot descargado de GitHub.

En mi caso únicamente he cambiado el nombre del paquete para que sea “com.autentia.traviscode” y he cambiado el “groupId” y “artefactId” del proyecto en el fichero “pom.xml”.

Cuando tengamos copiado este proyecto, realizaremos una compilación de maven desde la consola para ver si funciona (“mvn clean install”). Si todo está correcto haremos un commit y push al repositorio de GitHub que hemos creado, de esta manera, cuando la integración con Travis y Codacy esté acabada podremos ver el análisis y la cobertura de código que nos ofrecerá Codacy sobre este arquetipo de Spring Boot. En mi proyecto será el commit “Example project from https://github.com/spring-guides/gs-spring-boot.git”.

3. Integración con Travis

Accedemos a la página web de Travis y nos registramos con nuestra cuenta de GitHub. En nuestro perfil tendremos la opción de activar los proyectos que tenemos subidos en GitHub, en mi caso activaré cdelhoyo/travis-codacy.

Una vez activado el proyecto, con nuestro siguiente push se lanzará la compilación de Travis y nos enviará un email con el resultado de la compilación, pero para ello necesitamos añadir el fichero .travis.yml a la raíz de nuestro proyecto.

.travis.yml
language: java
sudo: false

cache:
	directories:
	- "$HOME/.m2"

before_cache:
- rm -rf $HOME/.m2/repository/com/autentia/traviscodacy

jdk:
	- oraclejdk8

En nuestro caso usaremos el lenguaje Java con la jdk8, además guardaremos en cache las dependencias de Maven excepto las de nuestro proyecto. Una vez añadido el fichero, podemos hacer el commit y push para ver como compila travis. En mi repositorio será el commit “.travis.yml added”.

Si accedemos inmediatamente a nuestro panel de Travis, veremos como la compilación está en marcha.

Y una vez termine, deberíamos ver cómo se ha completado con éxito y nos ha mandado un email para informar de ello.

En este punto ya tenemos integrado nuestro proyecto de GitHub con Travis y podemos pasar a la integración con Codacy.

4. Integración con Codacy

Para comenzar la integración con Codacy, en primer lugar entraremos en su página web y nos registraremos nuevamente con nuestra cuenta de GitHub. Al entrar nos dará la opción de añadir un proyecto de nuestra cuenta, yo elegiré cdelhoyo / travis-codacy.

Ahora que tiene vinculada nuestra cuenta de GitHub, Codacy puede hacer un análisis de nuestro código y nos mostrará un panel de control con las principales métricas:

Pero este análisis no tiene la cobertura de código, un indicador bastante útil en el análisis de código. Al ser un proyecto Java, para obtener la cobertura de código, usaremos un informe de jacoco que tendremos que configurar en nuestro proyecto.

Para configurar jacoco usaremos una configuración típica para test unitarios y test de integración añadiendo el plugin de jacoco a nuestro pom.xml.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
		<modelVersion>4.0.0</modelVersion>

		<groupId>com.autentia</groupId>
		<artifactId>traviscodacy</artifactId>
		<version>0.1.0</version>

		<parent>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-parent</artifactId>
				<version>2.0.0.RELEASE</version>
		</parent>

		<dependencies>
				<dependency>
						<groupId>org.springframework.boot</groupId>
						<artifactId>spring-boot-starter-web</artifactId>
				</dependency>
				<dependency>
						<groupId>org.springframework.boot</groupId>
						<artifactId>spring-boot-starter-test</artifactId>
						<scope>test</scope>
				</dependency>
		</dependencies>

		<properties>
				<java.version>1.8</java.version>
		
				<jacoco.version>0.7.6.201602180812</jacoco.version>
				<jacoco.outputDir>${project.build.directory}</jacoco.outputDir>

				<jacoco.utreportpath>${project.build.directory}/jacoco</jacoco.utreportpath>
				<jacoco.utreportfile>${jacoco.utreportpath}/jacoco.exec</jacoco.utreportfile>

				<jacoco.itreportpath>${project.build.directory}/jacoco</jacoco.itreportpath>
				<jacoco.itreportfile>${jacoco.itreportpath}/jacoco-it.exec</jacoco.itreportfile>
		</properties>


		<build>
				<plugins>
						<plugin>
								<groupId>org.springframework.boot</groupId>
								<artifactId>spring-boot-maven-plugin</artifactId>
						</plugin>

						<plugin>
								<groupId>org.jacoco</groupId>
								<artifactId>jacoco-maven-plugin</artifactId>
								<version>${jacoco.version}</version>
								<executions>
										<execution>
												<id>pre-unit-test</id>
												<phase>process-test-classes</phase>
												<goals>
														<goal>prepare-agent</goal>
												</goals>
												<configuration>
														<propertyName>surefireArgLine</propertyName>
														<destFile>${jacoco.utreportfile}</destFile>
														<append>true</append>
												</configuration>
										</execution>
										<execution>
												<id>post-unit-test</id>
												<phase>test</phase>
												<goals>
														<goal>report</goal>
												</goals>
												<configuration>
														<outputDirectory>${jacoco.utreportpath}</outputDirectory>
														<dataFile>${jacoco.utreportfile}</dataFile>
												</configuration>
										</execution>
										<execution>
												<id>pre-integration-test</id>
												<phase>pre-integration-test</phase>
												<goals>
														<goal>prepare-agent-integration</goal>
												</goals>
												<configuration>
														<propertyName>failsafeArgLine</propertyName>
														<destFile>${jacoco.itreportfile}</destFile>
														<append>true</append>
												</configuration>
										</execution>
										<execution>
												<id>post-integration-test</id>
												<phase>post-integration-test</phase>
												<goals>
														<goal>report</goal>
												</goals>
												<configuration>
														<outputDirectory>${jacoco.itreportpath}</outputDirectory>
														<dataFile>${jacoco.itreportfile}</dataFile>
												</configuration>
										</execution>
								</executions>
						</plugin>

						<plugin>
								<groupId>org.apache.maven.plugins</groupId>
								<artifactId>maven-surefire-plugin</artifactId>
								<configuration>
										<argLine>${surefireArgLine}</argLine>
										<excludes>
												<exclude>**/*IT.java</exclude>
										</excludes>
								</configuration>
						</plugin>

						<plugin>
								<groupId>org.apache.maven.plugins</groupId>
								<artifactId>maven-failsafe-plugin</artifactId>
				<executions>
					<execution>
						<id>integration-tests</id>
						<goals>
							<goal>integration-test</goal>
							<goal>verify</goal>
						</goals>
						<configuration>
							<argLine>${failsafeArgLine}</argLine>
													<reportsDirectory>${project.build.directory}/failsafe-reports</reportsDirectory>
													<skipTests>false</skipTests>
						</configuration>
					</execution>
				</executions>
						</plugin>
				</plugins>
		</build>
	
</project>

Si comprobamos las diferencias, veremos que solo hemos añadido el plugin de jacoco con sus propiedades. Para comprobar que está todo configurado correctamente, volveremos a compilar nuestro proyecto con Maven y comprobaremos que se genera el informe de jacoco “jacoco.xml” en el directorio “target/jacoco”.

Por desgracia esto no es suficiente para tener la cobertura de código en Codacy, pero para conseguirla solo tendremos que cambiar la configuración de travis para que al compilar mande el informe de jacoco a Codacy.

.travis.yml
language: java
		sudo: false
		
		cache:
			directories:
				- "$HOME/.m2"
		
		before_cache:
			- rm -rf $HOME/.m2/repository/com/autentia/traviscodacy
		
		before_install:
			- sudo apt-get install jq
			- wget -O ~/codacy-coverage-reporter-assembly-latest.jar $(curl https://api.github.com/repos/codacy/codacy-coverage-reporter/releases/latest | jq -r .assets[0].browser_download_url)
		
		after_success:
			- java -jar ~/codacy-coverage-reporter-assembly-latest.jar report -l Java -r target/jacoco/jacoco.xml
		
		jdk:
			- oraclejdk8

Para que Travis envie el informe a Codacy simplemente tendremos que añadir el “before_install” y el “after_success” definidos en la configuración del “.travis.yml” para usar el jar que nos proporciona Codacy “codacy-coverage-reporter-assembly-latest.jar”.

Además, tendremos que añadir la variable de entorno “CODACY_PROJECT_TOKEN” en Travis. Para ello, tenemos que ir al apartado “Settings” (accesible en “More options”) de nuestro proyecto en el panel de Travis y añadir en “Environment Variables” la variable “CODACY_PROJECT_TOKEN” con el valor de nuestro token

Para conseguir el valor del token de Codacy iremos a la pestaña “Settings” del menú lateral de nuestro proyecto de Codacy, pulsaremos sobre la pestaña superior “Integrations” y obtendremos el token de “Project API”.

Por último, haremos un commit y push de los ficheros “pom.xml” y “.travis.yaml”. En mi caso será el commit “Jacoco configuration”.

Si accedemos al panel de control de Travis para nuestro proyecto, podemos ver que la compilación ha terminado con éxito.

Y si accedemos al panel de control de Codacy ya podemos analizar nuestra cobertura de código.

5. Integración con otros lenguajes

Hemos hecho un ejemplo con un proyecto Java usando jacoco, pero Codacy soporta varios lenguajes más como Scala, Ruby, Javascript, Python o PHP. Codacy recomienda las siguientes herramientas para conseguir la cobertura de código:

  • Java – JaCoCo
  • JavaScript – Istanbul
  • PHP – PHPUnit
  • Python – Coverage.py
  • Scala – Scoverage
  • Ruby – SimpleCov

Ejemplos de proyectos integrados con Travis y Codacy

6. Conclusiones

En este tutorial hemos visto como integrar de manera rápida y sencilla un proyecto de GitHub con un entorno de integración continua y análisis de código, además, si el proyecto es de código abierto, este entorno será gratuito y no tendrá coste alguno, también hemos visto que Codacy soporta multitud de lenguajes como Java, Scala, Ruby, Javascript, Python o PHP.

7. Referencias


Introducción a Machine Learning con TensorFlow

$
0
0

Índice de contenidos

1. Introducción

En este tutorial vamos a ver los conceptos básicos de machine learning centrándonos en redes neuronales. Veremos como TensorFlow puede ayudarnos a conseguir una solución potente y escalable para resolver problemas complejos de categorización con redes neuronales de aprendizaje profundo.

Este tutorial utiliza el lenguaje de programación Python para los ejemplos. No vamos a explicar cosas concretas del lenguage pero si aún no lo conoces, no está de más aprender las cosas básicas de un lenguage tan conocido como Python. Podéis empezar por aquí o por aquí.

Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2,2 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS High Sierra
  • Entorno de desarrollo: Visual Studio Code
  • Software: python, TensorFlow

2. Teoría de ML

Vamos a empezar por un poco de teoría. Explicaremos muy por encima algunos términos y asuntos que son importantes en el mundo del Machine Learning.

Machine Learnging o “aprendizaje automático” es una rama de la Inteligencia Artificial que pretende desarrollar las técnicas necesarias para que las máquinas aprendan.

Deep Learning es un término que engloba un conjunto de algoritmos de aprendizaje profundo. Es un subgrupo de todo lo que abarca ML.

Redes neuronales son un modelo computacional que intenta imitar el comportamiento de las neuronas de cerebros biológicos. En este modelo se crean varias capas de neuronas interconectadas con las neuronas de las capas anteriores y siguientes con unos pesos en los enlaces.

A la unión de una neurona con sus enlaces de entrada y sus pesos asignados se le llama perceptrón. Así la salida del perceptrón tiene un mecanismo para calcular si está activo en función de los valores dados por la salida de la anterior capa multiplicados por el peso del enlace de unión.

La función de activación de la neurona es la que define la conversión de las entradas ponderadas en su activación de salida. Algunas de las funciones usadas normalmente son: lineal, sigmoidea, escalón, TanH, ReLu

Las redes neuronales se pueden clasificar según su tipología/arquitectura. Algunas de estas clasificaciones pueden ser las siguientes.

  • DNN (Deep Neural Net) la cual tiene más de una capa oculta.
  • CNN (Convolutional Neural Net) son especialmente importantes en el procesamiento de imágenes y la categorización. Se caracterizan por tener capas de filtros convolucionales que, de manera similar a la corteza visual del cerebro, transforman la entrada aplicando máscaras para extraer información de distintos tipos.
  • RNN (Recurrent Neural Net) se caracterizan por tener conexiones recurrentes con ellas mismas o neuronas de la misma capa. Especialmente interesantes para detección de patrones, pues permiten “recordar” los valores de la anterior predicción.

Las redes nueronal pueden tener distintos tipos de aprendizaje:

  • Aprendizaje supervisado a la red se le proporcionan un conjunto de ejemplos con sus valores de salida deseados. Así la red se entrena aplicando algoritmos para ir corrigiendo y propagando los pesos más adecuados para lograr la salida indicada.
  • Aprendizaje no supervisado solo se le indican entradas a la red sin proporcionarle los valores de salida correctos/deseados. La red aprende a clasificarlos buscando las similitudes entre conjuntos de datos
  • Aprendizaje por refuerzo se enseña al sistema con recompensas o castigos. Así indicándole el grado de desempeño irá ajustando sus valores para alcanzar el máximo posible

Para ejecutar predicciones y entrenamientos de redes neuronales la mayoría de operaciones que se realizan son cálculos con matrices. Para el procesamiento de gráficos una de las tareas más importantes es ésta, por ello el uso de GPUs en este campo es muy adecuado y puede reducir mucho los tiempos. Google ha dado un paso más introduciendo unos chips llamados TPUs que están más optimizados y centrados en los cálculos necesarios para ejecutar algoritmos de aprendizaje automático.

3. TensorFlow

TensorFlow es una herramienta de machine learning. Popularizada por su eficiencia con redes neuronales de aprendizaje profundo pero que permite la ejecución de procesos distribuidos que no tengan nada que ver con redes neuronales.

Sus Estimators representan un modelo completo y ofrecen los mecanismos para entrenar los modelos, evaluar su error y generar predicciónes. Son la capa de más alto nivel. Te abstraen del entorno donde se ejecutarán ya sea local, un clúster distribuido con CPUs, GPUs o TPUs. Existen unos “Estimators” predefinidos que facilitan la construcción de redes neuronales sencillas:

  • tf.estimator.DNNClassifier para modelos de clasificación con redes profundas.
  • tf.estimator.DNNLinearCombinedClassifier para clasificación combinada de modelos lineares y con redes profundas.
  • tf.estimator.LinearClassifier para clasificación con modelos lineales.

Otras APIs de alto nivel importantes son:

  • Checkpoints: que permiten almacenar y restaurar el estado de configuración de la red.
  • Feature columns: para facilitar transformaciones de los datos de entrada mediante intermediarios entre los datos de entrada y los Estimators
  • Datasets: herramienta para construir y cargar un input de datos, manipularlo e introducirlo en tu modelo.
  • Layers: ofrece una api de alto nivel para la creación de capas de neuronas facilitando sus interconexiones, funciones de activación, etc.

4. Instalando y probando

Lo primero que debemos hacer es instalar TensorFlow. Para ello podemos ver cómo hacerlo según vuestro SO en: https://www.tensorflow.org/install/

En mi caso he optado por la instalación mediante Docker. Ejecutando:

docker run -it gcr.io/tensorflow/tensorflow bash

Esto debería descargar la imagen, crear el docker y arrancarlo. Ahora vamos a probar nuestra instalación. Desde la consola bash de nuestro docker ejecutamos:

python

Ahora en la consola de Python (del propio Docker) introducimos nuestro hola mundo de TensorFlow:

# Python
import tensorflow as tf
hello = tf.constant('Hello, TensorFlow!')
sess = tf.Session()
print(sess.run(hello))

Con la última linea veremos en el output el texto: “Hello, TensorFlow!”

Durante el tutorial arrancaremos el Docker de TensorFlow añadiendo el montaje de un volumen local para que podamos desde el contenedor de Docker utilizar los ejemplos que usaremos del repositorio git https://github.com/miyoda/tensorflow-example

docker run -it -v /your-computer-repository-directory:/notebooks/example gcr.io/tensorflow/tensorflow bash

5. Ejemplo visual

En la web https://playground.tensorflow.org podemos jugar con redes neuronales de una manera muy visual. Podemos probar para distintos conjuntos de datos diferentes configuraciones de número de capas ocultas, neuronas en cada capa, función de activación, ratio de aprendizaje, etc.

El problema es simplemente categorizar como “Azul” (1) o “Naranja” (-1) un conjunto de puntos que se distribuyen, en función del patrón de datos indicado a la izquierda, en un sistema de coordenadas cartesianas con “-6 > x > 6” y “-6 > y > 6”.

Os animo a que hagáis algunas pruebas y os fijéis sobre todo con el ejemplo básico de datos en “círculo” que vamos a implementar en Python en el siguiente apartado. Como veréis en este ejemplo se puede lograr una solución con un alto grado de acierto configurando incluso una sola capa oculta de 3 neuronas y dándole como entrada simplemente los valores de x (X1) e y (X2).

En cambio para solucionar el problema del dataset en “Spiral” la red necesita que le demos información más “precocinada” de los datos. Así que observaremos que no obtendremos una solución muy buena hasta que no le demos como entrada de la red el cálculo del sen(x) y el sin(y).

Podéis apreciar que la interfaz muestra los enlaces entre las 2 entradas de “sin” mucho más gruesos. Esto es porque han quedado configurados con mayor peso. Esto no quiere decir que los valores de entrada x e y no sean importantes, pues de hecho la red no logra una solución buena si quitas estos inputs.

6. Ejemplo en Python

Puedes descargar el código de este ejemplo desde el repositorio https://github.com/miyoda/tensorflow-example

En el subdirectorio “playground-1” tenemos la solución del ejemplo visto anteriormente en la web de playground de TensorFlow.

Lo primero que hemos preparado es un pequeño script para generar los datos de prueba. Esto no tiene mucho sentido si tenemos la “formula” para calcular los datos clasificados, ¿para qué queremos una red neuronal que nos los clasifique?: Para aprender. Los datos contienen valor de coordenadas del punto (x e y) y su categorización de color (0 para naranja y 1 para azul). Vamos a verlo en el fichero “data_generator.py”:

import random
import math
		
def generate(numPoints):
	result = []
	for i in range(0, numPoints):
		x = random.uniform(-6,6)
		y = random.uniform(-6,6)
		color = 1 if distance((0,0), (x,y)) < 3 else 0
		result.append((x,y,color))
	return result
		
def distance(pointFrom, pointTo):
	diff = (pointTo[0] - pointFrom[0], pointTo[1] - pointFrom[1])
	return math.sqrt(diff[0]*diff[0]+diff[1]*diff[1])

Por otro lado en el fichero “example_data.py” tenemos algunos métodos para facilitar la preparación de los datos para su uso que crea conjuntos de datos de entrenamiento (TRAIN) y de prueba (TEST) para que la red pueda entrenarse y autoprobarse durante su etapa de aprendizaje. Divide los datos a su vez en dos. Los de entrada train_x (coordenadas x e y) y los de salida train_y (color).

Ahora en el fichero “premade_estimator.py” vamos a configurar la red neuronal utilizando un Estimator predefinido. En este caso DDNClassifier.

Lo primero que tenemos que hacer es preparar los datos de entrada y configurar las columnas de input (features):

# Fetch the data
(train_x, train_y), (test_x, test_y) = example_data.load_data()

# Feature columns describe how to use the input.
my_feature_columns = []
for key in train_x.keys():
    my_feature_columns.append(tf.feature_column.numeric_column(key=key))

Ahora creamos el Estimator de tipo DNNClassifier indicándole que queremos dos capas ocultas con 6 y 4 neuronas, y que la clasificación podrá ser entre 2 valores (naranja o azul)

# Build 2 hidden layer DNN with 10, 10 units respectively.
  classifier = tf.estimator.DNNClassifier(
      feature_columns=my_feature_columns,
      # Two hidden layers
      hidden_units=[6, 4],
      # The model must choose between 2 classes.
      n_classes=2)

Procedemos a entrenar el modelo con los datos de entrenamiento generados.

# Train the Model.
classifier.train(
	input_fn=lambda:example_data.train_input_fn(train_x, train_y,																			args.batch_size),
	steps=args.train_steps)

Ahora evaluamos la red generada/entrenada con los datos de test. Mostramos por pantalla el “accuracy” el cual tendrá que estar lo más cerca de 1 posible (dependiendo de las exigencias del problema a resolver).

# Evaluate the model.
eval_result = classifier.evaluate(
      input_fn=lambda:example_data.eval_input_fn(test_x, test_y, args.batch_size))
print('\nTest set accuracy: {accuracy:0.3f}\n'.format(**eval_result))

Ahora vamos a utilizar la red ya entrenada con 4 datos de prueba para ver que resultados arroja:

# Generate predictions from the model
expected = ['ORANGE', 'ORANGE', 'BLUE', 'BLUE']
predict_x = {
    'x': [-4, -3, 1, -1.5],
    'y': [1, -3, -1, 0.5]
}

predictions = classifier.predict(
        input_fn=lambda:example_data.eval_input_fn(predict_x,
                        labels=None,
                        batch_size=args.batch_size))

template = ('\nPrediction is "{}" ({:.1f}%), expected "{}"')

for pred_dict, expec in zip(predictions, expected):
    class_id = pred_dict['class_ids'][0]
    probability = pred_dict['probabilities'][class_id]
    print(template.format("BLUE" if class_id > 0 else "ORANGE",
                              100 * probability, expec))

Si ahora arrancamos el Docker de TensorFlow y ejecutamos el código con “python /notebooks/example/playground-1/premade_estimator.py obtendremos algo como lo siguiente:

python /notebooks/example/playground-1/premade_estimator.py
/usr/local/lib/python2.7/dist-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
			from ._conv import register_converters as _register_converters
TRAIN DATA:
		[(3.7930826366632004, 3.0035981747767444, 0), (1.0488879547572427, -5.647917058837368, 0), ...]
TEST DATA:
		[(4.669380996775592, 2.339267440220139, 0), (1.7377747136263988, -5.980986206879744, 0), ...]
INFO:tensorflow:Using default config.
WARNING:tensorflow:Using temporary folder as model directory: /tmp/tmp4nrlxU
INFO:tensorflow:Using config: {'_save_checkpoints_secs': 600, '_session_config': None, '_keep_checkpoint_max': 5, '_task_type': 'worker', '_global_id_in_cluster': 0, '_is_chief': True, '_cluster_spec': , '_evaluation_master': '', '_save_checkpoints_steps': None, '_keep_checkpoint_every_n_hours': 10000, '_service': None, '_num_ps_replicas': 0, '_tf_random_seed': None, '_master': '', '_num_worker_replicas': 1, '_task_id': 0, '_log_step_count_steps': 100, '_model_dir': '/tmp/tmp4nrlxU', '_save_summary_steps': 100}
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
		2018-03-31 09:49:38.236836: I tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Saving checkpoints for 1 into /tmp/tmp4nrlxU/model.ckpt.
INFO:tensorflow:loss = 159.065, step = 1
INFO:tensorflow:global_step/sec: 416.865
INFO:tensorflow:loss = 25.40447, step = 101 (0.240 sec)
INFO:tensorflow:global_step/sec: 525.519
INFO:tensorflow:loss = 17.685644, step = 201 (0.190 sec)
INFO:tensorflow:global_step/sec: 537.505
INFO:tensorflow:loss = 14.17207, step = 301 (0.186 sec)
INFO:tensorflow:global_step/sec: 524.442
INFO:tensorflow:loss = 12.54402, step = 401 (0.191 sec)
INFO:tensorflow:global_step/sec: 523.837
INFO:tensorflow:loss = 11.637623, step = 501 (0.191 sec)
INFO:tensorflow:global_step/sec: 531.361
INFO:tensorflow:loss = 8.233404, step = 601 (0.188 sec)
INFO:tensorflow:global_step/sec: 519.661
INFO:tensorflow:loss = 8.66986, step = 701 (0.192 sec)
INFO:tensorflow:global_step/sec: 512.726
INFO:tensorflow:loss = 6.985471, step = 801 (0.195 sec)
INFO:tensorflow:global_step/sec: 517.947
INFO:tensorflow:loss = 6.3264084, step = 901 (0.193 sec)
INFO:tensorflow:Saving checkpoints for 1000 into /tmp/tmp4nrlxU/model.ckpt.
INFO:tensorflow:Loss for final step: 5.9725046.
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Starting evaluation at 2018-03-31-09:49:41
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from /tmp/tmp4nrlxU/model.ckpt-1000
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Finished evaluation at 2018-03-31-09:49:41
INFO:tensorflow:Saving dict for global step 1000: accuracy = 0.975, accuracy_baseline = 0.825, auc = 0.99278504, auc_precision_recall = 0.9732998, average_loss = 0.095491454, global_step = 1000, label/mean = 0.175, loss = 5.7294874, prediction/mean = 0.15055662
		
		Test set accuracy: 0.975
		
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from /tmp/tmp4nrlxU/model.ckpt-1000
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
		{'probabilities': array([0.9810508 , 0.01894918], dtype=float32), 'logits': array([-3.9468637], dtype=float32), 'classes': array(['0'], dtype=object), 'class_ids': array([0]), 'logistic': array([0.01894918], dtype=float32)}
		
		Prediction is "ORANGE" (98.1%), expected "ORANGE"
		{'probabilities': array([0.9895298 , 0.01047021], dtype=float32), 'logits': array([-4.5486956], dtype=float32), 'classes': array(['0'], dtype=object), 'class_ids': array([0]), 'logistic': array([0.01047021], dtype=float32)}
		
		Prediction is "ORANGE" (99.0%), expected "ORANGE"
		{'probabilities': array([0.15968752, 0.84031254], dtype=float32), 'logits': array([1.660555], dtype=float32), 'classes': array(['1'], dtype=object), 'class_ids': array([1]), 'logistic': array([0.84031254], dtype=float32)}
		
		Prediction is "BLUE" (84.0%), expected "BLUE"
		{'probabilities': array([0.15968752, 0.84031254], dtype=float32), 'logits': array([1.660555], dtype=float32), 'classes': array(['1'], dtype=object), 'class_ids': array([1]), 'logistic': array([0.84031254], dtype=float32)}
		
		Prediction is "BLUE" (84.0%), expected "BLUE"

Podemos ver que hemos obtenido un “accuracy” de 0.975 y que parece que funciona bastante bien con las predicciones solicitadas.

7. Referencias

8. Conclusiones

Como hemos visto TensorFlow nos facilita mucho la construcción de redes neuronales complejas. Y lo más importante es que está preparado para escalar la solución y optimizar su ejecución en arquitecturas distribuidas sobre GPUs que aumentan increiblemente su eficiencia.

Invocar a procedimientos usando Spring Boot Data JPA

$
0
0

Índice de contenidos

1. Introducción

En este tutorial vamos a ver las distintas posibilidades que tenemos a la hora de invocar a procedimientos o métodos almacenados en ORACLE usando Spring Boot Data JPA.

A modo de introducción previa al tutorial os dejo otra forma de hacerlo con Spring JDBC como nos indicaba Daniel en su tutorial.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3, 500GB Flash Storage)
  • Sistema Operativo: Mac OS Sierra 10.13.4
  • Entorno de desarrollo: IntelliJ IDEA 2018.1
  • JDK 1.8
  • Apache Maven 3.5.0

3. Dependencias y configuración

Lo primero que haremos será crear un proyecto Maven Spring Boot y añadiremos las siguientes dependencias en el fichero pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc8</artifactId>
        <version>12.2.0.1</version>
    </dependency>
</dependencies>

Tras añadir las dependencias habrá que configurar el datasource en el fichero application.yml:

spring:
    datasource:
        url: jdbc:oracle:thin:@//localhost:5000/dbname
        username: user
        password: pass
        driver-class-name: oracle.jdbc.OracleDriver

4. Usando @NamedStoredProcedureQuery

La primera posibilidad que nos da Spring es definir el acceso mediante anotaciones de Java de la siguiente manera:

@Entity
@NamedStoredProcedureQueries({
    @NamedStoredProcedureQuery(
        name = "java_procedure_name",
        procedureName = "SCHEMA_NAME.PACKAGE_NAME.METHOD_OR_PROCEDURE_NAME",
        parameters = {
          @StoredProcedureParameter(mode=ParameterMode.IN, name="inputParam1", type=String.class),
          @StoredProcedureParameter(mode=ParameterMode.IN, name="inputParam2", type=String.class),
          @StoredProcedureParameter(mode=ParameterMode.OUT, name="outputParam", type=String.class)
    })
})
public class TableName implements Serializable {
}

Tras definir el procedimiento podremos invocarlo usando CrudRepository:

public interface TableNameRepository extends CrudRepository<TableName, Long> {
    @Procedure(name = "java_procedure_name")
    String procedureName(@Param("inputParam1") String inputParam1, @Param("inputParam2") String inputParam2);
}

5. Usando createStoredProcedureQuery

La siguiente forma que tenemos para invocar a los procedimientos es usar el método createStoredProcedureQuery(String procedureName) que expone javax.persistence.EntityManager. De esta forma podremos definir la invocación de los procedimientos de forma dinámica:

@Repository
public class ProcedureInvoker {

    private final EntityManager entityManager;

    @Autowired
    public ProcedureInvoker(final EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    ...
        StoredProcedureQuery storedProcedureQuery = entityManager.createStoredProcedureQuery("SCHEMA_NAME.PACKAGE_NAME.METHOD_OR_PROCEDURE_NAME");
        
        // Registrar los parámetros de entrada y salida
        storedProcedureQuery.registerStoredProcedureParameter("INPUT_PROCEDURE_PARAMETER_NAME", String.class, ParameterMode.IN);
        storedProcedureQuery.registerStoredProcedureParameter("OUTPUT_PROCEDURE_PARAMETER_NAME1", String.class, ParameterMode.OUT);
        storedProcedureQuery.registerStoredProcedureParameter("OUTPUT_PROCEDURE_PARAMETER_NAME2", Long.class, ParameterMode.OUT);

        // Configuramos el valor de entrada
        storedProcedureQuery.setParameter("INPUT_PROCEDURE_PARAMETER_NAME", "value")

        // Realizamos la llamada al procedimiento
        storedProcedureQuery.execute();

        // Obtenemos los valores de salida
        final String outputValue1 = (String) query.getOutputParameterValue("OUTPUT_PROCEDURE_PARAMETER_NAME1");
        final Long outputValue2 = (Long) query.getOutputParameterValue("OUTPUT_PROCEDURE_PARAMETER_NAME2");
    ...
}

También podremos invocar a procedimientos que devuelven un SYS_REFCURSOR y almacenar el resultado en un objecto List de la siguiente manera:

...
    storedProcedureQuery.registerStoredProcedureParameter(2, Class.class, ParameterMode.REF_CURSOR)

    storedProcedureQuery.execute();

    // Obtenemos el resultado del cursos en una lista
    List<Object[]> results = storedProcedure.getResultList();

    // Recorremos la lista con map y devolvemos un List<BusinessObject>
    storedProcedureResponse.stream().map(result -> new BusinessObject(
        (String) result[0],
	    (Long) result[1]
    )).collect(Collectors.toList());

6. Conclusiones

Usar anotaciones hará que tengamos un código más limpio y orientado a JPA pero tiene dos puntos en contra:

  • Solo podremos devolver un único parámetro o un cursor.
  • A nivel conceptual, un procedimiento no pertenece a una tabla sino a la base de datos. Pero Spring nos obliga a anotar la clase @NamedStoredProcedureQuery con @Entity cuando puede que el procedimiento que va a ser invocado devuelva valores que no representan ninguna tabla dentro de nuestro modelo de datos.

Usando createStoredProcedureQuery tendremos muchas más flexibilidad a la hora de obtener todo tipo de valores pero también hará que el código se pueda descontrolar sino tenemos cuidado a la hora de usar este tipo de estructuras.

Un saludo.

Alejandro

aalmazan@autentia.com

ES6: el remozado JavaScript. Parte I: variables y constantes

$
0
0

Índice de contenidos

  • 1. Introducción
  • 2. Declaración de variables
  • 3. Constantes
  • 4. Funciones
    • 4.1. Valores por defecto
    • 4.2. Parámetro REST y operador SPREAD
    • 4.3. Funciones Arrow
  • 5. Objetos y Arrays
    • 5.1. Forma abreviada para quitar repeticiones
    • 5.2. Asignación por Destructuring
    • 5.3. Iteraciones: for… in vs for… of
    • 5.4. Entendiendo Iteradores, y como crearlos
    • 5.5. Generadores
    • 5.6. Los nuevos tipos Map y WeakMap
    • 5.7. Los nuevos tipos Set y WeakSet
  • 6. Clases
    • 6.1. Entendiendo los prototipos
    • 6.2. Clases
    • 6.3. Módulos
  • 7. Otras novedades en el lenguaje
    • 7.1. Promesas
      • 7.1.1. Async y await
    • 7.2. Proxies e Interceptores
    • 7.3. Strings y Literales
      • 7.3.1. Nuevos métodos de String
    • 7.4. Novedades en los objetos Math y Number
      • 7.4.1 La clase Math
  • 8. Epílogo
  • 9. Referencias
El objetivo de este artículo es dar un repaso a las novedades introducidas en las últimas versiones de JavaScript.

1. Introducción

Ya hace tiempo que la nueva especificación que rige JavaScript viera la luz, y llegó pertrechada de novedades. Sin embargo, hay quien opina que su venida se demoró demasiado, y ahora tiene que ganar terreno frente a otros lenguajes, que parecen estarle ganando por la mano. Pero tiene la ventaja y virtud de que es un estándar, así que acabará sobreponiéndose.

Ningún programador que hubiera trabajado con JavaScript se mostraba indiferente: o bien eran auténticos defensores a ultranza de dicho lenguaje, o bien sus más fervientes detractores. Yo me encuentro en el primer grupo, pero comprendo perfectamente al segundo.

El mayor punto de frustración con el que se topa un desarrollador bregado en programación orientada a objetos, es que JavaScript le parece un “quiero y no puedo”, que carece de herencia, de polimorfismo y de tipos, e intenta trasladar sus hábitos, a veces mediante ingeniosas triquiñuelas, a un lenguaje que puede instanciar un objeto sin ni siquiera haber definido su clase. Y es que JavaScript usa un paradigma de programación orientada a prototipos, que está pensado para ejecutarse y no consumir grandes recursos. Simplemente es diferente. Es otra cosa. No es Java, ni se comporta como Java. Pero en cuanto lo entiendes, empiezas a apreciarlo, y a disfrutar de sus matices.

Claro, que a veces tiene cosas que exasperan a cualquiera. Por ejemplo, abra la consola de su navegador y compruebe cómo 0.1 + 0.2 no es 0.3…


– ¿¿¿Cómo???

A ver, a ver, vamos más despacio, ¿cómo que es falso?


¡Ahhhhhhhh! ¿y eso? …

Es como el chiste: ¿Qué prefieres? ¿velocidad o precisión?

En fin, eso es porque implementa el estándar definido en la aritmética de punto flotante IEEE-754, y es inherente a muchos lenguajes de programación, incluidos todos los lenguajes EcmaScript (JavaScript, ActionScript, etc…). Si quieres conocer más detalles de esta imprecisión, en el post “Why is 0.1+0.2 not equal to 0.3 in most programming languages?” lo explica muy bien.

Pero vamos a empezar con las novedades de esta nueva implementación de JavaScript. El nombre oficial es ECMA 262, 6ª Edición, pero es conocido como ES6, ECMAScript 6, o ES2015, dado que su especificación se liberó en Junio de 2015.

Su principal cambio, respecto a versiones anteriores de la especificación, es que introduce una nueva sintaxis manteniendo la retrocompatibilidad, es modular, y esta vez sí que tiene una forma directa de definir clases y herencia.

2. Declaración de variables.

Una de las razones por las que JavaScript no gusta a muchos programadores, es que el ámbito de las variables no es el mismo que en otros lenguajes, siendo en muchos casos variables globales.

function foo(){
  for (var i=0; i<10; i++){
    console.log(i);
  }

  console.log(i + 3); //devuelve 13
}

En el ejemplo anterior vemos que la variable i sigue existiendo fuera del bucle y además conserva su último valor 10.

Desde la concepción de JavaScript, las variables declaradas dentro de un bloque se mueven al principio del bloque en tiempo de interpretación. Eso hace que siempre las podamos referenciar sin que dé error. Este proceder se llama hoisting. En el caso que nos ocupa sería:

function foo(){
  var i;
  for (i=0; i<10; i++){
    console.log(i);
  }

  console.log(i + 3); //devuelve 13
}

En ES6 sí se introduce una nueva forma de declarar variables, que es mediante la palabra reservada let. El ámbito de las variables así definidas está limitado al bloque donde se han declarado y no hacen hoisting.

function foo(){
  var a = 3;  
  if (a < 5){
    let b = 10;
    console.log(a + b); //devuelve 13
  }

  console.log(b); //b no está definida fuera del if
}

La variable b no está viva fuera del bloque definido por las llaves del if. Si se ejecuta está función el intérprete de JavaScript debería dar un ReferenceError al intentar referirse a una variable que no existe.


Esto es especialmente útil en los bucles que llaman a funciones. Recordemos que JavaScript permite la programación funcional, y pasar otra función como argumento de una función. Un caso particular de este hecho son los callbacks.

let vocales = ['a','e','i','o','u'];

function foo(){
  for (var i=0; i < 5; i++){

    bar(function(){
      console.log('La vocal es :' + vocales[i]);
    });
  }
}

En este caso es evidente y casi inmediato, pero imagenemos por un momento que la función bar() es muy compleja. Cuando se empiece a ejecutar el primer callback es muy probable que el bucle haya terminado, y como i es una variable que sigue existiendo al salir del bucle puede acabar llamando al callback con el valor equivocado.

Antaño, esto se solucionaba pasando la variable como parámetro al callback. Pero ahora podemos usar let, y el ámbito estará definido en la ejecución de dicho callback.

let vocales = ['a','e','i','o','u'];

function foo(){
  for (let i=0; i < 5; i++){

    bar(function(){
      console.log('La vocal es :' + vocales[i]);
    });
  }
}

Las variables declaradas con let aunque pueden ser sobreescritas, no pueden volver a ser declaradas en el mismo bloque.


Entonces, ¿por qué el siguiente código no da error?


Porque se pueden declarar variables con el mismo nombre usando let siempre que estén declaradas en distintos bloques. Al tener distinto ámbito, se trata de variables diferentes aunque tengan el mismo nombre. Otra cosa, es que por claridad y legibilidad sea conveniente.

let version = '';
version = '1.2';
console.log(version); //version vale 1.2

if (version != ""){
  let version = '1.3';
  console.log(version); //version vale 1.3
}

console.log(version); //version vale 1.2

3. Constantes

Por fin se pueden definir constantes en JavaScript mediante la palabra reservada const. Al declararla es obligatorio definir su valor, y luego sólo podrá ser consultada.

const MAX_SIZE = 4;

for (let i=0; i < MAX_SIZE; i++){
  console.log(i);
}

Una vez declarada no podrá ser modificada, y arrojará un error si se intenta.


Por otro lado, el ámbito de las constantes se limita al bloque donde se han declarado.

const MAX_SIZE = 4;

for (let i=0; i < MAX_SIZE; i++){
  const MSG = "La variable es: "
  console.log(MSG + i);
}
console.log(MSG); //daría error porque MSG aquí no está definida

El resultado de ejecutar el código anterior es:


Hay que tener en cuenta que las constantes también pueden ser objetos y como constantes, si intentamos volver a asignarles un valor nos dará el correspondiente error:


Sin embargo, y aunque no podemos asignar un nuevo objeto a esa constante, sí que podemos modificar las propiedades de dicho objeto. Hay que entender cómo funciona por debajo: con la palabra reservada const estamos asignando un nombre a una dirección de memoria. Y a ese nombre no le podremos asignar otra dirección de memoria distinta al tratarse de una constante. Pero al tratarse de un objeto, si podemos modificar sus propiedades sin modificar su dirección de memoria. Así, podríamos hacer lo siguiente y no daría error:

const GAME = {lives: 3, width: 500, height: 300};
console.log(GAME.lives);  //el resultado será 3

GAME.lives = 5;
console.log(GAME.lives);  //el resultado será 5

Si lo que queremos es definir un objeto inmutable como constante sería utilizando Object.freeze().

const GAME = Object.freeze({lives: 3, width: 500, height: 300});

//es GAME inmutable?
Object.isFrozen(GAME);

Y comprobaríamos que dicho objeto es inmutable con Object.isFrozen().

Este post está formado de tres partes y en breve se publicará la siguiente: ES6: el remozado JavaScript. Funciones, Objetos y Arrays

Aprovechando las posibilidades de Selenium en Python

$
0
0

Índice de contenidos

1. Introducción

Hoy en día la recopilación de datos, extracción de imágenes/contenido, seguimiento de precios o análisis de sentimientos en aplicaciones webs se ha convertido en una fuente de datos rentable.

En este ejemplo vamos a ver que Selenium no es solo un entorno de pruebas para aplicaciones web, vamos a dar un paso más y vamos a extraer datos de esta misma web. A continuación, vamos a analizar estos datos de una manera sencilla para que puedas ver el potencial de esta herramienta.

2. Pasos previos

  • Instalar Python (en mi caso tengo Python 3.6 con Anaconda). Puedes descargarlo aquí. Está para todos los SSOO.
  • Instalar Selenium. Si usas Anaconda en window ejecuta tu cmd como administrador y escribe:
    conda install -c conda-forge selenium
  • Descargar el driver del navegador. En mi caso uso Firefox y lo puedes descargar desde aquí. En mi caso uso window 7 64bit por lo que he descargado geckodriver-v0.20.0- win64.zip

3. Objetivo

El objetivo de este tutorial es conocer cuál es el autor de adictosaltrabajo.com con más índice de impacto en sus publicaciones en la web.

4. Explicación del código paso a paso

Lo primero de todo importamos selenium y añadimos la ruta donde se encuentra instalado el driver de firefox que hemos descargado anteriormente.

from selenium import webdriver
	driver = webdriver.Firefox(executable_path=r'C:\Users\rprast\Downloads\geckodriver-v0.19.1-win64\geckodriver.exe')

En segundo lugar, tendremos que hacer una espera para que la web cargue completamente y accedemos a la web donde se encuentran los usuarios.

driver.implicitly_wait(30)
	driver.maximize_window()
	driver.get("https://www.adictosaltrabajo.com/quienes-somos/")

Une vez seguidos estos pasos, se crean dos listas una para el ratio de índice de impacto y otra con los nombres.

ratiolist=[]
	nombreslist=[]

	i_list=range(1, 143, 1)

Ahora tenemos que extraer el contenido que nos interesa. Que es:

  • Nombres
  • Publicaciones
  • Visitas
Para extraer estos datos iremos a inspeccionar elemento y analizaremos los xpath de estos campos. El xpath es la posición del elemento en la estructura jerárquica del XML de la web y se puede copiar fácilmente como se puede ver en el siguiente vídeo:

Por ejemplo, para el caso de los tres primeros nombres, hemos copiado aquí su xpath:

/html/body/div[4]/div[1]/table[1]/tbody/tr/td[2]/div/h4/a
/html/body/div[4]/div[1]/table[2]/tbody/tr/td[2]/div/h4/a
/html/body/div[4]/div[1]/table[3]/tbody/tr/td[2]/div/h4/a

Analizando estas 3 líneas podemos ver que solo cambia el número de table. De esta forma podemos ir cambiando este número para obtener el nombre de cada persona.

Este mismo proceso lo hemos hecho para el nombre, visitas y publicaciones.

for i in i_list:
	  nom1=('/html/body/div[4]/div[1]/table['+ str(i+1))
	  nom2=(']/tbody/tr/td[2]/div/h4/a')
	  nombre=(nom1+nom2)

	  publi1=('/html/body/div[4]/div[1]/table['+ str(i))
	  publi2=(']/tbody/tr/td[2]/p[2]/a')
	  publicaciones=(publi1+publi2)

	  visi2=(']/tbody/tr/td[2]/p[2]/b')
	  visitas=(publi1+visi2)
	  ...

Ahora ya tenemos las direcciones xpath, ya solo nos queda decirle al objeto driver que encuentre estos elementos,

for i in i_list:
	  ...
	  nombres=driver.find_element_by_xpath(nombre)
	  publicaciones = driver.find_element_by_xpath(publicaciones)
	  visitas = driver.find_element_by_xpath(visitas)
	  ...

Previamente habíamos definido dos listas (ratio y nombres). La lista de nombres la iremos rellenando cuando extraemos el texto de nombres. La lista de ratio es la que representa el índice de impacto, el cual se define con la división entre el número de visitas y el número de publicaciones. No podemos tener en cuenta un dato relevante como es la fecha de alta del autor, pero para este ejemplo didáctico esta aproximación es más que sufiente.

for i in i_list:
	  ...

	  p=publicaciones.text
	  p1=('publicación')
	  p2=('publicaciones')
	  findstr1= (p.find(p1))
	  findstr2= (p.find(p2))
	  if findstr2==-1:  
	    p=p[:-12]
	  else:
	    p=p[:-14]
	    
	  p=int(p)
	  v=visitas.text
	  commas_removed = v.replace('.', '') 
	  v = float(commas_removed)
	  try:
	    ratio=v/p
	  except:
	    ratio=0
	  
	  n=nombres.text
	  ratiolist.append(ratio)
	  nombreslist.append(n)

A partir de ahí, encontramos los nombres, publicaciones y visitas partiendo del string de xpath. Una vez hecho esto extraemos el texto del xpath con .text, eliminamos los puntos y lo pasamos a integer o float. Una vez calculado el índice de impacto, lo añadimos a la lista de índices de impacto y su autor correspondiente a la lista de autores.

Por último, cerramos la ventana y buscamos el mayor índice de impacto que se encuentra en la lista. Una vez localizado, escribiremos por pantalla el nombre del autor a quién corresponde dicho índice de impacto.

driver.quit()

	ratiomax=max(ratiolist)

	idx = max(range(len(ratiolist)), key = lambda i: ratiolist[i])
	print('El autor que tiene mas impacto en sus publicaciones es')
	print(nombreslist[idx-1])

5. A tener en cuenta

  • Cuando extraemos el texto en las publicaciones hay 2 opciones, publicación o publicaciones. Esto se ha solventado con un if que sustrae del string respectivamente 12 (publicación) o 14 caracteres (publicaciones).
  • Existe la posibilidad de que algunos autores tengan 0 publicaciones por lo que el ratio da un error al tener un división con un cero. Esto se ha solventado con un try donde se asigna 0 como ratio en estos casos.

6. Resultado

El autor que tiene mas impacto en sus publicaciones es: Enrique Medina Montenegro

7. Conclusiones

Como hemos podido comprobar, esto es una forma rápida de analizar datos procedentes de una aplicación web.

Espero que os haya servido

Un saludo.

ES6: el remozado JavaScript. Parte II: funciones, objetos y arrays

$
0
0

Índice de contenidos

El objetivo de este artículo es introducir las novedades de ES6 relativas a Funciones, Objetos y Arrays.

4. Funciones

Las funciones es uno de los apartados dentro de la especificación donde se han hecho más cambios. Veámoslos

4.1. Valores por defecto

Una cosa que siempre ha tenido JavaScript, y que ha sido fruto de controversia entre los programadores de otros lenguajes, es que se puede definir una función con más parámetros que con los que se la llama.

function ejemplo(nombre, apellidos) {
  if (apellidos != undefined){
    console.log("Hola " + nombre + " " + apellidos);
  } else {
    console.log("Hola " + nombre);
  }
}

ejemplo("Manuel");
ejemplo("Pepito", "Grillo");

Pero ahora, como pasaba en otros lenguajes EcmaScript como Flex, se pueden declarar valores por defecto para esos parámetros.

function ejemplo(nombre, apellidos = "García") {
  console.log("Hola " + nombre + " " + apellidos);
}

ejemplo("Manuel");                //Hola Manuel García
ejemplo("Pepito", "Grillo");      //Hola Pepito Grillo

A las funciones en JavaScript que pueden recibir un número variable de parámetros se les llama funciones variádicas. Y es uno de los puntos que aporta potencia al lenguaje, pero que lo convierte en incómodo a aquellos desarrolladores acostumbrados a otros lenguajes de programación, que para nada esperan este comportamiento.

4.2. Parámetro REST y operador SPREAD

Ahora en una función se permite que el último argumento que se le pasa, sea especial y se denota con tres puntos suspensivos. Eso nos indica que este último argumento, recibirá todos los parámetros de más de la función en forma de array:

function ejemplo (param1, param2, ...restParams) { 
  console.log(restParams);
}
ejemplo('a','b','c','d','e','f');  
//el resultado es el array ["c", "d", "e", "f"]

¿Y la operación contraria? Imaginemos que lo que tenemos es una función variádica que espera un número variable de parámetros en lugar de un array. Podemos convertir fácilmente el array a una lista de parámetros con el operador SPREAD, que también se denota con tres puntos suspensivos.

Esto puede parecer que da lugar a equívocos, pero fijémonos que uno se utiliza para definir una función, y el otro para invocarla.

let aVocales = ["a", "e", "i", "o", "u"];

function bar(){
  let txt = '';
  for(let i in arguments){
    txt += arguments[i];
  }
  console.log(txt); // la salida por consola es aeiou
}

function foo(a){
  console.log(a.length);  //a es un array
  bar(...a);    //esto es como llamar a bar('a','e','i','o','u')
}

foo(aVocales);

4.3. Funciones Arrow

Se denominan funciones arrow, se denotan por el símbolo => y se utilizan de la siguiente manera. Las siguientes expresiones son equivalentes

let multiplicar = function (a,b) {
                    return a * b;
                  }

let multiplicar = (a,b) => a * b;

console.log(multiplicar(3,5)); //15

Se utiliza mucho en las funciones anónimas que se definen como callbacks de otras funciones porque la función arrow sí preserva el contexto del this, al contrario de lo que pasa con las funciones estándar.

Por ejemplo, muchas veces habremos visto código de terceros que hacen cosas como var that = this y luego se refieren a ese that. Eso lo hacen porque la palabra this hace referencia al contexto del callback y no de la función externa. Veamos un ejemplo.

let ImageHandler = {
  id: "",

  init: function (id) {
    this.id = id;
    let that = this;
    document.addEventListener("click", function (event) {
      that.show(event);   // se usa that aquí, porque this haría referencia al contexto de la función anónima
    }, false);
  },

  show: function (event) {
    console.log("Se ha disparado el evento de typo " + event.type);
  }
};

Sin embargo, con una función Arrow quedaría:

let ImageHandler = {
  id: "",

  init: function (id) {
    this.id = id;
    document.addEventListener("click", (event) => this.show(event), false);
  },

  show: function (event) {
    console.log(event.type + " has been fired for " + this.id);
  }
};

Cuando la función arrow sólo recibe un argumento, no es necesario escribir paréntesis:

event => this.show()

Pero sí cuando no hay argumentos o son más de uno.

() => "Soy Íñigo Montoya";
(a,b) => a * b;

5. Objetos y Arrays

5.1. Forma abreviada para quitar repeticiones

En numerosas ocasiones nos hemos encontrado devolviendo un objeto con nombres y valores repetidos, donde el primero hace referencia a la propiedad, y el segundo es la variable con su valor

return {width: width, height: height};

Pero ahora es posible devolver una versión abreviada, donde la propiedad será como el nombre de la variable que contiene el valor. En el siguiente ejemplo, ambas funciones son equivalentes:

function getSizes(){
  let width = window.innerWidth;
  let height = window.innerHeight - 200; 
  return {width: width, height: height};  
}

function getSizes(){
  let width = window.innerWidth;
  let height = window.innerHeight - 200; 
  return {width, height}; 
}

Y también funciona en la asignación de variables:

let sizes = {width, height};
  console.log(sizes.width);

En lugar de

let sizes = {width: width, height: height};

5.2. Asignación por Destructuring

Se pueden asignar variables recorriendo las propiedades de un objeto de una en una, o bien se puede usar la asignación por destructuring, donde se asigna a cada variable la propiedad correspondiente.

let sizes = getSizes();
let width = sizes.width;
let height = sizes.height;

let {width, height} = getSizes();
console.log(width);
console.log(height);

No es obligatorio usar todas las propiedades del objeto. Se puede utilizar sólo una parte para hacer la asignación por destructuring.

let {width} = getSizes();

La asignación por destructuring también funciona para arrays.

let aVocales = ["a", "e", "i", "o", "u"];

let [z, y, x, w, v] = aVocales;
console.log(z);    //a
console.log(y);    //e
console.log(x);    //i
console.log(w);    //o
console.log(v);    //u

¿y funcionará con el operador Spread?

let aVocales = ["a", "e", "i", "o", "u"];

let [z, y, ...x] = aVocales;
console.log(z);    //a
console.log(y);    //e
console.log(x);    //["i", "o", "u"]

Pues sí que funciona, al menos en Google Chrome, aunque según la especificación, eso es de la próxima versión de EcmaScript ES7.

5.3. Iteraciones: for… in vs for… of

En las versiones previas de JavaScript ya se podía recorrer un array con bucles de tipo for… in

Realmente, recorre todas las propiedades enumerables de un objeto en un orden indeterminado.

let jugador = {
  name: "Sergio Callejas",
  posicion: "4",
  esTitular: true,
  dorsal: 33
}

for (propiedad in jugador){
  console.log(propiedad, eval('jugador.' + propiedad));
}

for (propiedad in jugador){
  console.log(propiedad, jugador[propiedad]);
}

De los dos bucles for, el primero nos obliga a acceder a la propiedad mediante la triquiñuela del eval(), porque si accedemos directamente mediante jugador.propiedad, no evalúa el valor de “propiedad” en cada vuelta del bucle, y busca literalmente el atributo llamado “propiedad”, y nos devolvería undefined.

Pero todos los objetos pueden recorrerse como arrays asociativos. Es decir, podemos acceder al nombre de ambas maneras.

console.log(propiedad.name)
console.log(propiedad["name"])

Y esto es lo que usamos en el segundo bucle.

Pero ¿y qué pasa si lo que tenemos es un array de objetos?

let equipo = [
  {name: "David Gómez", posicion: "5"},  
  {name: "Sergio Callejas", posicion: "4"},
  {name: "Javier Rodríguez", posicion: "3"},
  {name: "Laura Fernández", posicion: "2"},
  {name: "Manuel Fernández", posicion: "1"}
]


//recorremos por posición que ocupa en el array
for (i in equipo){
  console.log(equipo[i]);
}

Pero en ES6 existe una nueva forma de recorrer con for…of, que en lugar de por índice, recorre directamente los objetos que contiene:

//recorremos directamente los objetos del array
for (jugador of equipo){
  console.log(jugador.name);
}

Esto de for…of lo podemos hacer sobre los arrays porque son objetos iterables. Pero no es lo habitual en JavaScript. Si definimos un objeto, e intentamos recorrer sus propiedades, nos dará un error similar al siguiente: “TypeError: jugador[Symbol.iterator] is not a function”.


Pero siempre podemos crearlo. Veamos cómo.

5.4. Entendiendo Iteradores, y como crearlos

Hay algunos tipos de objetos que tienen implementado el iterador. Vamos a ver qué pinta tiene este iterador


Lo primero que se puede observar, es que la variable equipo, que es un array, sí implementa Symbol.iterator. Y al preguntar por iterador.next() nos devuelve un objeto con dos propiedades: value, que tiene el primer elemento del array, y done un booleano que nos indica si ya hemos recorrido todos los elementos.

Pero si usamos un objeto en lugar de un array, esto no funcionará porque no implementa un iterador. Pero podemos definirlo nosotros. Imaginemos el siguiente objeto no iterable:

let equipo = {
  posteBajo: {name: "David Gómez", dorsal: "00"},  
  posteAlto: {name: "Sergio Callejas", dorsal: "33"},
  alaPivot: {name: "Javier Rodríguez", dorsal: "23"},
  escolta: {name: "Laura Fernández", dorsal: "05"},
  base: {name: "Manuel Fernández", dorsal: "17"}
};

Y queremos iterar sobre sus atributos. Podríamos definir el siguiente iterador:

equipo[Symbol.iterator] = function(){
  let listaPosiciones = Object.keys(this);
  let i = 0;

  let next = () => {
    let posicion = listaPosiciones[i];
    return { 
      value : this[posicion], 
      done: (i++ >= listaPosiciones.length)
    };
  };

  return {next};
};

Una vez ya definido, podríamos recorrer las propiedades del objeto con un bucle de tipo for… of:

for (miembro of equipo){
  console.log(miembro);
};

5.5. Generadores

En esta nueva versión de JavaScript se introducen los generadores, que tienen todo el aspecto de una función, pero con algunas particularidades. La primera, y que es la que lo define, es que tras la palabra reservada function hay un asterisco *. Y en lugar de return usa la palabra reservada yield. Además, todo generador implementa un iterador.

function* miGenerador(){
  yield "uno";
  yield "dos";
  yield "tres";
}

let ejemplo = miGenerador();

Cuando se invoca a un generador, como en el ejemplo anterior, no hace nada de nada.

Como los iteradores, funciona cuando se llama a su método next(), entonces devuelve el primer yield, y se guarda el estado a la espera de que en algún momento se vuelva a llamar a next(). Cuando se llame a next() por segunda vez, devolverá el segundo yield. Y así hasta que no haya más.


Esto tiene múltiples implicaciones, ligadas a la preservación del estado y la asincronía. Una que se me ocurre es para implementar un asistente, wizard o sistema de pasos:

let wizardBox = {
  step1 : function(){
    console.log("paso 1 de 3");
  },
  step2 : function(){
    console.log("paso 2 de 3");
  },
  step3 : function(){
    console.log("paso 3 de 3");
  }
}

function* wizard(){
  yield wizardBox.step1();
  yield wizardBox.step2();
  yield wizardBox.step3();
}

let w = wizard();

w.next(); 
//ejecutamos el primer paso y hacemos lo que sea

w.next(); 
//se ejecuta el segundo paso continuado donde lo dejamos

w.next();
//se ejecuta el tercer paso continuando desde el punto anterior

Y otra de las más utilizadas es para escribir el iterador que poníamos de ejemplo en el punto anterior, pero de una forma mucho más simple:

equipo[Symbol.iterator] = function *(){
  let listaPosiciones = Object.keys(this);
  
  for (let posicion of listaPosiciones){
    yield this[posicion];
  }
};

5.6. Los nuevos tipos Map y WeakMap

En esta versión de JavaScript se introducen cuatro tipos nuevos de objetos: Map, WeakMap, Set y WeakSet.

Los mapas son un conjunto de pares clave / valor que implementan algunas operaciones:

  • para añadir elementos al mapa
  • para obtener un elemento del mapa
  • para consultar si está un elemento en el mapa
  • para quitar un elemento del mapa
  • para vaciar el mapa
let equipacion = new Map();
equipacion.set('camiseta',{size:'XXL', dorsal:'02'});
equipacion.set('zapatillas',46);
equipacion.set('pantalones','54');

equipacion.get('zapatillas');     //46
equipacion.get('camiseta').size;  //XXL
equipacion.size;                  //3

equipacion.has('pantalones');     //true
equipacion.delete('pantalones')   //quitamos el elemento pantalones
equipacion.has('pantalones');     //false
equipacion.clear();               //vaciamos el mapa
equipacion.size;                  //0

En el caso concreto de los mapas, son iterables, y admiten pares de objetos, tanto en las claves como en los valores:

let equipacion = new Map();
equipacion.set(equipo.posteBajo, {camiseta: 'XXXL', pie: 46});
equipacion.get(equipo.posteBajo);

Por el contrario los WeakMap son un tipo de mapas “débiles”. La primera diferencia es que las claves sólo pueden ser objetos. Nunca strings. Tampoco son iterables, y como tal no puedes consultar sus claves con Object.keys(). Además, hay que tener en cuenta que sus objetos se encuentran referenciados de forma “weak” (débil). Esto quiere decir, que el objeto que forma parte de la clave, si no es referenciado por nadie más que el WeakMap, es susceptible de que el Garbage Collector lo elimine.

Por tanto, el uso de los WeakMap tienen sus pros y sus contras.

En contra tienen que no son colecciones que se puedan recorrer con bucles de tipo for…of, hay que conocer la clave a priori para recuperar su valor. Tiene el peligro de que si nadie referencia a la clave, se pierda el valor asociado. A cambio, tiene la ventaja de no permitir memory leaks.

let equipacion = new WeakMap();

equipacion.set("posteBajo", {camiseta: 'XXXL', pie: 46});
//TypeError: la clave sólo puede ser un objeto

equipacion.set(equipo.posteBajo, {camiseta: 'XXXL', pie: 46});

console.log(equipacion.get(equipo.posteBajo));
//{camiseta: 'XXXL', pie: 46}

equipo.posteBajo = undefined;      //borramos el objeto posteBajo
console.log(equipacion.get(equipo.posteBajo));  //undefined

5.7. Los nuevos tipos Set y WeakSet

Al igual que los mapas, los conjuntos también se introducen en esta especificación de ES6. Consisten en colecciones de objetos o valores primitivos y no se pueden repetir.

let vocales = new Set();
vocales.add('a');
vocales.add('e');
vocales.add('i');
vocales.add('o');
vocales.add('u');
vocales.add('a');

console.log(vocales.size); //5

for (let vocal of vocales){ 
  console.log(vocal); 
}

Los WeakSet son como los Set pero con la misma línea argumental que los WeakMap:

  • No implementan un iterador
  • Sólo pueden contener objetos
  • Y su referencia a sus valores es débil
Este post está formado de tres partes y en breve se publicará la siguiente: ES6: el remozado JavaScript. Parte III: clases y otras novedades del lenguaje

Configuración del entorno de Xcode

$
0
0

Índice de contenidos

1. Introducción

Con este tutorial vamos a aprender a instalar el entorno de desarrollo llamado Xcode, el cual nos ofrece todo lo necesario para poder desarrollar aplicaciones para iPhone, iPad y Mac. Además veremos cómo crear una ID de Apple, el cual nos va a servir para posteriormente vincular el iPhone al entorno de desarrollo y poder probar nuestras aplicaciones en un dispositivo real.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware:
    • Portátil MacBook Pro 15’ (2.3 GHz Intel Core i7, 16GB DDR3)
    • iPhone 6S 4.7’ (Chip A9, 2GB RAM)
  • Sistema Operativo:
    • Mac OS High Sierra 10.13.4
    • OS 11.3
  • Entorno de desarrollo: Xcode 9.4 beta

3. Creación de ID de Apple

Para poder descargar el programa de Xcode, necesitamos una cuenta en Apple, por lo tanto, tendremos que dirigirnos a este enlace:

https://appleid.apple.com/account#!&page=create

Rellenamos todos los datos necesarios, y a continuación, nos pedirán que autentifiquemos mediante un código de 6 dígitos enviado al correo electrónico que hemos puesto en el proceso de registro.

4. Descarga e instalación del entorno

Para poder descargar el entorno nos dirigimos al enlace:

https://developer.apple.com/xcode/

A continuación, pulsaremos en el botón: ubicado en la parte superior derecha de la página.

Automáticamente, se nos dirigirá a la página donde debemos de aceptar el acuerdo legal de desarrolladores de Apple.

La siguiente página nos ofrecerá las diferentes herramientas disponibles de nuestro perfil de desarrolladores. Para dirigirnos a la página donde descargar el entorno debemos de pulsar sobre:

Es importante conocer la versión del sistema operativo del dispositivo para el cual se va a desarrollar, ya que, si bien es mejor elegir una versión Release del entorno, en este momento la única versión que cubre el desarrollo de aplicaciones para un iOS 11.3 en adelante, es el Xcode 9.4 Beta.

A su vez, también hay que tener en cuenta que si el sistema operativo del dispositivo donde se va a instalar Xcode es una versión inferior a Mac OS High Sierra 10.13.4, se deberá actualizar para poder tener soporte.

Una vez descargado, descomprimimos lo obtenido, ejecutamos el archivo de instalación Xcode-beta y seguimos los sencillos pasos de instalación.

La configuración del entorno nos va a servir como introducción, ya que en los siguientes tutoriales vamos a desarrollar una aplicación para dispositivos iOS.

ES6: el remozado JavaScript. Parte III: clases y otras novedades del lenguaje

$
0
0

Índice de contenidos

El objetivo de este artículo es introducir las novedades de ES6 relativas a clases y a otras funciones nuevas del lenguaje.

6. Clases

En JavaScript clásico hay dos corrientes principales para tratar con objetos:

  • una definir directamente el objeto sin preocuparnos de cuál es su clase, y si ésta nos es necesaria, preguntar por su prototipo y modificarlo a conveniencia.
  • Definir una función que será el constructor del ejemplo.

Si vamos a tratar con un objeto, que tendrá una única instancia, se define el objeto directamente:

let tablero = {
  filas: 20,
  columnas: 20, 
  bombas: 15

  render: function(){
    ... 
  }
};

Pero cuando podemos tener varias instancias será necesario definir una forma de crear objetos. Hasta ahora se hacía mediante una función constructora con la palabra reservada new.

function Pelota(id, punto, direccion){
  this.id = id;
  this.punto = punto;
  this.direccion = direccion;

  this.render = function(){
    ...
  };
}

let pelota1 = new Pelota(1, {x:0, y:0}, {deltaX: 4, deltaY: 3} );
let pelota2 = new Pelota(2, {x:0, y:0}, {deltaX: -1, deltaY: 2} );

Pero imaginemos que en lugar de dos instancias del objeto “pelota” hay miles de instancias, todas moviéndose, y rebotando por la pantallita.

Cada método que hemos añadido a la clase Pelota, tiene una dirección de memoria distinta apuntándole a él. En otros lenguajes como Java ésto es normal, pero en los navegadores históricamente se ha controlado mucho la memoria, ya que la aplicación se ejecuta en el navegador, que hasta no hace mucho, tenía unos recursos muy limitados.

Entonces, ¿es necesario que haya miles de funciones iguales que renderizan la posición de la pelota? ¿o sería suficiente con definir una única función externa?

function Pelota(id, punto, direccion){
  this.id = id;
  this.punto = punto;
  this.direccion = direccion;
}

function renderPelota(pelota){
  ... 
}

Lo único malo de hacerlo así es que la función parece que no tiene nada que ver con el objeto. Lo que se hacía era obtener el prototipo del objeto, y añadirle la función en cuestión.

Pelota.prototype.render = function(){
  ... 
}


let pelota1 = new Pelota(1, {x:0, y:0}, {deltaX: 4, deltaY: 3} );
let pelota2 = new Pelota(2, {x:0, y:0}, {deltaX: -1, deltaY: 2} );

pelota1.render();
pelota2.render();

Y con los prototipos llega uno de los mayores fracasos de JavaScript en cuanto a su popularidad. Y es que ha fracasado totalmente a la hora de comunicar a programadores de otros lenguajes de programación su funcionamiento. Durante años, los programadores de Java han intentado programar JavaScript como si fuera un lenguaje de Programación Orientado a Objetos, como el que ellos conocen, como Java. Y no. JavaScript es un lenguaje orientado a objetos sin clases. En su lugar todo objeto tiene un prototipo, que no es sino otro objeto.

6.1. Entendiendo los prototipos

El prototipo de un objeto es un objeto que nos devuelve su constructor y sus métodos. Con lo cual se puede recrear el objeto a partir de su prototipo.

Rectangulo = function(alto, ancho){
  this.alto = alto;
  this.ancho = ancho;
}
Rectangulo.prototype.area = function(){
  return this.alto * this.ancho;
}

let rectangulo1 = new Rectangulo(3,5);
console.log(rectangulo1.area());  //15

Si preguntamos por el prototipo de la clase Rectángulo nos devuelve su constructor y el método area.


Y podemos definir la clase Cuadrado en base al prototipo de Rectángulo:

Cuadrado = function(lado){
  this.lado = lado;
  this.alto = lado;
  this.ancho = lado;
}
Cuadrado.prototype = Object.create(Rectangulo.prototype);
let cuadrado1 = new Cuadrado(2);

Aunque no hayamos definido el área del cuadrado, está definida por la herencia del rectángulo:

console.log(cuadrado1.area()); //4

Y vemos que cuadrado1 es una instancia de Cuadrado, pero también es una instancia de Rectángulo. En este caso se dice que está en la cadena de prototipos.

console.log(cuadrado1 instanceof Cuadrado); //true
console.log(cuadrado1 instanceof Rectangulo); //true

Y ésto es uno de los mayores motivos de desasosiego para los programadores de otros lenguajes, que esperan que JavaScript se comporte como ellos esperan siguiendo el paradigma de POO que conocen. Es por eso, que la comunidad de desarrolladores, en lugar de aprender el lenguaje, ha ejercido presión para que éste evolucionara por unos caminos donde no les fallara la intuición. Y por eso, en ES6 se incluyen clases y modularización, que per sé no es malo, y así se acerca a una comunidad mucho más amplia.

6.2. Clases

Esta necesidad se ha visto satisfecha en la versión ES6. Y así, ahora tenemos clases para definir objetos.

class Rectangulo {

  constructor(alto, ancho){
    this.alto = alto;
    this.ancho = ancho;
  };

  area(){
    return this.alto * this.ancho;
  };

};

let r = new Rectangulo(2,3);
console.log(r.area()); //6

Realmente JavaScript no ha cambiado su paradigma orientado a prototipos. Esta forma de escribir clases es meramente azúcar sintáctico que por debajo se traduce en lo visto en el punto anterior, pero al menos, es una notación con la que la mayoría de desarrolladores están más familiarizados.

Y nos permite definir una herencia de una forma mucho más amigable.

class Cuadrado extends Rectangulo {

  constructor(lado){
    super(lado,lado);
    this.lado = lado;
  };

};

let c = new Cuadrado(3);
console.log(c.area());  //9

6.3. Módulos

En ES6 se introduce el concepto de módulo que no es otro que permitir trocear el código en distintos ficheros, de forma que se permita la exportación de clases, funciones, variables o constantes e importarlos desde otro módulo.

Así, nuestro ejemplo anterior quedaría guardado en un fichero poligonos.js con el siguiente código:

export class Rectangulo {

  constructor(alto, ancho){
    this.alto = alto;
    this.ancho = ancho;
  };

  area(){
    return this.alto * this.ancho;
  };

};


export class Cuadrado extends Rectangulo {

  constructor(lado){
    super(lado,lado);
    this.lado = lado;
  };

};

O si preferimos exportar y separar la definición de las clases, de cuáles exportamos:

class Rectangulo {
  ...
};


class Cuadrado extends Rectangulo {
  ...
};

export {Rectangulo, Cuadrado};

Y para importarlas desde otro fichero y hacer uso de ellas:

import { Rectangulo, Cuadrado } from 'poligonos.js';

let c = new Cuadrado(2);

Y esto se puede hacer tanto a nivel de clase, como de funciones, variables o constantes. Todo muy útil, y muy esperado por una comunidad de desarrolladores acostumbrados a modularizar su código. Las ventajas son incontestables, empezando desde el principio de responsabilidad única, a una simple organización de código. Pero aquí es donde viene la mala noticia, y es que aunque está definido en el estándar, no se ponen de acuerdo cómo implementarlo en los navegadores. Y ninguno de ellos lo implementa de forma nativa.

De todas formas, vamos a meditar qué significa implementar ésto en el navegador: la realidad es que la página html importaría un fichero JavaScript, que a su vez importaría a otros ficheros JavaScripts resolviendo todas sus dependencias e importando para ello más ficheros JavaScript. La resolución en cascada podría traducirse en miles de llamadas al servidor para traerse pequeños módulos JavaScript. Y aquí es donde viene uno de los cambios que han revolucionado el mundo front, y es que antes para ejecutar JavaScript te valía un navegador, y ahora se requiere un proceso de “compilación”, que normalmente realiza un TaskRunner tipo grunt, gulp, parcel o webpack., que empaqueta todo nuestro código diseminado por módulos en un único fichero (bundle) entendible por el navegador, que a veces ha sufrido procesos de minificación, de validación, transpilación a ES5 o varias cosas más.

En cualquier caso, hay un pollyfill para cargar módulos de forma estándar.

7. Otras novedades en el lenguaje

Son muchas las novedades que se incluyen en esta nueva especificación, y que vienen heredadas de lo que pasa en otros lenguajes.

7.1. Promesas

A medida que las interfaces son más atractivas, requerimos que la interacción con las mismas sea más inmediata, consiguiendo una buena experiencia de usuario, que de otro modo sería pobre. Pero al contrario de lo que ocurre en otros lenguajes, donde se pueden ejecutar varios hilos de ejecución concurrentemente, JavaScript tiene un único hilo de ejecución de forma que hasta que no ejecuta una instrucción, no pasa a la siguiente. Eso puede llegar a producir retrasos indeseables, e incluso pérdida de control por parte del usuario.

instruccion1();
instruccion2();

Hay que entender el hilo de ejecución como una cola. Cuando vinculamos una acción a un evento, lo que estamos haciendo es encolar la acción al final de la cola de instrucciones a ejecutar, y hasta que no se termine, no devolverá el control. Por ejemplo, definimos que al pulsar una tecla se ejecute una acción. Ésta no es inmediata y se encola.

A veces nos interesa cierta asincronía adrede, cuando queremos que se ejecute cierta instrucción pero nos interesa devolver el control inmediatamente, para no dejar bloqueada la pantalla.

instruccion1(function(){
  instruccion2();
});

El código anterior ejecuta la instruccion1, encola la instruccion2 para que se ejecute cuando pueda, y devuelve el control a la línea siguiente.

Este tipo de llamadas se llaman callbacks y se ejecutan cuando sea, pero la instruccion1 ya ha devuelto el control. Introduce la asincronía, y es la forma clásica en las versiones anteriores de JavaScript.

Pero tiene un problema bastante engorroso de lidiar con él, y es que los callbacks se pueden anidar con un nivel de complejidad que es el llamado Infierno de Callbacks.

getData(url, function (error, data) {
  if (error) {
    console.log('Error: ' + error);
  } else {
    return fetchData(data, function(erro,dat){
      if(erro) {
        console.log('Error: ' + erro);
      } else {
        return getFetchData(dat, function(err,da){
          if (err) {
            console.log(err);
          } else {
            return getFetchGetDta(da, function(er,d){
              if (er) {
                console.log(er);
              } else {
                return d;
              }
            });
          }
        });
      }
    });
  }
});

Una promesa es una nueva forma de implementar esta asincronía pero sin usar callbacks, lo que hace que se lea mucho más fácil.

getData(url)
  .then(fetchData)
  .then(getFethcData)
  .then(getFetchGetData)
  .catch(function(error){
    console.log('Error: ' + error);
  });

El ciclo de vida de una promesa es muy sencillo: cuando se crea se queda en estado pending esperando a que se resuelva o se rechace.

function getData(url){
  return new Promise(function(resolve, reject){
    ...
    if (data) {
      resolve(data);
    } else {
      reject(error);
    }

  });
}

Y podríamos invocar así:

getData(url)
  .then(instruccion1)
  .catch(function(error){
    console.log('Error: ' + error);
  });

Una promesa tiene 3 posibles estados: pendiente, resuelta y rechazada. Cuando la invocamos queda en estado pendiente, y se inicia la asincronía, y cuando se obtiene un resultado, se invoca con éste a resolve() o si se produce un error a reject(). El caso es que desde fuera no podemos consultar el estado de una promesa, pero podemos saber cuando cambia su estado mediante el then().

new Promise(function(resolve, reject) {
    console.log("Promesa pendiente");
    resolve();
}).then(function() {
    console.log("Promesa resuelta");
});

console.log("Hola mundo!");

Y el resultado de ejecutar el código anterior es:


Lo que ha pasado es lo siguiente: lo primero que se ha lanzado es la función que se le pasa a la promesa, y pinta “Promesa pendiente”. Luego se devuelve el control a la línea de ejecución y pasa a la siguiente instrucción y escribe en la consola “Hola Mundo!”, y acto seguido se resuelve la promesa, en nuestro caso cuando invocamos a resolve(), y entonces se lanza lo que hay en el then() y escribe “Promesa resuelta” en la consola.

El argumento que se le pasa a la promesa se llama función ejecutora. Cualquier error que se produzca en un bloque try … catch se resuelve la promesa lanzando el método reject().

let miPromesa = new Promise(function(resolve, reject) {
  throw new Error("Se rechaza sin llamar al siguiente resolve");
  resolve();
});

Cuando se rechaza una promesa se puede ejecutar de dos formas: como la segunda función de un then() o como un catch(). El siguiente código sería equivalente:

miPromesa.then(function() {
  console.log("Promesa resuelta");
}, function(error){
  console.log("Error: " + error.message);
});

miPromesa.then(function() {
  console.log("Promesa resuelta");
}).catch(function(error){
  console.log("Error: " + error.message);
});

Y escribiría por consola: “Error: Se rechaza sin llamar al siguiente resolve”.

Se pueden tener varias promesas, y querer que se lance algo cuando se cumplan todas o que se rechace con que una falle.

Promise.all([miPromesa1, miPromesa2]).then(function() {
  console.log("Todas mis promesas resueltas");
}).catch(function(error){
  console.log("Error: " + error.message);
});

7.1.1. Async y await

Esta funcionalidad aún no está disponible en ES6, aunque muchos transpiladores ya permiten que la usemos. Se espera que esté en la próxima liberación del estándar ES7, o ES2017.

Básicamente es lo mismo que las promesas, para introducir asincronía, pero con una sintaxis ligeramente distinta y a mi parecer más sencilla. Comienza con preceder una función de la palabra async para indicar que en esa función hay alguna parte que es asíncrona. Y luego, en la parte que debe esperar a resolver una promesa, se precede de la palabra reservada await indicando que debe esperar hasta que ésta queda resuelta.

async function(url){
  let miElemento = document.getElementById("texto");
  let texto = await getData(url);
  miElemento.innerHTML = texto;
}

7.2. Proxies e Interceptores

Esta nueva versión de JavaScript nos aporta una funcionalidad existente en otros lenguajes y que se echaba en falta y es la posibilidad de definir Proxies e Interceptores sobre objetos. Como no se trata de azúcar sintáctico, sino que es algo totalmente nuevo de ES6, es una característica que no puede ser emulada por un polyfill, ni transpilada a ES5.

En esencia un Proxy es un objeto que recubre a otro ya existente, y que permite interceptar algunas operaciones sobre él. Veámoslo con un ejemplo:

let cuadrado = {
  lado: 4,
  area: function() {
      return this.lado * this.lado;
  }
}

let cuadradoHandlers = {
  get: function(target,prop){
    console.log("Se consulta el valor de la propiedad: " + prop);   
  }
}

let pCuadrado = new Proxy(cuadrado,cuadradoHandlers);

¿Qué pasa cuando consultamos el valor del lado del nuevo objeto?


¿Y cómo es que no ha devuelto 4? Sólo ha interceptado el get, y no hemos devuelto la respuesta de lo que vale la propiedad cuadrado.lado

let cuadradoHandlers = {
  get: function(target,prop){
    console.log("Se consulta el valor de la propiedad: " + prop);
    return target[prop];
  }
}

Ahora sí. Si repetimos la prueba obtendremos el valor del lado. ¿Y si consultamos el valor del área, que no es una propiedad sino un método?


Cuando consultamos el área nos retorna el valor correcto, sin embargo vemos que se han escrito por consola tres consultas a propiedades: una para el área, y como para calcular el área hay que hacer lado * lado, se accede dos veces más a la propiedad lado. De ahí que salga tres veces.

Aquí estamos usando el interceptor de forma inocente para mostrar un mensaje por consola, pero se puede utilizar para alterar el resultado de métodos y operaciones, para formatear campos numéricos, fechas, etc… o incluso para internacionalizar mensajes de error.

A todos los efectos es como si fuese el mismo objeto, que cuando accedemos a través de su versión proxy intercepta la consulta de sus propiedades. Pero centrémonos en esa afirmación: “es como si fuese el mismo objeto”. No es exactamente cierta, pero se parece mucho. Si cambiamos la propiedad en el objeto proxy, cambia en el objeto al que se refiere, y viceversa.

pCuadrado.lado = 27;
console.log(cuadrado.lado);  //27

cuadrado.lado = 3;
console.log(pCuadrado.lado);  //3

Se puede utilizar para hacer validaciones al momento de asignar un valor a una propiedad. En el ejemplo que nos ocupa, se podría validar que el lado fuera siempre un número entero positivo:

let cuadradoHandlers = {
  set: function(target, prop, value){
    if (prop == "lado"){
      if (!Number.isInteger(value) || value<0) {
        throw new TypeError("El lado debe ser un número positivo");
      }
      target["lado"] = value;
    }
  }
}

Hay muchos métodos de los objetos que se pueden interceptar. La documentación del objeto Proxy y sus handler es muy exhaustiva.

7.3. Strings y Literales

Otra de las novedades que se echaban en falta era poder definir cadenas que ocuparan varias líneas. Los templates strings vienen a solventar ese problema sin necesidad de reemplazar /n por <br> ni similares. Sólo hay que encerrar el texto entre comillas invertidas.

let saludo = `Hola a todos.

Os deseo un buen fin de semana.

Y este texto está escrito
en varias líneas`;

console.log(saludo);

También sirven para interpolar resultados:

let precio = 2950;
const IVA = 1.21

let cadena = `El precio es ${precio*IVA} euros`;
console.log(cadena);

Aquí se mostrará por consola El precio es 3569.5 euros. Hay que tener en cuenta, que estas expresiones se evalúan sólo cuando se definen, no cuando se consultan.

precio = 10;
console.log(cadena);

Si asignamos a precio 10 y volvemos a mostrar por consola la cadena nos seguirá mostrando el mismo mensaje anterior de El precio es 3569.5 euros. No cambia aunque ahora el precio sea 10.

Si queremos hacer que se evalúen cada vez se pueden usar las plantillas de texto con postprocesado (tagged template literals), que no son otra cosa que definir una función con la plantilla que se quiere que se devuelva, y por otro llamar a esa función pasándole los parámetros con los que debe interpretarse. En el ejemplo que tenemos entre manos:

let precio = 2950;
const IVA = 1.21


function cadenaPrecio(cadenas, ...values){
  return `El precio es ${values[0]} euros`;
}

console.log(cadenaPrecio `${precio*IVA}`);  //El precio es 3569.5 euros

precio = 10;
console.log(cadenaPrecio `${precio*IVA}`);  //El precio es 12.1 euros

Con los template literals los caracteres como retornos de carro /n y tabuladores /t se convierten en retornos de carro y tabuladores de verdad.


Pero a veces, nos interesa evaluar una interpolación, pero preservar estos caracteres. Existe un nuevo método dentro del objeto String que nos permite preservar el texto tal y como se escribió, pero evaluando las interpolaciones: String.raw.

String.raw`Gracias por su visita.\n\tEl precio es:\n\t${precio*IVA} euros`;

Y por consola saldrá Gracias por su visita.\n\tEl precio es:\n\t3569.5 euros.

7.3.1. Nuevos métodos de String

Se añaden nuevos métodos, que si bien se podían hacer antes con el indexof de ES5, facilitan la vida al programador:

  • startsWith: comprueba si una cadena comienza por otra
  • endsWith: comprueba si una cadena finaliza por otra
  • Includes: comprueba si una cadena está incluida en otra
  • repeat: repite una cadena n veces
'abcdef'.startsWith('a');    //true
'abcdef'.startsWith('abc');  //true
'abcdef'.startsWith('bc');   //false
'abcdef'.includes('bc');     //true
'abcdef'.endsWith('bc');     //false
'abcdef'.endsWith('ef');     //true
'abc'.repeat(3)              //abcabcabc

7.4. Novedades en los objetos Math y Number

La primera novedad que incluye ES6 es la posibilidad de definir enteros por su forma octal o binaria. Hasta ahora se podía hacer en hexadecimal, usando el prefijo 0x para indicar que lo que seguía era una representación hexadecimal. Ahora se puede usar el prefijo 0b para indicar que lo que sigue es la representación binaria de un número y 0o para indicar que lo que sigue es la representación octal de un número.

let a = 0xFF;        //255 en hexadecimal
let b = 0o377;       //255 en octal
let c = 0b11111111;  //255 en binario

console.log(a === b);      //true
console.log(a === c);      //true

Se introducen muchos métodos en Number, la mayoría ya viejos conocidos. Funciones existentes, que ahora se engloban bajo el objeto Number para darle una perspectiva más propia de un paradigma orientado a objetos. Así la función isNaN() se puede usar como Number.isNaN(), aunque tienen algún matiz. Lo mismo con isFinite(), que ahora se puede usar como Number.isFinite().

Se introduce Number.isInteger(value) y Number.isSafeInteger(value).

Pero para mí, una de las novedades introducidas más importantes, es Number.EPSILON. Una constante, que nos sirve para comparar con el error de imprecisión por la operativa de coma flotante. La descripción exacta de este valor es: la distancia mínima entre 1 y el siguiente número representado en como flotante, que es 2,22e-16. Una unidad muy pequeña. No confundir con el número e.

Y de alguna forma nos viene a recordar el problema de comparar números en JavaScript. Ya vimos que 0.1 + 0.2 no es 0.3 y sin embargo 0.25 + 0.125 sí es 0.375. Vamos a profundizar en el problema.

console.log(0.1 + 0.2 == 0.3);       //false 
console.log(0.25 + 0.125 == 0.375);  //true

La representación de 0.1 y 0.2 en coma flotante es eso, una representación. Una aproximación al número real más cercano que se puede representar. En este caso los decimales más cercanos con representación son:


Vemos que la distancia entre la representación del resultado de sumar 0.1 + 0.2 y la representación de 0.3 es del orden de 5.55 x 1017

Sin embargo, ¿por qué no pasa con 0.25 y 0.125? Pues porque tienen representación binaria exacta al poderse representar su denominador como potencias de 2. Uno es 22 y el otro 23

Eso quiere decir que siempre que hagamos aritmética en coma flotante, ya sea en JavaScript, como en cualquier otro lenguaje que use este estándar para representar números, debemos tener en cuenta la precisión.

7.4.1 La clase Math

Incluye numerosas constantes y funciones matemáticas entre las que en esta versión se han añadido todas las razones trigonométricas hiperbólicas:

Math.cosh(1)     //coseno hiperbolico
Math.sinh(1)     //seno hiperbólico
Math.tanh(1)     //tangente hiperbólica
Math.acosh(1.5430806348152437)    //arco coseno hiperbólico
Math.asinh(1.1752011936438014)    //arco seno hiperbólico
Math.atanh(0.7615941559557649)    //arco tangente hiperbólica

Nuevas funciones para manejar logaritmos que no estén en base e:

  • Math.log10(): logaritmo en base 10
  • Math.log2(): logaritmo en base 2

Mención aparte merecen

  • Math.log1p(x) es el logaritmo neperiano de x+1. Se suele usar para valores muy pequeños que tienen problemas de precisión.
  • Math.expm1() es ex -1

Y otras funciones son:

  • Math.hypot(x,y): es la hipotenusa. Es decir la raiz cuadrada de la suma del cuadrado de sus catetos.
Math.hypot(3,4) // el resultado es 5

ya que 32 + 42 = 52

Pero además, es el módulo del vector libre (3,4). De hecho, podemos usar esta función para obtener el módulo de un vector de n-dimensiones, ya que admite n argumentos.

Math.hypot(a1, a2 ,..., an);

El resultado es la raíz cuadrada de (a12 + a22 + … + an2)

  • Math.trunc(x): devuelve la parte entera del número x
  • Math.sign(x): devuelve si un número es positivo (1), negativo (-1) o cero (0).
  • Math.imul(x,y): devuelve el resultado de una multiplicación de 32 bits
  • Math.fround(x): devuelve la representación en coma flotante más cercana del número x
  • Math.cbrt(x): devuelve la raíz cúbica de x
  • Math.clz32(x): devuelve el número de ceros por la izquierda en la representación binaria del número x con 32 bits. Aún no se me ocurre en qué casos puede ser útil este método.

8. Epílogo

Durante muchos años, JavaScript se ha mantenido estable y sin evolucionar, pero el redescubrimiento de sus posibilidades por parte de los desarrolladores ha empujado una evolución, que lo ha rejuvenecido ampliando sus posibilidades. Sin duda es un lenguaje que está vivo y que a día de hoy es cambiante.

Recomiendo emplear el estándar, que para eso está. Y antes de profundizar en otros frameworks que se apoyan en ES6, conocer de primera mano la tecnología que subyace a estos frameworks.

Muchos desarrolladores se refieren despectivamente a desarrollar con JavaScript a pelo como “Vainilla JS” en referencia a la carestía del lenguaje. Siento discrepar, y no puedo evitar confesar que amo profundamente este lenguaje, y su versatilidad con el tipado dinámico. Mientras pueda, defenderé el uso del estándar frente a frameworks que se pueden quedar obsoletos o desmantenidos en cualquier momento.

9. Referencias


Vinculación de iPhone a Xcode y aplicación “Hola Mundo” en Swift 4.1

$
0
0

Índice de contenidos

1. Introducción

Con este tutorial vamos a aprender a vincular el iPhone mediante el ID de la cuenta de Apple creada en el tutorial anterior “Configuración del entorno de Xcode” al entorno de desarrollo Xcode, y posteriormente a crear una aplicación básica la cual muestra el mensaje de “Hola Mundo” por pantalla. Finalmente, instalaremos la aplicación en el iPhone.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware:
    • Portátil MacBook Pro 15’ (2.3 GHz Intel Core i7, 16GB DDR3)
    • iPhone 6S 4.7’ (Chip A9, 2GB RAM)
  • Sistema Operativo:
    • Mac OS High Sierra 10.13.4
    • OS 11.3
  • Entorno de desarrollo: Xcode 9.4 beta

3. Configuración del ID de Apple en el entorno de desarrollo

Lo primero que haremos es crear un nuevo proyecto. Para ello elegiremos una aplicación de tipo “Single View App” y pulsamos en “Next”.

Lo siguiente que tenemos que hacer es especificar los datos de la aplicación y pulsamos en “Next”.

Después de elegir la ubicación donde se va a crear el proyecto, nos aparecerá la siguiente pantalla, donde debemos elegir como “Device” la opción de iPhone. Como podemos observar el “Status” indica que necesita una identificación de una cuenta de Apple.

Para solventar este problema, debemos dirigirnos a Xcode -> Preferences

Para añadir nuestra ID de Apple al entorno, desde la pestaña de “Accounts” pulsamos sobre el icono “+” situado en la parte inferior izquierda de la ventana.

Elegimos la opción de “Apple ID”, a continuación introducimos nuestra ID de Apple y pulsamos en “Next”.

El resultado debe ser de la siguiente forma:

Cuando tengamos listo esto, nos dirigimos a la ventana del proyecto, y pulsando sobre el proyecto en la parte izquierda, lo que nos llevará nuevamente al fichero de configuración del proyecto, donde elegiremos en la sección “Signing”, el Team que hemos creado en le paso anterior.

4. Creación del “Hola Mundo”

Si nos fijamos en la parte izquierda del programa, observamos la estructura del proyecto. Los ficheros que utilizaremos serán “ViewController.swift” y “Main.storyboard”.

Si pulsamos sobre el fichero “Main.storyboard”, y pasamos a la vista del Editor Asistente, veremos una vista preliminar de la aplicación y el código asociado del fichero “ViewController.swift”.

Para agregar componentes al storyboard de la aplicación, tendremos que elegir y arrastrar el componente a la vista de la aplicación. Los componentes se encuentran en la parte inferior derecha del programa.

Para poder tener una referencia a los componentes de la vista en la clase “ViewController.swift”, tendremos que seleccionar el componente deseado, y arrastrarlo al código manteniendo el botón de CTRL pulsado. Al soltarlo en la parte del código que más convenga, saldrá una ventana donde debemos de asignar el nombre al componente.

El código para darle el valor al texto del componente obtenido, es el siguiente:

ViewController.swift
class ViewController: UIViewController {
	    @IBOutlet weak var lMensaje: UILabel!

	    override func viewDidLoad() {
	        super.viewDidLoad()

	        lMensaje.text = "Hola Mundo"
	    }

	    override func didReceiveMemoryWarning() {
	        super.didReceiveMemoryWarning()
	        // Dispose of any resources that can be recreated.
	    }
	}

5. Instalación y ejecución de la aplicación en un dispositivo iPhone

Para poder instalar la aplicación en el dispositivo móvil, hemos de conectarlo al equipo y elegir en la parte superior del programa el dispositivo detectado por el entorno. Por último, pulsaremos sobre el botón de compilar.

Es necesario tener el dispositivo desbloqueado en el momento de pulsar en el botón de compilado. Además tendremos que dirigirnos a Ajustes -> General -> Gestión de dispositivos -> -> y activar “Confiar en ”, ya que al intentar ejecutar la aplicación, no nos lo permitirá ya que es de origen desconocido.

7. Referencias

Primeros pasos con Nx

$
0
0

Índice de contenidos

1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Slimbook Pro 13.3″ (Intel Core i7, 32GB RAM)
  • Sistema Operativo: LUbuntu 16.04
  • Visual Studio Code 1.16.1
  • @angular/cli 1.7.1
  • @angular 5.2.7
  • @nrwl/nx

2. Introducción

A poco que te hayas pegado con aplicaciones Angular un poco más complejas te habrás preguntado cuál será la mejor forma de organizar todo tu código y poder hacerlo reutilizable sin morir en el intento.

Una forma de estructurar adecuadamente una aplicación con Angular es dividir la aplicación en distintos módulos por funcionalidad que sean fácilmente “enganchables” con el módulo principal de forma lazy, para no aumentar el tamaño general del bundle.

Cuando hacemos esta división nos podemos dar cuenta que existen elementos que se repiten en los distintos módulos. Es algo muy común establecer un módulo “shared” que almacene todos estos elementos transversales como: pipes, directivas, componentes, servicios, modelo, validadores y guardas del router.

Además podemos darnos cuenta de que algunos de estos elementos nos conviene extraerlos a una librería para que puedan ser importados en otros proyectos de Angular.

Esto requiere de mucha disciplina por parte de los desarrolladores, y sobre todo, de los arquitectos y es fácil encontrarse con la excusa de que estas refactorizaciones llevan un tiempo que muchas veces no se tiene.

Por suerte este tipo de problemas, sobre todo en grandes compañias, es tan común que muchos ex-miembros del core de Angular se han unido en una empresa llamada Nrwl, fundada por Victor Savkin (más conocido por ser el padre del router de Angular y que no hubiera versión 3 del framework); juntos han creado la tecnología Nx que se integra con el CLI de Angular para añadir nuevos comandos (gracias a schematics) que facilitan la organización y el desarrollo de proyectos complejos en un único espacio de trabajo.

Una de las principales características de esta tecnología es que facilita el uso del monorepo, es decir, que todas nuestras aplicaciones y librerías relacionadas con Angular estén en un único repositorio de Git, al estilo de Google, Facebook, o Uber. Las principales ventajas que encontramos son:

  • No enmascara ninguna pieza de código, todo lo que hay se ve en el mismo repositorio, así es más fácil darse cuenta de qué piezas ya están implementadas evitando las re-implementaciones.
  • Permite ejecutar los tests en todo el repositorio, lo que hace que fallen si hay cualquier cambio en alguna librería compartida que afecta a otra, evitando el exceso de código por programación defensiva.
  • Crear una nueva pieza (aplicación/librería) solo supone crear una nueva carpeta en el proyecto, lo que favorece que los desarrolladores queramos modulizar más nuestro código, al no tener que esperar al típico trámite burocrático de crear un nuevo repositorio en la organización y hacer la configuración inicial, lo que puede llevar días, si estamos en el mismo repo, lo podemos hacer en minutos.
  • Las refactorizaciones de código que implican cambios en aplicaciones y librerías también se hacen de forma más sencilla y efectiva al no tener que cambiar el contexto y poder ejecutar los tests de forma conjunta.
  • Solo tenemos una única versión para todos los proyectos por lo que las migraciones a versiones superiores del framework se hacen de manera más eficiente; como Angular saca versión cada 6 meses y deja compatibilidad con la versión anterior, tenemos un año para modificar nuestro código si es que existe algún breaking change.

3. Empezando con Nx

Nota: Toda la información se encuentra en su documentación oficial

Lo primero que necesitamos es instalar de forma global la siguiente dependencia que nos va a permitir poder ejecutar los comandos de nx desde el CLI de Angular:

$> npm install -g @nrwl/schematics

Una vez termine el proceso de instalación tendremos a nuestra disposición el siguiente comando para la creación del workspace:

$> create-nx-workspace nombre-workspace [--directory=nombre-directory] [--npm-scope=nombre-scope-sin-@]

Este comando tiene dos parámetros que son opcionales:

  • directory: para definir un nombre de carpeta distinto al del nombre de workspace.
  • npmScope: para definir un scope de npm distinto al nombre del workspace, del tipo @nombreScope, es importante definirlo sin la @, ya que al tratarse de un scope de npm ya se lo pondrá internamente.

Dentro de este espacio de trabajo recién creado se encuentran dos directorios: uno “apps” que almacena el módulo principal de cada una de las aplicaciones de Angular y otro “libs” que almacena los módulos secundarios y librerías compartidas.

Creación de una nueva aplicación

Para crear una nueva aplicación hacemos uso del comando generate de Angular pasándole el nombre de la aplicación.

$> npm run ng -- generate app nombre-app

Este comando genera la aplicación con el nombre especificado dentro de la carpeta “apps” y modifica automáticamente el fichero .angular-cli.json para registrar la nueva aplicación.

En caso de querer añadir automáticamente el módulo de routing tendremos que añadir el siguiente flag:

$> npm run ng -- generate app nombre-app --routing

Nota:Si entramos dentro del directorio podemos ver que la estructura de esta aplicación no difiere en nada de un proyecto creado con Angular CLI.

Estas aplicaciones serán las encargadas de generar el esqueleto visual de la aplicación, con el módulo principal, la gestión del router para la carga lazy del resto de módulos y el aspecto estético de la aplicación.

Es importante para el rendimiento general que esta aplicación solo contenga lo imprescindible; el grueso de nuestro código tiene que recaer en las librerías.

Creación de una librería como módulo secundario

Para la creación de una librería como módulo secundario tenemos que hacer uso del siguiente comando:

$> npm run ng -- generate lib nombre-module-lib

o si queremos añadir la gestión del router:

$> npm run ng -- generate lib nombre-module-lib --routing

y si queremos que se configure asociado al router de una aplicación y se cargue de forma lazy, tenemos que ejecutarlo con el flag “lazy” e indicando la ruta del módulo principal asociado:

$> npm run ng -- generate lib nombre-module-lib --routing --lazy --parentModule=apps/nombre-app/src/nombre-app.module.ts

Este tipo de librerías van a contener la lógica de las distintas funcionalidades sin preocuparse por el aspecto estético.

Creación de una librería

Para la creación de una librería hacemos uso del flag –nomodule:

$> npm run ng -- generate lib nombre-lib --nomodule

Este comando crea una simple librería sin módulo asociado, la cual se va a encargar de implementar todas aquellas funcionalidades transversales al resto de módulos: como el sistema de notificación de mensajes, el logging, acceso a datos externos, etc… serán susceptibles de extraerse como librerías independientes para su uso en otros proyectos con Angular.

Arranque/distribución de la aplicación

Para poder arrancar o distribuir la aplicación tenemos que especificar con el flag “app” el nombre de la aplicación:

$> npm run start -- --app=nombre-app
$> npm run build -- --app=nombre-app

Cosas a tener en cuenta cuando trabajamos con un workspace de Nx

En este punto donde puede ser que varias librerías y aplicaciones creadas en nuestro workspace, tenemos que tener en cuenta algunos aspectos:

  • Todos los ficheros se referencian con el nombre del workspace. Es decir, cuando vayamos a importar un elemento de una librería dentro de una aplicación debemos hacerlo como @nombre-workspace/path-al-elemento
  • Es importante que a medida que vayamos incluyendo elementos en los proyectos nos acordemos de actualizar el index.ts asociado, a fin de que las importaciones automáticas se realicen de forma correcta.
  • Cuando ejecutamos el comando “npm run test” lo estamos haciendo de todos los tests del workspace, no solo del proyecto en el que estemos trabajando, si queremos ejecutar solo los de nuestro proyecto, tendremos que utilizar fdescribe o bien cambiar la ruta del fichero test.ts especificando solo la ruta a nuestro proyecto.

Aquí tienes un ejemplo de workspace con Nx

Actualizar el workspace a la última versión de Angular

Como ya hemos mencionado una de las grandes ventajas de trabajar con un único repositorio es que las actualizaciones se pueden efectuar de forma más rápida, pero si además Nx lo hace por nosotros pues mejor que mejor. Para ello tenemos que seguir los siguientes pasos:

Instalar la última versión de estas dependencias de forma local al proyecto:

$> npm install --save-dev @nrwl/nx@latest @nrwl/schematics@latest --save-exact

Luego ejecutar el comando de npm habilitado por el workspace:

$> npm run update

En caso de no tenerlo (por tener una versión más antigua de nx) debemos ejecutar:

$> ./node_modules/.bin/nx update

Esto va a recorrer todo nuestro workspace realizando todas las actualizaciones necesarias de forma automática. Incluso nos actualiza el fichero package.json con las tareas de npm que nos falten.

Terminado el proceso instalamos todas las dependencias por si hubiera necesitado introducir alguna nueva:

$> npm install

Visualizar en un gráfico las dependencias entre aplicaciones y librerías

Otra de las cosas que nos ofrece Nx es una manera muy visual de ver las dependencias que tenemos dentro de nuestro workspace.

Para probarlo puedes hacer clone de mi proyecto:

$> git clone https://github.com/raguilera82/nx-radh.git

Nota:No olvides hacer npm install para instalar todas las dependencias necesarias.

Para obtener el gráfico simplemente tenemos que ejecutar el script de npm asociado:

$> npm run dep-graph

Y a día de hoy podemos ver algo parecido a esto:

Los puntos suspensivos indican que la librería se está cargando en modo lazy.

4. Conclusiones

En este tutorial solo hemos visto la punta del Iceberg de lo que ofrece la tecnología Nx que nos permite integrar proyectos de Ionic como aplicaciones dentro de nuestro workspace, hacer uso de ngrx como una librería compartida, entre otras cosas, que iremos explorando en próximos tutoriales.

Recordad que esta técnica y otras muchas más las encontraréis en la guía práctica de Angular y también ofrecemos cursos in-house y online.

Cualquier duda o sugerencia en la zona de comentarios.

Saludos.

Mi primera experiencia en Agile Asturias con un workshop para Product Owners de David Fernández

$
0
0

En Semana Santa me estrené en un Meetup… de Asturias, ¡que ya tocaba pisar uno en la tierrina! Así que avisé al majérrimo Toño de la Torre y fuimos juntos al taller de Agile Asturias de Herramientas para Product Owners que por David Fernández ofrecía en Gijón.

A decir verdad tampoco es que fuésemos a la aventura, David Fernández ya cuenta con una amplia experiencia refrendada en su trayectoria como ponente en las conferencias más importantes del país con las nuevas metodologías ágiles de trabajo como tema protagonista.

Este taller de dos horas de duración estaba pensado para ayudar a los Product Owners a conceptualizar sus productos teniendo en cuenta tanto las peticiones de cliente así como las capacidades del equipo que ha de desarrollarlo. Para ello, David utilizó diversas herramientas como Lean Canvas, Elevator Pitch, Risks’ map, Stakeholders’ map, Tradeoffs y finalmente culminó su sesión con un Roadmap del producto. Tras una breve presentación en la que definió temas básicos para el correcto seguimiento del taller, David estructuró a los 25 asistentes en cuatro grupos de trabajo conjunto.

En mi grupo propuse el desarrollo de un servicio de formación que no estaba basado en un SaaS, lo cual trastocaba a ratos los planes de algunos managers o desarrolladores que componían junto a mí el equipo. La forma en la que el instructor había planteado el taller implicaba que conversásemos y nos pusiésemos de acuerdo en cuanto a qué priorizar de la pila de producto teniendo en cuenta las peticiones de unos y restricciones de otros. Una vez llegado a un acuerdo plasmaríamos en post-its nuestras ideas y tras pasar por siete paneles conseguíamos un producto 100% definido. Un par de horitas muy bien aprovechadas en las que afianzar o aprender conceptos de diseño y gestión de producto.

Como siempre digo en este tipo de eventos, el aprendizaje adquirido en el taller de David Fernández sólo representó la mitad de lo que supone asistir a un Meetup, la otra mitad de estas reuniones consiste en las conversaciones que se producen una vez termina la parte más técnica, eso que llaman networking. Es en esos ratos en los que se conocen a otros profesionales del sector que quizá compartan problemas, herramientas técnicas o incluso peticiones análogas de clientes y que suponen lo que para mí son los rasgos que hacen de la Comunidad tecnológica la más puntera de España; el apoyo, la compartición y aprendizaje constante de conocimientos. La independencia que confiere el acceso libre a recursos y consejos que comparte la Comunidad de manera altruista.

Las últimas palabras de este texto quiero dedicarlas a las personas que invierten muchas horas de forma desinteresada en trabajar en provincias para formar Comunidades, donde todo sucede a una velocidad distinta de ciudades como Madrid o Barcelona. Toño, María, Aurora, David… gracias. Vuestra labor es muy importante, ¡seguid así!

Aplicación con Swift 4.1 que identifica las zonas mediante iBeacons

$
0
0

Índice de contenidos

1. Introducción

En este tutorial vamos a aprender a crear una aplicación capaz de detectar las diferentes zonas preestablecidas mediante las balizas iBeacons. Se trata del Kit Kontakt iBeacon de Apple, compuesto por 5 balizas que emiten una señal bluetooth de baja energía. Cada uno tiene 3 parámetros importántes: UUID , Major y Minor.

  • UUID: Identificador único universal de un iBeacon
  • Major: Identificador de un subgrupo de iBeacons
  • Minor: Identificador específico de un iBeacon

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware:
    • Portátil MacBook Pro 15’ (2.3 GHz Intel Core i7, 16GB DDR3)
    • iPhone 6S 4.7’ (Chip A9, 2GB RAM)
  • Sistema Operativo:
    • Mac OS High Sierra 10.13.4
    • OS 11.3
  • Entorno de desarrollo: Xcode 9.4 beta

3. Petición de permisos de localización

Es importante informar y requerir el permiso de localización al usuario, y para ello debemos añadir dos parámetros más en la lista de “Info.plist”.

  • Privacy – Location Always and When In Use Usage Description
  • Privacy – Location When In Use Usage Description

4. Creación de interfaz

En el fichero de “Main.storyboard”, agregamos 3 “Image View” y nombramos cada una con una letra.

Debería de tener este aspecto:

Debemos importar la librería CoreLocation y además implementar el protocolo CLLocationManagerDelegate en la clase “ViewController.swift”. Vamos a declarar un locationManager para utilizarlo poseteriormente en la petición de permisos y el escaneo de iBeacons. El código quedaría así:

ViewController.swift
import UIKit
	import CoreLocation

	class ViewController: UIViewController, CLLocationManagerDelegate {

	    var locationManager:CLLocationManager = CLLocationManager()

	    //Referencia a los componentes de la interfaz:
	    @IBOutlet weak var iZonaA: UIImageView!
	    @IBOutlet weak var iZonaB: UIImageView!
	    @IBOutlet weak var iZonaC: UIImageView!
	    @IBOutlet weak var numiBeacons: UILabel!

			//Declaración de variables que recogen la distancia de la baliza al dispositivo móvil
			var disA:Double = 0
			var disB:Double = 0
			var disC:Double = 0


	    override func viewDidLoad() {
	        super.viewDidLoad()

	        locationManager.delegate = self

	        //Pedimos el permiso de localización
	        locationManager.requestAlwaysAuthorization()

	    }

5. Creación de una región en base a los datos de los iBeacons

Creamos un método encargado de asignar los valores específicos de nuestro kit de iBeacons. Para saber estos datos hay que entrar al Panel Kontak en el enlace: https://panel.kontakt.io/signin en el cual, anteriormente, se haya registrado el producto.

En este ejemplo, únicamente necesitaremos el UUID del kit, y el valor de Major de cada iBeacon. Finalmente comenzará el escaneo en la región definida.

func escaneariBeacons(){

        let uuid = UUID(uuidString: "f7826da6-4fa2-4e98-8024-bc5b71e0893e")!

        let identifier = "com.autentia" //Identificador personalizado

        let region = CLBeaconRegion(proximityUUID: uuid, identifier: identifier)

        locationManager.startRangingBeacons(in: region)

    }

6. Lanzamiento del escaneo de iBeacons

Lo siguiente, es implementar el método locationManager, para verificar si el usuario ha concedido los permisos de localización y llamar al método anterior.

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {

        if status == .authorizedAlways{
            escaneariBeacons()
        }
    }

6. Identificación de zonas

func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {

        guard let discoveredBeaconProximity = beacons.first?.proximity else { numiBeacons.text = ("No hay ibeacons cerca"); return }

        if(beacons.count > 0){  // Si hay iBeacons en el perímetro
            numiBeacons.text = "\(beacons.count)"
            for b in beacons{
                if(b.major == 43614){ // iBeacon de la zona A
                    disA = b.accuracy
                }else if(b.major == 15288){ // iBeacon de la zona B
                    disB = b.accuracy
                }else if(b.major == 40577){ // iBeacon de la zona C
                    disC = b.accuracy
                }
            }

            // Si el más cercano es el de la zona A
            if(disA < disB && disA < disC && disA != -1.0 && disB != -1.0 && disC != -1.0){
                iZonaA.backgroundColor = UIColor.green
                iZonaB.backgroundColor = UIColor.red
                iZonaC.backgroundColor = UIColor.red

                // Si el más cercano es el de la zona de B
            }else if(disB < disA && disB < disC && disA != -1.0 && disB != -1.0 && disC != -1.0){
                iZonaB.backgroundColor = UIColor.green
                iZonaA.backgroundColor = UIColor.red
                iZonaC.backgroundColor = UIColor.red

                // Si el más cercano es el de la zona de C
            }else if(disC < disB && disC < disA && disA != -1.0 && disB != -1.0 && disC != -1.0){
                iZonaC.backgroundColor = UIColor.green
                iZonaB.backgroundColor = UIColor.red
                iZonaA.backgroundColor = UIColor.red
            }
        }else{
            numiBeacons.text = "0"
        }
    }

Por último, queda instalar la aplicación, distribuir los iBeacons y probar la aplicación. El aspecto final:

6. Conclusiones

Si bien es cierto que, la tecnología basada en iBeacons es útil y atractiva, ya que requiere de pocas herramientas para su funcionamiento, hay que destacar que la precisión en el cálculo de la distancia entre el teléfono y las balizas deja mucho que desear. Por lo tanto, no debe estar destinado a un uso que requiera exactitud.

Bot de Telegram con Java

$
0
0

Índice de contenidos

1. Introducción

En este tutorial vamos a crear un bot usando la API de Telegram. Esta API es pública y puede ser usada tras la obtención de un token vinculado con la cuenta de Telegram.

La API puede ser consultada de múltiples formas pero en esta ocasión vamos a usar un wrapper que facilita el desarrollo llamado TelegramBots. En la documentación de la API de Telegram se pueden encontrar más librerías para acceder a la API con distintos lenguajes.

2. Entorno

El tutorial está desarrollado usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3, 500GB Flash Storage)
  • Sistema Operativo: Mac OS Sierra 10.13.4
  • Entorno de desarrollo: IntelliJ IDEA 2018.1
  • JDK 1.8
  • Gradle 4.7
  • TelegramBots 3.6.1

3. Obtención del token del bot

Lo primero que haremos será crear un nuevo bot y obtener su token de autenticación. Para ello nos descargamos la aplicación de Telegram de aquí (Android) o aqui (iOS). Después la abriremos y nos daremos de alta con nuestro número de teléfono.

Posteriormente, hablaremos con el BotFather. Tras enviarle el comando /newbot y seguir sus instrucciones, nos creará las credenciales de nuestro nuevo bot y nos dará el token. Este token es privado y no se debe compartir.

4. Proyecto Java con Gradle

Lo siguiente que haremos será crear un nuevo proyecto gestionado con Gradle (también se puede hacer con Maven) y añadiremos la dependencia del wrapper de la API de Telegram:

dependencies {
	compile group: 'org.telegram', name: 'telegrambots', version: '3.6.1'
}

Esta librería nos permitirá acceder a toda la API de Telegram de forma fácil y sin tener que preocuparnos de usarla directamente.

5. Bot eco

Ahora desarrollaremos el código de nuestro bot. Para ello vamos a hacer que responda con la misma información que le enviemos.

Lo primero será crear la clase que se encargará de gestionar las peticiones del bot. Para ello tenemos que extender la clase

TelegramLongPollingBot
. Esto hará que nuestro bot pregunte periódicamente por nuevas actualizaciones a Telegram. También podemos crear bots de tipo
TelegramWebhookBot
, y será Telegram quien nos avise de nuevas actualizaciones (en este caso usaremos Polling para simplificar).
import org.telegram.telegrambots.api.methods.send.SendMessage;
import org.telegram.telegrambots.api.objects.Update;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.exceptions.TelegramApiException;

public class EchoBot extends TelegramLongPollingBot {

	@Override
	public void onUpdateReceived(final Update update) {
		// Esta función se invocará cuando nuestro bot reciba un mensaje

		// Se obtiene el mensaje escrito por el usuario
		final String messageTextReceived = update.getMessage().getText();

		// Se obtiene el id de chat del usuario
		final long chatId = update.getMessage().getChatId();

		// Se crea un objeto mensaje
		SendMessage message = new SendMessage().setChatId(chatId).setText(messageTextReceived);
		
		try {
			// Se envía el mensaje
			execute(message);
		} catch (TelegramApiException e) {
			e.printStackTrace();
		}
	}

	@Override
	public String getBotUsername() {
		// Se devuelve el nombre que dimos al bot al crearlo con el BotFather
		return "EchoBot";
	}

	@Override
	public String getBotToken() {
		// Se devuelve el token que nos generó el BotFather de nuestro bot
		return "999999:qwertyuiop";
	}
}

En esta clase tenemos la lógica de nuestro bot, ahora solo tenemos que crear otra clase que será el punto de entrada a la aplicación y que registrará el bot.

import org.telegram.telegrambots.ApiContextInitializer;
import org.telegram.telegrambots.TelegramBotsApi;
import org.telegram.telegrambots.exceptions.TelegramApiException;

public class Main {
	public static void main(String[] args) {
		// Se inicializa el contexto de la API
		ApiContextInitializer.init();

		// Se crea un nuevo Bot API
		final TelegramBotsApi telegramBotsApi = new TelegramBotsApi();

		try {
			// Se registra el bot
			telegramBotsApi.registerBot(new EchoBot());
		} catch (TelegramApiException e) {
			e.printStackTrace();
		}
	}
}

6. Probamos el bot

Ahora tan solo tenemos que arrancar nuestro proyecto desde la clase

Main
y probar el resultado. Buscaremos nuestro bot en la aplicación de Telegram, ya sea con @nombreDelBot o desde el BotFather escribiéndole el comando /mybots>.

En la pantalla del bot pulsaremos en el botón Iniciar y ya podremos enviarle mensajes. Nuestro inteligente bot nos responderá con la misma información :).

6. Conclusiones

Este es el ejemplo más sencillo que podemos hacer usando este wrapper pero también podremos desarrollar bots más complejos como imágenes, emojis, ficheros, botones, comandos…Todo lo que la API de Telegram permita. Incluso podremos desarrollar el bot como si de una aplicación Spring Boot se tratara.

Un saludo.

Alejandro

aalmazan@autentia.com

Event Sourcing para aplicaciones escalables

$
0
0

Event Sourcing para aplicaciones escalables (no CQRS).

Índice de contenidos

1. Introducción

En este post se presenta una pequeña introducción sobre el patrón de arquitectura Event Sourcing y se expondrá un ejemplo para ver cómo implementarlo.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2,3 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS High Sierra 10.13.3
  • IntelliJ IDEA 2017.3.5
  • Docker version 18.05.0-ce-rc1, build 33f00ce
  • docker-compose version 1.21.0, build 5920eb0
  • Java version 1.8.0_161

3. ¿Qué es Event Sourcing Pattern?

Event sourcing es un patrón de arquitectura (no una arquitectura en sí misma) que se encarga de capturar todos los cambios que se pueden producir en nuestra aplicación como una secuencia de eventos.
Este patrón de arquitectura no es algo nuevo, de hecho Martin Fowler publicó un post en 2005 hablando sobre cómo modelar los cambios en un sistema a través de eventos. En los últimos años ha tomado mayor relevancia debido principalmente a la necesidad de escalabilidad que se les exigen a aplicaciones modernas y dado que encaja bastante bien con la naturaleza asíncrona que presentan estas aplicaciones.

4. ¿Por qué Event Sourcing (ES)?

Hasta hace unos años era habitual que las aplicaciones presentaran tiempos de respuestas bajos, caídas frecuentes, etc. Hoy en día los sistemas deben dar servicio 24/7 y proporcionar siempre unos buenos tiempos de respuesta; ésto sólo se puede conseguir dejando de lado los diseños tradicionales (como las aplicaciones basadas en ACID). Todo sistema que presente la necesidad de soportar una gran carga de usuarios o procesos debe adoptar características como: responsividad, resiliencia, comunicación asíncrona y escalabilidad. En otras palabras, deben ser sistemas reactivos. Event Sourcing encaja de forma natural en un sistema de estas características:

  • A diferencia de los tradicionales CRUD, cuya escalabilidad está muy limitada debido a que toda operación se debe realizar de forma atómica y consistente (ACID), con Event Sourcing solo se almacenan eventos lo que propicia un modelo óptimo para una arquitectura basada en eventos, lo que conlleva escalabilidad y responsividad.
  • Al tener almacenado cada uno de los eventos que han ocurrido en el sistema se obtiene información de negocio muy valiosa ya que en todo momento se conoce cómo el dominio ha llegado a su estado actual.
  • Auditoria sin esfuerzo al tener en todo momento un tracking del dominio.

5. Ejemplo de implementación

Como ejemplo se modelará un sistema basado en Event Sourcing junto con una arquitectura conducida por eventos (event-driven). Se han creado dos pequeñas aplicaciones que modelan lo que podría ser un módulo de un sistema bancario y que se comunican entre sí en base a eventos. Por razones de simplicidad no se han validado aspectos obvios, como que la persona que hace la retirada de saldo sea la propietaria de la cuenta. Para ello se ha dividido la aplicación en dos funcionalidades de dominio:

  • Un módulo de operaciones, encargado de hacer depósitos o retiradas de saldo de una cuenta.
  • Un módulo de cuentas que se encarga de la tramitación y validación de dichas operaciones.

5.1 Stack

Para el ejemplo se ha usado el siguiente stack:

  • Docker para desplegar cada una de las aplicaciones de forma independiente.
  • Wildfly 11 es el servidor en el cual correrán las aplicaciones dentro de los contenedores Docker.
  • Java EE 8; JAX-RS para los endpoints REST y CDI como contenedor de beans y eventos internos.
  • Kafka para bus de eventos entre las aplicaciones. Podría usarse cualquier otro bus de mensajes como RabbitMQ.

5.2 Flujo

Lo principal es entender el flujo de eventos entre las aplicaciones. Cuando la aplicación arranca, automáticamente se registrarán servicios de escucha en el bus de eventos (kafka listeners), que se encargan de recibir eventos de la cola y comunicar su llegada a cada aplicación.



Para interactuar con la aplicación se han habilitado diferentes endpoints REST. Cada petición generará el siguiente flujo:

  • 1. Persistencia de cada evento. Los eventos ocurren en el pasado y nada puede cambiar que haya ocurrido, por lo que deben ser almacenados (de hecho el nombre de los eventos debe escribirse en pasado). Por ejemplo: ejecutar una acción de retirada de saldo y que posteriormente se detecte que no hay suficiente saldo en la cuenta debe quedar reflejado a través de eventos.
  • 2. Publicación de cada evento al bus de mensajería. El evento persistido debe ser publicado para que el sistema pueda entrar en un estado consistente, evitando todo lo posible la consistencia eventual (más detalles posteriormente).

5.3 Implementación

Como ejemplo se modelará el flujo completo de cómo la aplicación gestionaría un depósito de saldo en una cuenta bancaria.

Creación de una cuenta

curl -i -XPOST -d '{"ownerName":"Sergio","ownerSurnames":"Verde Caballero"}' \ 
-H 'Content-Type:application/json' \ 
http://localhost:8080/bank-account-event-sourcing/resources/accounts

HTTP/1.1 201 Created
Connection: keep-alive
X-Powered-By: Undertow/1
Server: WildFly/11
Location: http://localhost:8080/bank-account-event-sourcing/resources/operations/4ee78bab-b979-4a32-af1a-de92be94bc1b
Content-Length: 0
Date: Sun, 20 May 2018 07:20:01 GMT

Tras este paso previo se añade saldo a la cuenta:

curl -i -XPOST -d '{"account":"1178b586-2faf-4549-bc9c-b5c21d506121","quantity":50.0}' \ 
-H 'Content-Type:application/json' \
http://localhost:8080/bank-account-event-sourcing/resources/operations/deposit

5.3.1 Flujo 1 – Operación de depósito

Este es el código que modela el flujo:

OperationService.java

@Inject
OperationEventStorage eventStorage;

@Inject
EventBus eventBus;

public String deposit(String accountId, Float quantity) {
	DepositOrderPlaced depositOrder = new DepositOrderPlaced(accountId, quantity);
	eventStorage.add(depositOrder);
	eventBus.produce(depositOrder);
	return depositOrder.getId();
}

En este punto se ha creado un evento para ingresar saldo y es publicado a la cola de eventos. La configuración actual del productor de mensajes asegura que éste se ha entregado correctamente a la cola, aunque todavía no se ha procesado la petición.

Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaOrderSerializer.class.getName());
props.put(ProducerConfig.ACKS_CONFIG, "1"); // wait leader broker to get response

Actualmente la aplicación se encuentra en estado de consistencia eventual: se ha realizado un depósito, pero si en ese mismo instante (antes de que se procese el mensaje) se consulta el estado de la cuenta, el saldo será 0. La petición REST retorna la cabecera Location, donde se encuentra la información necesaria para consultar el estado del depósito, que en este momento estará como PLACED.

5.3.2 Flujo 2 – Orden de depósito recibida


Una vez la cola recibe el mensaje la publicará a todos los consumidores, en este caso la aplicación de gestión de cuentas, que se encargará de:

  • 1. Validar el estado de la cuenta.
  • 2. Generar el evento que indica que el depósito se ha realizado correctamente.
  • 3. Comunicarlo de vuelta a la cola.
Este es el código del consumidor Kafka que se encarga de procesar el evento. La comunicación con el servicio se ejecuta a partir de eventos CDI (esto no es necesario, podría comunicarse directamente con su propio servicio):

KafkaConsumer.java
@Inject
Event<AccountKafkaEvent> orderEvent;

@Resource
ManagedExecutorService executorService;

public void consume(@Observes @Initialized(ApplicationScoped.class) Object init) {
	executorService.submit(() -> {

		Properties props = new Properties();
		props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka:9092");
		props.put(ConsumerConfig.GROUP_ID_CONFIG, "operations-consumer");
		props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
		props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaOrderDeserializer.class.getName());
		Consumer<String, AccountKafkaEvent> consumer = new org.apache.kafka.clients.consumer.KafkaConsumer(props);
		consumer.subscribe(Collections.singletonList("test"));

		try {
			while (true) {
				System.out.println("WAITING FOR CONSUMING OPERATIONS...");
				ConsumerRecords<String, AccountKafkaEvent> records = consumer.poll(Long.MAX_VALUE);
				for (ConsumerRecord<String, AccountKafkaEvent> record : records) {
					AccountKafkaEvent operation = record.value();
					orderEvent.fire(operation);
				}
			}
		}finally {
			consumer.close();
		}

	});
}

El servicio se encargará de observar eventos CDI internos recepcionando el evento de depósito (entre otros), haciendo las comprobaciones oportunas, persistiéndolo y comunicando que el depósito se ha realizado de forma correcta o rechazándolo.

AccountService.java

void depositOrder(@Observes DepositOrderPlaced depositOrder) {
	System.out.println("DEPOSIT ORDER PLACED! checking information...");
	String accountId = depositOrder.getAccountId();
	Optional<AccountInfo> info = accountRepository.get(accountId);
	if(info.isPresent()) {
		DepositOrderAccepted externalDepositOrderAccepted = new DepositOrderAccepted(depositOrder.getId());
		com.sergio.model.events.internal.DepositOrderAccepted orderAccepted = new com.sergio.model.events.internal.DepositOrderAccepted(depositOrder.getId(), depositOrder.getQuantity());
		accountRepository.save(accountId, orderAccepted);
		eventBus.produce(externalDepositOrderAccepted);
	} else{
		reject(depositOrder.getId(), "account not found = " + depositOrder.getAccountId());
	}
}

5.3.3 Flujo 3 – Operación de depósito aceptada

Con esto el flujo ha acabo y el depósito (si todo ha ido bien) ha sido aceptado, quedando en estado ACCEPTED. El siguiente paso lógico sería consultar la cuenta para ver que los cambios quedan reflejados:

curl -i http://localhost:8080/bank-account-event-sourcing/resources/accounts/1178b586-2faf-4549-bc9c-b5c21d506121

HTTP/1.1 200 OK
Connection: keep-alive
X-Powered-By: Undertow/1
Server: WildFly/11
Content-Type: application/json
Content-Length: 94
Date: Sun, 20 May 2018 18:05:57 GMT

{"id":"1178b586-2faf-4549-bc9c-b5c21d506121","owner":"Sergio Verde Caballero","quantity":50.0}

Como ya se ha comentado no se actualiza el dominio sino que se generan los eventos necesarios para, partiendo de un estado inicial, poder reconstruirlo dejándolo en un estado consistente. Para ello es necesario recuperar todos los eventos del sistema de más antiguos a más recientes.

private static class EventProcessor {
	private final AccountInfo accountInfo;
	EventProcessor(AccountInfo accountInfo) {
		this.accountInfo = accountInfo;
	}
	void process(List<AccountEvent> allEvents) {
		allEvents.forEach(ev -> ev.apply(accountInfo));
	}
}

public Optional<AccountInfo> get(String accountId) {
	AccountInfo accountInfo = accounts.get(accountId);
	if (accountInfo == null) {
		return Optional.empty();
	} else {
		List<AccountEvent> allEvents = events.getOrDefault(accountId, Collections.emptyList());
		EventProcessor processor = new EventProcessor(accountInfo);
		processor.process(allEvents);
		return Optional.of(accountInfo);
	}
}

6. Conclusiones

Aunque diseñar aplicaciones teniendo en cuenta este patrón puede aportar mayor complejidad, los beneficios que éste aporta compensa con creces la complejidad. Hoy en día es necesario dar una vuelta de tuerca al diseño de aplicaciones, aportando la suficiente flexiblidad como para que sean capaces de escalar a medida que la carga crezca. Event Sourcing es un patrón de arquitectura y no una arquitectura en sí misma, por lo que puede que encaje en ciertas partes del sistema pero que no lo haga en otras. Saber identificar qué partes deben ser escalables es vital para que el sistema aguante la gran carga de usuarios y procesos que deben soportan las apliciones de hoy día.

7. Referencias

Visualización dinámica de componentes en Angular

$
0
0

Índice de contenidos

1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Slimbook Pro 2 13.3″ (Intel Core i7, 32GB RAM)
  • Sistema Operativo: LUbuntu 18.04
  • Visual Studio Code 1.24.0
  • @angular/cli 6
  • @angular 6

2. Introducción

Cuando desarrollamos aplicaciones web con Angular nos podemos encontrar con el caso de tener que visualizar componentes de forma dinámica en base a una cierta lógica de negocio; pero no me refiero a que un componente padre renderice uno u otro hijo en función de un *ngIf, nos estamos refiriendo a instanciar el componente de forma dinámica. El componente debe existir ya.

Un caso de uso que cada vez es más habitual es en el que tenemos una tabla con registros donde cada uno tiene un tipo diferente y en función de ese tipo al hacer click sobre la fila queremos mostrar el componente que le corresponda.

3. Vamos al lío

En cualquier proyecto de Angular CLI que tengamos podemos crear nuestro componente dinámico que será el encargado de a través de las clases ComponenteFactoryResolver y ViewContainerRef instanciar la clase del componente que le indiquemos y sus inputs asociados. Para crearlo simplemente ejecutamos:

$> npm run ng -- generate component dynamic

Esto nos va a crear el fichero dynamic.component.ts donde establecemos la siguiente implementación:

import { Component, ComponentFactoryResolver, Input, ReflectiveInjector, ViewChild, ViewContainerRef } from '@angular/core';

@Component({
  selector: 'app-dynamic',
  template: ``, (1)
  styleUrls: ['./dynamic.component.css']
})
export class DynamicComponent {

  currentComponent = null;

  @ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef; (2)

  @Input() set componentData(data: { component: any, inputs: any }) { (3)
    if (!data) { (4)
      return;
    }

    let inputProviders = [];
    if (data.inputs) { (5)
      inputProviders = Object.keys(data.inputs)
        .map((inputName) => {
          return {
            provide: inputName,
            useValue: data.inputs[inputName]
          };
        });
    }
    const resolvedInputs = ReflectiveInjector.resolve(inputProviders); (6)

    const injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.dynamicComponentContainer.parentInjector); (7)

    const factory = this.componentFactoryResolver.resolveComponentFactory(data.component); (8)

    const component = factory.create(injector); (9)

    this.dynamicComponentContainer.insert(component.hostView); (10)

    if (this.currentComponent) { (11)
      this.currentComponent.destroy();
    }

    this.currentComponent = component; (12)
  }

  constructor(private componentFactoryResolver: ComponentFactoryResolver) { (13)
  }

}
  • (1) Al tratarse de un componente contenedor hacemos uso de ng-tamplate y le damos un identificador por el que poder ser recuperado.
  • (2) A través del anterior identificador y con la ayuda de la clase ViewContainerRef nos quedamos con la referencia del ng-template
  • (3) Establecemos una propiedad de entrada con @Input donde vamos a recibir un objeto que va a tener el componente a instanciar y los inputs que tenga asociados. Se va a encargar de instanciar el componente, gestionar la destrucción del mismo y establecer los inputs requeridos.
  • (4) Si no hay data directamente devolvemos el control.
  • (5) Solo es caso de que el componente a instanciar tenga inputs asociados los establecemos dentro de la variable inputProviders.
  • (6) Creamos el objeto resolvedInputs a través de inputProviders gracias a la clase RefectiveInjector que proporciona Angular.
  • (7) Creamos el objeto injector necesario para resolver las dependencias que el componente a instanciar pueda injectar a través del constructor.
  • (8) A través de la instancia del componente proporcionado se crea una factoria.
  • (9) A través de la factoria se crea el componente pasándole el objeto injector.
  • (10) Se inserta el componente creado en el área marcada con ng-template del componente contenedor.
  • (11) En caso de que ya exista un componente lo destruimos para crear uno nuevo.
  • (12) Establecemos el componente como el componente actual.
  • (13) Inyectamos a través del constructor la clase ComponentFactoryResolver necesaria para la lógica del componente contenedor.

Antes de poder utilizar este componente tenemos que tener una cosa en cuenta, y es que los componentes que vayamos a visualizar de forma dinámica al no poner explícitamente su selector en ningún sitio necesitan ser declarados en la propiedad entryComponents (a parte de declarations) del @NgModule correspondiente.

Ahora en cualquier template de cualquier componente podemos hacer uso del componente contenedor de esta forma:

$<app-dynamic [componentData]="componentData">$</app-dynamic>

Y en la lógica de ese componente establecer un valor para componentData, pasándole los inputs del componente si es que los tuviera.

componentData: any = {
    component: HomeDefaultComponent,
    inputs: {
      name: 'dinámico'
    }
  };

Y para recuperar el input “name” establecido, no podemos hacerlo con @Input, sino que tenemos que hacer uso de la clase Injector de Angular, de este forma:

import { Component, Injector, OnInit } from '@angular/core';

@Component({
    selector: 'home-default',
    template: ` 

Name: {{name}}

` }) export class HomeDefaultComponent implements OnInit { name: string; constructor(private injector: Injector) {} ngOnInit() { this.name = this.injector.get('name'); } }

4. Conclusiones

Como véis le hemos dado una solución muy sencilla y elegante a un caso de uso que sin esta implementación podría volverse inmanejable simplemente conociendo y exprimiendo un poco este framework tan poderoso.

Recordad que esta técnica y otras muchas más las encontraréis en la guía práctica de Angular y también ofrecemos cursos in-house y online.

Cualquier duda o sugerencia en la zona de comentarios.

Saludos.


TICK Stack para BigData con series temporales

$
0
0

Índice de contenidos

1. Introducción

En este tutorial vamos a explicar el stack tecnológico ofrecido por InfluxData que sirve para procesar, analizar y almacenar datos de series temporales. Ofrece las principales piezas del stack como Open Source y un servicio administrado en la nube de pago.

InfluxData es una plataforma completa para el tratamiento de grandes cantidades de datos temporales desde su recolección hasta su análisis final. Puede ser así una alternativa muy útil y completa para trabajar en escenarios de BigData de series temporales consiguiendo una gran optimización en su almacenamiento y su acceso.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2,2 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS High Sierra
  • Entorno de desarrollo: Visual Studio Code
  • Software: InfluxData stack con Telegraf (1.7), InfluxDB (1.5), Chronograph (1.5), y Kapacitor (1.5)

3. Piezas

El stack de InfluxData está divido en 4 piezas principales: Telegraf, InfluxDB, Chronograf y Kapacitor.

Telegraf

Telegraf es un agente que se encarga de recopilar y reportar métricas y/o eventos desde cualquiera de las fuentes posibles. Cuenta con más de 100 plugins o integraciones para diferentes origenes de datos. Permite fácilmente añadir nuevos orígenes de datos ya sean con los plugins existentes para orígenes de datos muy conocidos, o hechos a medida.

Puedes ver la documentación oficial en https://docs.influxdata.com/telegraf/

InfluxDB

InfluxDB es la base de datos encargada de la persistencia, indexación y búsqueda de datos; está especializada en tratamiento de datos de series temporales. Puede manejar gran cantidad de inserciones y búsquedas. También está pensada para tratar datos de métricas, monitoreo, sensores IoT, y análisis en tiempo real. Ofrece un API http y otros protocolos para consumir sus datos como Graphite, collectd, y OpenTSDB.

Permite la configuración de políticas de retención (RP) de datos para expirar automáticamente datos antiguos o modificar el número de réplicas. También se pueden lanzar queries continuas (CQ) para reducir la resolución de los datos, cambiando por ejemplo el cálculo de la media de un valor con una resolución cada 10 minutos a una resolución cada 30 minutos para datos de hace más de un mes.

Puedes ver la documentación oficial en https://docs.influxdata.com/influxdb/

Chronograf

Chronograf es la parte del stack de InfluxData para visualizar y monitorizar datos fácilmente.

Permite ver y controlar el estado de todos los servidores del clúster. Ofrece una interfaz gráfica para configurar alertas, ejecutar “jobs” y detectar anomalías en los datos utilizando las funcionalidades de Kapacitor. Permite crear dashboards personalizados para visualización de datos con distintos tipos de gráficos. Permite administrar la base de datos y las políticas de retención, ver las queries en curso y cancelarlas, y administrar usuarios.

Puedes ver la documentación oficial en https://docs.influxdata.com/chronograf/

Kapacitor

Kapacitor es la pieza que facilita la configuración de alertas, ejecución de “jobs” y detección de anomalías en los datos.

Permite tratar datos en “streaming” o en “batch”. Permite ejecutar transformaciones de datos y guardarlos en InfluxDB. Permite configurar funciones personalizadas para detección de anomalías en los datos. Se integra con sistemas de alertas y chats como: HipChat, OpsGenie, Alerta, Sensu, PagerDuty, Slack, etc.

Puedes ver la documentación oficial en https://docs.influxdata.com/kapacitor/

4. Instalación

Esto se puede instalar libremente pero hay ciertas funcionalidades que no están incluidas en la versión Open Source. Una gran desventaja de esta solución es que la versión que permite alta disponibilidad en la base de datos no es Open Source.

Ofrecen todo el stack como SaaS con el nombre “InfluxCloud”. Puedes consultar los precios en https://cloud.influxdata.com/plan-picker

Si quieres instalar la versión completa por tu cuenta tienes que contratar “InfluxEnterprise”. Más información en https://www.influxdata.com/influxenterprise/

Descargando y arrancando

Vamos a instalar sobre dockers la versión Open Source. Clonaros el repositorio git https://github.com/influxdata/TICK-docker Acceder a la última versión disponible (actualmente la 1.3). Arrancar los dockers definidos en el fichero docker-compose.yml

> git clone https://github.com/influxdata/TICK-docker
> cd TICK-docker/1.3
> docker-compose up

Telegraf

Se ha lanzado telegraf con una configuración establecida en el fichero “etc/telegraf.conf” que explicaremos en el próximo apartado. En estos momentos ya se encuentra recopilando datos básicos sobre uso de cpu, ram, etc.

InfluxDB

Docker también ha levantado el servidor de InfluxDB en el puerto 8086. Puedes verificarlo comprobando que http://localhost:8086/ping te devuelve un 20x.

Puedes arrancar un cliente de InfluxDB que hay como docker para ejecutar comandos con:

> docker-compose run influxdb-cli

Y ejecutar algunos comandos básicos como los siguientes:

> show databases
> show stats

Chronograf

Tambien tendrás desplegado chronograf en el puerto 8888. Puedes verlo accediendo a: http://localhost:8888/

Kapacitor

Por último dispondrás también de Kapacitor en el puerto 9092. Puedes verificarlo comprobando que http://localhost:9092/kapacitor/v1/ping te devuelve un 20x.

Puedes arrancar un cliente de kapacitor que hay como docker para ejecutar comandos con:

> docker-compose run kapacitor-cli

Y ejecutar algunos comandos básicos como los siguientes:

> kapacitor list tasks
> kapacitor list recordings
> kapacitor list topics
> kapacitor list topic-handlers

5. Ejemplo

Telegraf

En el fichero /etc/telegraf.conf podréis ver que está configurado como destino influxdb “[[outputs.influxdb]]” con sus datos de conexión. Entre estos datos está seleccionada la base de datos “telegraf”.

Se han configurado también distintos inputs como: [[inputs.cpu]], [[inputs.disk]], [[inputs.mem]], …

# Telegraf configuration

# Telegraf is entirely plugin driven. All metrics are gathered from the
# declared inputs, and sent to the declared outputs.

# Plugins must be declared in here to be active.
# To deactivate a plugin, comment out the name and any variables.

# Use 'telegraf -config telegraf.conf -test' to see what metrics a config
# file would generate.

# Global tags can be specified here in key="value" format.
[global_tags]
  # dc = "us-east-1" # will tag all metrics with dc=us-east-1
  # rack = "1a"

# Configuration for telegraf agent
[agent]
  ## Default data collection interval for all inputs
  interval = "10s"
  ## Rounds collection interval to 'interval'
  ## ie, if interval="10s" then always collect on :00, :10, :20, etc.
  round_interval = true

  ## Telegraf will cache metric_buffer_limit metrics for each output, and will
  ## flush this buffer on a successful write.
  metric_buffer_limit = 10000
  ## Flush the buffer whenever full, regardless of flush_interval.
  flush_buffer_when_full = true

  ## Collection jitter is used to jitter the collection by a random amount.
  ## Each plugin will sleep for a random time within jitter before collecting.
  ## This can be used to avoid many plugins querying things like sysfs at the
  ## same time, which can have a measurable effect on the system.
  collection_jitter = "0s"

  ## Default flushing interval for all outputs. You shouldn't set this below
  ## interval. Maximum flush_interval will be flush_interval + flush_jitter
  flush_interval = "10s"
  ## Jitter the flush interval by a random amount. This is primarily to avoid
  ## large write spikes for users running a large number of telegraf instances.
  ## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
  flush_jitter = "0s"

  ## Run telegraf in debug mode
  debug = false
  ## Run telegraf in quiet mode
  quiet = false
  ## Override default hostname, if empty use os.Hostname()
  hostname = ""


###############################################################################
#                                  OUTPUTS                                    #
###############################################################################

# Configuration for influxdb server to send metrics to
[[outputs.influxdb]]
  # The full HTTP or UDP endpoint URL for your InfluxDB instance.
  # Multiple urls can be specified but it is assumed that they are part of the same
  # cluster, this means that only ONE of the urls will be written to each interval.
  # urls = ["udp://localhost:8089"] # UDP endpoint example
  urls = ["http://influxdb:8086"] # required
  # The target database for metrics (telegraf will create it if not exists)
  database = "telegraf" # required
  # Precision of writes, valid values are "ns", "us" (or "µs"), "ms", "s", "m", "h".
  # note: using second precision greatly helps InfluxDB compression
  precision = "s"

  ## Write timeout (for the InfluxDB client), formatted as a string.
  ## If not provided, will default to 5s. 0s means no timeout (not recommended).
  timeout = "5s"
  # username = "telegraf"
  # password = "metricsmetricsmetricsmetrics"
  # Set the user agent for HTTP POSTs (can be useful for log differentiation)
  # user_agent = "telegraf"
  # Set UDP payload size, defaults to InfluxDB UDP Client default (512 bytes)
  # udp_payload = 512


###############################################################################
#                                  INPUTS                                     #
###############################################################################

# Read metrics about cpu usage
[[inputs.cpu]]
  # Whether to report per-cpu stats or not
  percpu = true
  # Whether to report total system cpu stats or not
  totalcpu = true
  # Comment this line if you want the raw CPU time metrics
  fielddrop = ["time_*"]

# Read metrics about disk usage by mount point
[[inputs.disk]]
  # By default, telegraf gather stats for all mountpoints.
  # Setting mountpoints will restrict the stats to the specified mountpoints.
  # mount_points=["/"]

  # Ignore some mountpoints by filesystem type. For example (dev)tmpfs (usually
  # present on /run, /var/run, /dev/shm or /dev).
  ignore_fs = ["tmpfs", "devtmpfs"]

# Read metrics about disk IO by device
[[inputs.diskio]]
  # By default, telegraf will gather stats for all devices including
  # disk partitions.
  # Setting devices will restrict the stats to the specified devices.
  # devices = ["sda", "sdb"]
  # Uncomment the following line if you do not need disk serial numbers.
  # skip_serial_number = true

# Read metrics about memory usage
[[inputs.mem]]
  # no configuration

# Read metrics about swap memory usage
[[inputs.swap]]
  # no configuration

# Read metrics about system load & uptime
[[inputs.system]]
  # no configuration


###############################################################################
#                              SERVICE INPUTS                                 #
###############################################################################

InfluxDB

Ejecuta desde la consola de influxdb-cli unas consultas sobre la base de datos de “telegraf”:

> show databases # verás que existe una llamada Telegraf
> use telegraf # seleccionamos la base de datos Telegraf para las siguientes sentencias
> SHOW MEASUREMENTS # mostramos las mediciones existentes
			name: measurements
			name
			----
			cpu
			disk
			diskio
			mem
			swap
			system

> SHOW FIELD KEYS # mostramos los distintos fields para cada tipo de medición
			name: cpu
			fieldKey         fieldType
			--------         ---------
			usage_guest      float
			usage_guest_nice float
			usage_idle       float
			usage_iowait     float
			usage_irq        float
			usage_nice       float
			usage_softirq    float
			usage_steal      float
			usage_system     float
			usage_user       float
				
			name: disk
			fieldKey     fieldType
			--------     ---------
			free         integer
			inodes_free  integer
			inodes_total integer
			inodes_used  integer
			total        integer
			used         integer
			used_percent float
				
			name: diskio
			fieldKey         fieldType
			--------         ---------
			io_time          integer
			iops_in_progress integer
			read_bytes       integer
			read_time        integer
			reads            integer
			weighted_io_time integer
			write_bytes      integer
			write_time       integer
			writes           integer
				
			name: mem
			fieldKey          fieldType
			--------          ---------
			active            integer
			available         integer
			available_percent float
			buffered          integer
			cached            integer
			free              integer
			inactive          integer
			total             integer
			used              integer
			used_percent      float
				
			name: swap
			fieldKey     fieldType
			--------     ---------
			free         integer
			in           integer
			out          integer
			total        integer
			used         integer
			used_percent float
			
			name: system
			fieldKey      fieldType
			--------      ---------
			load1         float
			load15        float
			load5         float
			n_cpus        integer
			n_users       integer
			uptime        integer
			uptime_format string

> SELECT usage_idle FROM cpu WHERE cpu = 'cpu-total' LIMIT 5 # consultamos datos sobre uso de cpu
			name: cpu
			time                usage_idle
			----                ----------
			1529153390000000000 99.87437185932569
			1529153400000000000 95.53001277139474
			1529153410000000000 99.26952141063127
			1529153420000000000 99.6485943774752
			1529153430000000000 99.67369477910451

Chronograf

Acede a la url de Chronograf y prueba a lanzar queries básicas sobre el uso de cpu o el nivel de ram desde “Data Explorer”.

Puedes configurar tu primer dashboard y añadirle un par de gráficos para ver cómo funciona y las facilidades que te da para explotar y visualizar fácilmente los datos de InfluxDB.

Puedes incluso configurar fácilmente alertas para Kapacitor creando una nueva regla en el apartado “Alerting”, configurando simplemente el field de una serie temporal y una condición de lanzamiento. Del mismo modo puedes configurar el mensaje y suceso que lanza esa alerta que puede ser una simple escritura en un log, un webhook, etc.

6. Conclusiones

Hemos visto que InfluxDB ofrece una plataforma muy completa para el tratamiento óptimo de datos de series temporales permitiéndonos fácilmente entre otras cosas: su procesamiento y recepción, su posterior almacenamiento, configurar políticas para que este almacenamiento sea óptimo y escalable en el tiempo, configurar dashboards y gráficas para poder visualizar estos datos, y configurar alertas para detectar anomalías en los datos.

Amarás LXD por encima de todas las cosas

$
0
0

Índice de contenidos

1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Slimbook Pro 2 13.3″ (Intel Core i7, 32GB RAM)
  • Sistema Operativo: LUbuntu 18.04
  • LXD 3.1
  • LXC 3.1

2. Introducción

Este es un tutorial muy especial para mí dado que hace el número 100, así que me vais a permitir que sea un poco más apasionado y menos imparcial de lo habitual, aunque ninguna mentira voy a contar ;-).

En este tutorial vengo a hablar de una tecnología que me lleva enamorado desde hace ya unos cuantos meses, LXD.

En resumidas cuentas, con LXD te olvidas de tener que hacer máquinas virtuales con Virtualbox o VMWare, simplemente se crean como contenedores de sistemas ligeros al estilo Docker y se comportan como una máquina virtual, es la tecnología perfecta para simular una nube de AWS, o cualquier otro proveedor, en nuestro equipo de desarrollo. Además permite instalar Docker y Docker Compose para ejecutar contenedores dentro de las máquinas creadas.

Es una tecnología que está impulsada por Canonical y actualmente sólo funciona al 100% en Ubuntu y sus distintos sabores, como LUbuntu que es el que uso en el día a día. Esto puede ser una mala noticia para algunos y para otros el empujón definitivo para el uso de Ubuntu como su sistema operativo que, por otro lado, es el único al que tod@s podemos acceder de forma 100% gratuita.

Las posibilidades son infinitas con esta tecnología ya que nos permite crear n mil contenedores de sistema que en la práctica van a funcionar como máquinas virtuales pero sin las limitaciones de espacio y memoria que una tecnología de virtualización como Virtualbox o VMWare establecen. De este modo tú como desarrollador puedes tener un completo sistema de integración continua con todos los servicios (GitLab, Nexus, Jenkins, Sonarqube, …) cada uno instalado de forma independiente en su propia máquina para evitar conflictos y, por defecto, todos interconectados. También lo puedes usar para probar la última herramienta que haya salido o montar un servidor de pruebas. Este sería un ejemplo del listado de máquinas que tengo definidas actualmente en mi equipo a las que puedo acceder vía ssh como si fueran máquinas en la nube.


Por tanto si quieres aprovechar esta maravillosa tecnología y seguir los pasos de este tutorial, vas a necesitar al menos una máquina virtual con Ubuntu, aunque si no estás desarrollando nada específico para Mac, estás tardando en pasarte a lo bueno, la combinación Slimbook PRO + LUbuntu a mi me resulta perfecta para el desarrollo fullstack! :-).

Pero como el movimiento se demuestra andando…

3. Vamos al lío

Permíteme que insista… todo lo que vamos a ver a continuación aplica sólo en el sistema operativo Ubuntu, en concreto yo estoy utilizando LUbuntu 18.04.

La instalación de esta tecnología no puede ser más sencilla, se hace a través de un paquete snap ejecutando:

$> sudo snap install lxd --classic

Nota: si no lo tienes instalado simplemente ejecuta: sudo apt install snapd. También sólo en caso de estar en la versión 16.04 tenemos que ejecutar: sudo apt install zfsutils-linux -y para instalar este tipo de almacenamiento, en la versión 18.04 ya viene por defecto instalado.

Ahora para no tener que estar anteponiendo sudo al resto de comandos vamos a añadir a nuestro usuario actual al grupo “lxd” ejecutando estos comandos:

$> sudo usermod -a -G lxd ${USER}
$> newgrp lxd

Ahora vamos a inicializar LXD para permitir la creación de los contenedores de sistema LXC que ya hemos dicho que se pueden tratar como máquinas virtuales al uso compatibles con Docker, que serían contenedores de aplicación. Simplemente ejecutamos:

$> lxd init

Al ejecutar este comando el sistema nos va a solicitar la siguiente información:

  • Would you like to use LXD clustering? (yes/no) [default=no]: le decimos que no queremos usar el clustering.
  • Do you want to configure a new storage pool? (yes/no) [default=yes]: le decimos que si queremos configurar un nuevo almacenamiento, será necesario para el filesystem de los contenedores que creemos.
  • Name of the new storage pool [default=default]: le damos un nombre, default está bien.
  • Name of the storage backend to use (btrfs, ceph, dir, lvm, zfs) [default=zfs]: vamos a utilizar el antes mencionado zfs, si no te sale esta opción por defecto, haz Control + C e instala el paquete zfsutils-linux y vuelve a empezar con el comando init.
  • Create a new ZFS pool? (yes/no) [default=yes]: Confirmamos que sí queremos crear este pool.
  • Would you like to use an existing block device? (yes/no) [default=no]: No queremos utilizar un bloque existente.
  • Size in GB of the new loop device (1GB minimum) [default=15GB]: aquí configuramos el tamaño del almacenamiento inicial, esto va a depender del espacio de tu máquina y el número de contenedores que quieras levantar y el contenido que va a almacenar, pero yo recomiendo tirar por lo alto, en la medida de lo posible.
  • Would you like to connect to a MAAS server? (yes/no) [default=no]: No queremos conectar con un servidor de MAAS.
  • Would you like to create a new local network bridge? (yes/no) [default=yes]: Si queremos crear un “bridge” ya que será necesario para intercomunicar automáticamente los contenedores entre sí.
  • What should the new bridge be called? [default=lxdbr0]: Dejamos ese nombre por defecto.
  • What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: Lo dejamos en “auto” para no preocuparnos por tema de redes.
  • What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: Lo ponemos en “none” para evitar conflictos.
  • Would you like LXD to be available over the network? (yes/no) [default=no]: Le decimos que no, por el momento no queremos que LXD esté disponible en la red, sólo en nuestro equipo.
  • Would you like stale cached images to be updated automatically? (yes/no) [default=yes] Nos pregunta si queremos actualizaciones automáticas, vamos a decirle que sí.
  • Would you like a YAML “lxd init” preseed to be printed? (yes/no) [default=no]: Ahora mismo no nos interesa que nos saque la información en YAML.

Realizada la configuración inicial, ahora vamos a crear un contenedor de sistema base con la configuración necesaria para no tener que utilizar root y tener acceso remoto a través de SSH. Seguimos los siguientes pasos:

Creamos un nuevo contenedor partiendo de una imagen de Ubuntu 16.04 Server.

$> lxc launch ubuntu:16.04 prime

Una vez creado, como es la primera vez tardará un poco en descargar la imagen, vamos a listar todos los contenedores con el comando:

$> lxc list

Podemos ver que el contenedor está corriendo y ya tiene una IP asignada. Ahora para acceder dentro como root, vamos a ejecutar:

$> lxc exec prime bash

De esta forma el resto de los pasos los vamos a ejecutar dentro de nuestro contenedor “prime” como root. Lo primero que vamos a hacer es configurar la fecha y hora del sistema.

$(prime)> timedatectl set-timezone Europe/Madrid

Creamos un usuario no-root, por ejemplo, master:

$(prime)> useradd -m master

Establecemos una password para ese usuario:

$(prime)> sh -c 'echo master:superpass | chpasswd'

Añadimos el usuario a los grupos lxd y sudo:

$(prime)> usermod -a -G lxd,sudo master

Permitimos que el usuario tenga shell:

$(prime)> usermod -s /bin/bash master

Permitimos la autenticación en el contenedor:

$(prime)> sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config

Reiniciamos el servicio ssh:

$(prime)> service ssh restart

Editamos las variables del entorno del usuario “master”, editando el fichero ~/.bashrc

export LC_CTYPE=en_US.UTF-8 
export LC_ALL=en_US.UTF-8

Leemos las nuevas variables:

$(prime)> source ~/.bashrc

Salimos del contenedor:

$(prime)> exit

Podemos probar que efectivamente podemos acceder al contenedor a través de SSH con el usuario el master y la password proporcionada:

$> ssh master@ip_asignada

Nuestro contenedor base ya está listo para poder ser reutilizado. Para ello vamos a publicarlo como imagen, para lo que tenemos que ejecutar los siguientes comandos:

$> lxc stop prime
$> lxc publish prime  --alias=prime

Ahora vamos a probar la integración con Docker, para ello creamos una nueva maquina con el comando a partir de la anterior con los modificadores de seguridad que nos permiten la creación de contenedores de Docker:

$> lxc launch prime docker-test -c security.nesting=true -c security.privileged=true

Ahora si ejecutamos:

$> lxc list

Podemos ver la IP que LXD le ha asignado de forma automática. Con lo que podemos acceder con el usuario “master” ejecutando:

$> ssh master@ip_docker_test

Ya estamos dentro de una máquina Ubuntu 16.04 vacía para nuestro uso y disfrute como si de una instancia EC2 de AWS se tratará pero más barato. Para el fin de probar la integración con Docker tenemos que instalar Docker-CE, para ello ejecutamos:

$(docker-test)> sudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
$(docker-test)> curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$(docker-test)> sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) stable"
$(docker-test)> sudo apt-get update && sudo apt-get install -y docker-ce
$(docker-test)> sudo usermod -aG docker $USER
$(docker-test)> docker --version

Y la podemos dejar ya preparada para la ejecucion de Docker Compose:

$(docker-test)> sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
$(docker-test)> sudo chmod +x /usr/local/bin/docker-compose
$(docker-test)> docker-compose -version

Ahora para ver que efectivamente Docker está funcionado dentro del contenedor de sistema vamos a ejecutar el típico hello-world:

$(docker-test)> sudo docker run hello-world

Y para probar un servicio vamos a ejecutar una instancia de nginx de esta forma:

$(docker-test)> sudo docker run -d -p 82:80 nginx

Ahora si abrimos un navegador en nuestro equipo y accedemos a la URL: http://ip_docker_test:82 tenemos que ver la página por defecto del servidor de nginx funcionando.

4. Conclusiones

Esta es sólo la punta del iceberg de todo lo que nos permite hacer esta maravillosa tecnología. Entre otras cosas podemos asignar cuotas de disco y memoria a cada contenedor, hacer snapshots para volver a puntos estables en cualquier momento, manejar completamente las interfaces de red de cada contenedor, …

Abrazar Ubuntu tiene muchas y buenas ventajas. Ánimo que no duele 😉

Cualquier duda o sugerencia en la zona de comentarios.

Saludos.

Streamlines, Swimlanes y cómo utilizarlos en Trello

$
0
0

Tanto Streamline como Swimlane son términos más propios de física que de la gestión de proyectos. Es por ello necesario hacer distinción y aclarar la terminología en el área del Management que nos atañe.

Índice de contenidos

1. Introducción

Streamlines y Swimlanes son piezas de un puzzle que nos van a permitir hacer las cosas mejor. Si estas las sumamos a Trello, observaremos que completamos un puzzle en el que todas las piezas encajan a la perfección y nos proporcionan una herramienta valiosísima con la que trabajar.

Su uso está generalizado entre las diferentes empresas así como a título particular debido a que proporciona una forma muy visual, portable y sencilla de acceder a una gran cantidad de información compartida. Además, en nuestra experiencia en la formación de los diferentes clientes, ha demostrado ser una herramienta ágil, fácil y de rápido manejo de uso por los usuarios.

La herramienta virtual en la que nos vamos a apoyar es Trello en su versión gratuita, debido a que mejora la capacidad de aprendizaje de conceptos ágiles y favorece la transición hacia la transformación digital. La flexibilidad de su uso la hace ser elegida como primera herramienta con la que gestionar nuestra demanda de trabajo en proyectos llevados para grupos pequeños (3 – 7 personas). Además, al ser Atlassian el propietario, permite la total integración con herramientas de su catálogo como son Jira y Confluence. Por tanto, su uso por principiantes es altamente recomendado antes de empezar con herramientas mayores como es el caso de Jira, la cual ha ido importando la interfaz de Trello.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15” (2,5 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS High Sierra 10.13.5
  • Trello Versión de Escritorio – FREE
  • Navegador Chrome Versión 67.0.3396.87 (Build oficial) (64 bits)

3. Streamlines, Swimlanes y cómo utilizarlos en Trello

Un Streamline es la acción que consiste en hacer un negocio, actividad o proceso más simple y efectivo mediante el empleo de métodos de trabajo más sencillos y rápidos. Estos métodos pueden ser: la racionalización del trabajo entre diferentes grupos, la limitación del WIP, el uso de Políticas explícitas, reducir el número de personas involucradas, incluir Clases de Servicio, etc. Su uso conlleva la optimización del flujo de trabajo.

No se debe confundir el concepto Streamline con el concepto de Swimlane. Un Swimlane es una división por carriles en los Diagramas de Flujo y Tableros kanban para distinguir de forma visual el trabajo compartido y las dependencias entre los procesos de negocio. Por tanto, favorecen la visualización y simplifican el proceso o actividad que se esté llevando a cabo. Ayudan a realizar el Streamline de nuestro flujo de trabajo.

Los Swimlanes se emplean como un mecanismo para organizar diferentes actividades en categorías visualmente separadas con la finalidad de mostrar las diferentes dependencias entre funcionalidades.

Para fomentar la visualización de los Swimlanes en los tableros kanban se pueden utilizar diferentes colores y símbolos.

Con los Streamlines (recordemos que son las acciones que van a hacer que las cosas vayan mejor) vamos a conseguir racionalizar, agilizar y gestionar la evolución del producto en el que, por la naturaleza de la organización, estemos obligados a gestionar dependencias cruzadas entre capas o componentes que conformen una solución.

Por tanto, una vez entendidos ambos conceptos, entendemos que un tablero kanban con Streamlines difiere de otros en que los diferentes grupos que constituyen el desarrollo de la solución (roles), capas o componentes son ubicados en diferentes carriles (Swimlanes). Una vez realizada esta división, el tablero kanban mostrará por cada carril (correctamente etiquetado) cómo se organiza el flujo de trabajo.

4. Cómo utilizarlos en Trello

Trello es un software de administración de proyectos con interfaz web, cliente para iOS y android para organizar proyectos.

Empleando el sistema kanban, permite el registro de actividades a través de tarjetas visuales, cuya finalidad es organizar tus elementos de trabajo. Entre otras características, Trello permite crear listas, grupos de trabajo, etiquetas, adjuntar archivos, asignar una fecha de vencimiento a las tarjetas, agregar comentarios, enlaces, imágenes e incluso compartir tus tableros.

Trello permite de una forma muy visual y muy fácil de usar, gestionar tus ideas. Además, incluye potenciadores (Power Ups) que mejoran e incrementan tanto la usabilidad como la funcionalidad del mismo.

Existen tres versiones de Trello: la versión gratuita, versión Business Class y la versión Enterprise; estas dos últimas accesibles a través de una cuota mensual. Las diferencias son que la versión Free tiene limitación a 1 único Power Up y limitación de archivos  adjuntos de hasta 10 MB, la versión Business Class incluye Aplicaciones integradas, sin limitación de power ups, adjuntos de 250 MB, resúmenes de equipos y mayor seguridad y por último la versión Enterprise permite la gestión de varios equipos. Para mayor nivel de detalle en sus diferencias: https://trello.com/pricing

La diferencia fundamental entre ambas es que la versión Business permite un uso ilimitado de Power Ups así como recursos de uso de la aplicación más completos, mientras que la versión Free solo dispone de un Power Up de uso y en cuanto a funciones está ligeramente más restringido.

Con el paso del tiempo Trello ha ido creciendo en popularidad y ello ha llevado a la gran comunidad de seguidores de que dispone a que siga incluyendo mejoras en la herramienta, todo ello mediante el uso de Plugins (la mayoría afortunadamente gratuitos) que se instalan en el navegador (recomendado usar Chrome).

Entre los Plugins disponibles podemos encontrar: aquellos que nos permiten gestionar proyectos predictivos (Scrum), proyectos reactivos (Kanban), otros que nos van a permitir mejorar la visualización, etc. En la tienda de plugins del navegador podemos descubrir la gran variedad existente escribiendo “Trello”.

En Trello el límite lo pone tu imaginación.

4.1. Opción 1 – Tablero kanban con Streamlines

Requisitos:

  • Navegador Chrome actualizado a la última versión
  • Cuenta de Trello versión Free
Plugins del navegador utilizados:

  • Swimlanes for Trello
  • Agile Scrum for Trello Boards
  • Pro for Trello, FREE Trello Tweaks

*NOTA: No se va a detallar el completo funcionamiento de los Plugins mencionados puesto que no es objeto de este artículo.

Una vez instalados los Plugins anteriores en el navegador accederemos a nuestra cuenta de Trello y creamos un nuevo tablero.

Posteriormente, dependiendo de la estructura en fases que le queramos dar, procederemos a generar una lista por cada fase, en nuestro caso vamos a tener ACTIONS, BLOCKED, DOING, DONE, DELIVERED, VALIDATED y se ha generado una lista inicial para identificar la FASE:


El Plugin Swimlanes for Trello permite generar los Swimlanes, para ello en la cabecera de la lista debemos introducir el carácter | o bien el emoji del nadador de Trello :swimmer: y a continuación, el título que le queramos dar al Swimlane. Por ejemplo, para generar un nuevo Swimlane para el EQUIPO 1, crearemos una nueva lista y en la cabecera indicaremos Product Backlog | EQUIPO 1, posteriormente tantas listas como fases vaya a tener nuestro tablero; repitiendo el proceso por cada equipo que queramos agregar. De esta forma iremos creando nuestros carriles independientes.



Para dar formato al texto como en el ejemplo se ha utilizado el lenguaje de marcado de Trello (markup language) y el de los diferentes plugins. Para acceder a este puedes hacerlo desde la Descripción de la tarjeta. En la esquina inferior derecha aparece la Guía de Formato donde se detalla este:


A través de las páginas de los autores de los diferentes Plugins, así como desde la configuración de los mismos se puede acceder al lenguaje de marcado de cada uno. A continuación, listamos los utilizados en el tutorial:

                                              
Políticas – Agrega en la cabecera:                *** TEXTO ***
WIP – Agrega en la cabecera:                [X] → cambia X por el nº de tu WIP
Estimación – Agrega en la cabecera:                (X) → cambiar X por tu nº de estimación
Prioridades – Agrega en la cabecera:                ! → Baja; !! → Media; !!! → Alta

Con ello completamos en el tablero los Streamlines empleados en nuestro ejemplo, en el que se ha utilizado limitación del WIP, estimaciones, swimlanes, políticas de servicio y prioridades.

A modificar, según el caso personal de cada uno, quedando en mi caso tal que así:

4.2. Opción 2 – Tablero Scrumban en Trello

Requisitos:

  • Navegador Chrome actualizado a la última versión
  • Cuenta de Trello versión Free
Plugins del navegador utilizados:

  • Swimlanes for Trello
  • Agile Scrum for Trello Boards
  • Plus for Trello
  • Kanbanello for Trello

*NOTA: No se va a detallar el completo funcionamiento de los Plugin mencionados puesto que no es objeto de este artículo.

Power Up de Trello

  • Agile Metrics by Screenful: Además de permitirte crear Sprints y Releases para la parte Kanban te permitirá obtener los Lead & Cycle Times.

En este caso similar al anterior vamos a definir un Tablero Trello con 2 Streamlines. El 1º para nuestra demanda reactiva (Kanban) y el 2º para nuestra demanda predictiva (Scrum).

Utilizaremos las ventajas que nos proporcionan los diferentes plugins para gestionar ambos. Con el Plugin Swimlanes crearemos los carriles por donde discurrirá nuestro flujo de trabajo, con Agile Scrum y Plus for Trello gestionaremos las estimaciones y diferentes gráficas (Burndown, Estimaciones, etc.) así como el WIP, con Kanbanello podremos agrupar diferentes listas que comparten el mismo WIP e identificar visualmente las listas que alcanzaron el límite WIP y por último con el Power Up Agile Metrics nos permitirá obtener los Lead & Cycle Times.


5. Conclusiones

Dada la versatilidad de los Plugin utilizados y debido a que se complementan entre ellos sin generar incidencias, es posible tener la gestión de proyectos de cierta magnitud (ateniéndose a las restricciones de Trello) y con un tamaño de equipo entre 3-7 personas de forma más que correcta y sin hacer un desembolso ni cuota mensual debido a la disponibilidad FREE de los mismos.

Se recomienda jugar con ellos así como con los Power Ups disponibles de Trello (lamentablemente Trello sólo permite 1 único Power Up en su versión gratuita) para conseguir la mayor personalización de tu tablero.

Para profundizar más en el uso de Scrum con Trello recomiendo la lectura del Blog del Trello y en particular el artículo https://blog.trello.com/how-to-scrum-and-trello-for-teams-at-work.

6. Plugins

https://chrome.google.com/webstore/search/swimlanes%20for%20trello https://chrome.google.com/webstore/detail/agile-scrum-for-trello-bo/njmflagahgdhopbcdilgahjlfiecakpe https://chrome.google.com/webstore/detail/pro-for-trello-free-trell/hcjkfaengbcfeckhjgjdldmhjpoglecc https://chrome.google.com/webstore/detail/plus-for-trello-time-trac/gjjpophepkbhejnglcmkdnncmaanojkf https://chrome.google.com/webstore/detail/kanbanello-for-trello/onkejpdhplmfiiafhkjjfalenddohccb

7. Referencias

Diccionario  de Cambridge
Wikipedia

Corriendo Apache Spark sobre Kubernetes

$
0
0

Índice de contenidos

1. Introducción

Apache Spark es una herramienta para procesamiento en memoria de grandes cantidades de datos. Suele ejecutarse sobre una arquitectura Hadoop y es muy útil que sea así sobre todo si el origen de datos está también en Hadoop como por ejemplo con HDFS o HBase.

En este tutorial vamos a ver cómo Spark ofrece un mecanismo de ejecución alternativo para que el procesamiento de los jobs se lance sobre PODs/contenedores de Kubernetes. Este mecanismo creará dinámicamente los contenedores necesarios para la ejecución de los jobs y luego los destruirá. Se puede beneficiar así de un gran ahorro de costes haciendo uso de un clúster de Kubernetes con autoescalado.

Para seguir este tutorial se recomienzan conocimientos básicos sobre Kubernetes y al menos saber la teoría básica sobre Apache Spark y para qué sirve. Para aprender un poco de Kubernetes podéis seguir este tutorial. Para aprender un poco de Apache Spark podéis seguir este otro tutorial.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2,2 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS High Sierra
  • Entorno de desarrollo: Visual Studio Code
  • Software: Apache Spark 2.3.1 y Minikube 0.28.0

3. Contexto

Vamos a utilizar el script de Spark “spark-submit” para lanzar los jobs de Spark. En este caso utilizaremos una opción que tiene para que el destino de ejecución sea un clúster de Kubernetes. Indicaremos al script la URL del API de Kubernetes y así dará la orden de crear un POD que será el “driver” de Spark. Este driver se encargará de crear los PODs executors y de sincronizar su trabajo. Una vez finalizado el trabajo el driver borrará los PODs executors y finalizará su ejecución correctamente.

4. Instalación

Minikube

Necesitamos un clúster de Kubernetes con kubectl configurado para conectarse. En nuestro caso vamos a utilizar “minikube” para correr Kubernetes localmente sobre una máquina virtual.

Podéis ver más documentación sobra la instalación y uso de minikube en https://kubernetes.io/docs/setup/minikube/.

Una vez instalado lo arrancamos con:
> minikube start

Ahora tenemos la máquina virtual con Minikube arrancada y kubectl configurado para conectar con este clúster de Kubernetes. Se puede, y es recomendable, incrementar desde VirtualBox algunos recursos de la máquina de minikube como procesador y memoria RAM. Podemos ejecutar el comando “kubectl proxy” para habilitar el acceso desde la url http://localhost:8001/ui/ al dashboard de nuestro clúster.

En el clúster de Kubernetes deberá estar activo “kube-dns” y tener permisos para crear y listar PODs, configMaps y secrets. No deberías tener problemas con tu Minikube de serie.

Apache Spark

Si aún no tienes instalado Apache Spark tendrás que instalarlo. Este proceso dependerá de tu sistema operativo. Para MacOS suele utilizarse brew:

> brew install java scala apache-spark

No es objetivo de este tutorial centrarse en la instalación y utilización de Spark en general. Podéis encontrar mucha información en la documentación oficial https://spark.apache.org/docs/latest/

Preparación de la imagen de Docker

Primero vamos al directorio de instalación de Spark. Por ejemplo en mi caso es “/usr/local/Cellar/apache-spark/2.3.1/libexec”. Aquí tenemos las utilidades para crear nuestro contenedor de ejecución de Spark. El ejecutable de utilidad está en “bin/docker-image-tool.sh” y el dockerfile base en “kubernetes/dockerfiles/spark/Dockerfile”. Este Docker incluirá en su directorio /opt/spark/jars todos los jars de la carpeta “jars”. Vamos a modificar este fichero para incluir un jar de job de ejemplo, aunque este se podría también subir a un repositorio maven o pasárselo directamente con una url http. Para ello modificamos el dockerfile añadiendo la línea:

COPY examples/jars/*.jar /opt/spark/jars-examples/

Estando logados con Docker en un repositorio remoto (puede ser uno público como DockerHub) ejecutamos:

> docker-image-tool.sh -r mirepo -t latest build
			> docker-image-tool.sh -r mirepo -t latest push

Esto nos construirá y publicará una imagen con el nombre mirepo/spark:latest.

Si durante el proceso nos da el error ‘“docker build” requires exactly 1 argument’. Están solucionándolo pero si no tenéis la versión de Spark con el arreglo solo hay que modificar el fichero docker-image-tool.sh y añadir en la línea 59 “BUILD_ARGS=()”.

5. Lanzando jobs

Spark Submit

Vamos a enviar a Kubernetes la orden de ejecutar un job para calcular un valor aproximado de PI (típico ejemplo que se usa para Spark). Ejecutamos el siguiente comando:

spark-submit --deploy-mode cluster --name spark-pi --class org.apache.spark.examples.SparkPi --master k8s://http://localhost:8001 --conf spark.kubernetes.namespace=default --conf spark.executor.instances=2 --conf spark.kubernetes.container.image=mirepo/spark:1.0 local:///opt/spark/jars-examples/spark-examples_2.11-2.3.1.jar

Como vemos le hemos indicado el jar que está incluido en el Docker “local:///opt/spark/jars-examples/spark-examples_2.11-2.3.1.jar”, pero esto podría haber sido una ruta http a un jar subido a algún servidor o un repositorio maven. También le indicamos a Spark datos como:

  • El nombre del job: –name spark-pi
  • El nombre de la clase a ejecutar: –class “org.apache.spark.examples.SparkPi”
  • La ruta del API del clúster de Kubernetes: –master “k8s://http://localhost:8001”
  • El número de instancias a crear para la ejecución: “spark.executor.instances=2”
  • La imagen de Docker a utilizar: –conf spark.kubernetes.container.image=mirepo/spark:1.0

¿Y qué está pasando ahora?. Spark ha creado un Docker de tipo “driver” como un POD de Kubernetes. Este driver ha creado los executors indicados como PODs de Kubernetes y se ha conectado a ellos para ejecutar el código.

Cuando la ejecución del job se ha completado, el drive termina y limpia todo, pero el driver del POD en sí permanece en estado “Terminated: Completed” con los logs accesibles.

Podemos ver ahora los logs de esta tarea terminada y observaremos el cálculo que ha realizado el job para el valor de PI:

6. Conclusiones

Hemos visto como Spark permite aprovechar la potencia de procesamiento de un clúster de Kubernetes y puede ser una buena alternativa para ejecutar cargas de trabajo de Apache Spark. Es muy útil si ya se cuenta con un la instalación de un clúster de Kubernetes y no se quiere instalar una nueva infraestructura distinta para Spark.

7. Referencias

TypeScript VS JavaScript

$
0
0

Cómo sobrevivir a un proyecto grande de JavaScript: Oda a TypeScript. En este tutorial compararemos JavaScript con TypeScript y veremos lo que nos ofrece y por qué hará que tengamos hasta un 15% de bugs menos.

Índice

1. Introducción

Históricamente JavaScript ha sido conocido por su flexibilidad, pero, en proyectos grandes o con cierta complejidad esta flexibilidad se vuelve un punto negativo, dado que pasamos de ser flexibles a ser laxos. ¿A qué nos referimos con ser laxos? Pues a que la experiencia del desarrollo se vuelve una carga, dado que:

  • Desconocemos los tipos con los que tratamos.
  • La introducción de bugs puede aumentar hasta un 15% en producción. (https://blog.acolyer.org/2017/09/19/to-type-or-not-to-type-quantifying-detectable-bugs-in-javascript/).
  • La documentación exhaustiva del código es necesaria para determinar con qué estructuras de datos estamos tratando.
  • Las ayudas del lenguaje son nimias.
  • Los refactors automáticos prácticamente son inexistentes (resulta casi imposible hasta cosas tan sencillas como cambiar de nombre una variable o función).

Y esta problemática no es ni mucho menos por la falta de herramientas de desarrollo, sino más bien por la propia naturaleza del lenguaje.

2. Introducción a TypeScript

Por todos los problemas mencionados anteriormente es por lo que surgió TypeScript. Microsoft lanzó este proyecto en octubre de 2012 y cuenta con una gran aceptación dentro de la comunidad, siendo el lenguaje de los más valorados de la prestigiosa encuesta anual de Stackoverflow. Y no solamente es de los más valorados, si no que también se usa en un amplio espectro de proyectos y de frameworks distintos.

Empresas como Google, Github, Adobe, Walmart, Slack, Microsoft, JetBrains y Netflix hacen uso de TypeScript, con lo que es de esperar que tenga una larga vida, y más aún cuando proyectos como Angular y Vue fomentan de forma tan activa el uso de TypeScript.

Por último, Microsoft tiene un gran aliciente para continuar el desarrollo de TypeScript y es la transformación que ha sufrido la empresa en la última década, dedicando muchos recursos a la creación de proyectos libres y abiertos por y para la comunidad (VSCode, TypeScript, ReactXP, API REST Guidelines, etc).

3. Características del lenguaje

En JavaScript, aunque sea dinámico podemos obtener ciertas ayudas que mejoran nuestra experiencia de desarrollo.

Las herramientas actuales son capaces de inferir que foo es de tipo String, con lo que nos ofrece todos los métodos de los que dispone la clase String. Esta inferencia no es magia. Microsoft ha tipado todos los elementos de JavaScript en lo que se denomina una definición de tipos (hecha, irónicamente en TypeScript, y de la cual se aprovechan muchos IDEs). Muchas bibliotecas incluyen definiciones de tipos para su API pública.

¿Pero qué ocurre cuando invocamos substr con un parámetro incorrecto? No nos aparece error, porque no sabe si en algún momento foo se vuelve de otro tipo y ese tipo cuenta con un método substr. En este caso dará un error en tiempo de ejecución. ¿Qué pasa si hacemos lo mismo en TypeScript?

Cuando usamos TypeScript, el lenguaje es capaz de saber que foo no ha cambiado de tipo, con lo que te avisa que substr no puedes invocarlo con un argumento que no sea un número. Este tipo de ayudas se empieza a notar cuando tenemos ficheros que exportan tipos, valores o funciones. TypeScript es capaz de darnos toda la ayuda necesaria para hacer un uso correcto de los mismos.

Podemos tipar nuestros objetos con interfaces y hacer uso de estas para poder tratar de forma mucho más óptima con los objetos:

Y además, podemos acceder a todo tipo de refactors, extracciones e incluso auto-imports:

Todo lo que hemos mostrado no es más que la punta del iceberg, aunque realmente, para obtener beneficios sustanciales la inversión de esfuerzo es mínima, ya que solamente con tipar nuestras variables y funciones obtendremos una gran parte de las ventajas de TypeScript.

4. Desventajas

Una desventaja de TypeScript es la introducción de más complejidad en la parte de configuración nuestro proyecto. Es una dependencia más que hay que actualizar, configurar y gestionar.

Si bien TypeScript cuenta con una curva de aprendizaje y esto se puede ver como una desventaja, no olvidemos que esta es muy leve, ya que TypeScript parte de JavaScript, teniendo ambos una sintaxis muy similar y TypeScript sólo añade el tipado.

5. Ejemplo

Desde luego lo mejor para ver el potencial de TypeScript es usarlo. Por tanto he preparado un repositorio de un proyecto hecho con Vue. Para apreciar los cambios realizados más concretamente es interesante ver esta esta PR .

Se recomienda encarecidamente que se descargue el código, se instale depencias con yarn install o npm install, se compile y se abra con VSCode o con WebStorm (TypeScript cuenta con soporte para un amplio abanico de IDEs – https://www.typescriptlang.org – Sección “Get TypeScript”). Y ver que la experiencia de desarrollo es abrumadoramente buena.

6. Comparativa

Esta comparativa que se mostrará a continuación viene con un repositorio de ejemplo: https://github.com/cesalberca/ts-vs-js

6.1. Tipos básicos

JavaScript:

TypeScript:

Nota: En este caso todos los tipos del ejemplo de TypeScript se pueden omitir, dado que TypeScript cuenta con inferencia de tipos.

6.2. Null y undefined

JavaScript:

TypeScript:

En el caso de TypeScript, una variable con un tipo String nunca va a poder ser de tipo nula, con lo cual no compila. En el caso siguiente, cuando una variable puede ser nulable e invoquemos métodos o propiedades sobre esa variable el compilador nos obligará a comprobar que es nula, dándonos más seguridad frente a nulos que JavaScript.

6.3. Funciones

JavaScript:

TypeScript:

6.4. Interfaces

JavaScript:

TypeScript:

En este caso al tratar con la interfaz Persona, sabemos en todo momento qué propiedades tiene y nos avisa cuando tratamos de acceder a una propiedad que no existe. Si modificamos una propiedad de la interfaz, nos obliga a cambiar su uso, lo que facilita en gran medida los refactors.

6.5. Clases

JavaScript:

TypeScript:

Las clases son propias de JavaScript, solo que al añadir tipos, obtenemos toda lo potencia de TypeScript, con todas las ayudas y la detección de errores.

7. Comparativa con código real

Aquí mostraremos las principales diferencias de código real entre TypeScript y JavaScript.

JavaScript:

TypeScript:

Como se puede observar TypeScript cuenta con una inferencia de tipos muy determinista, con lo que en muchos casos se pueden obviar por completo, como es en este caso, donde no hay ninguna diferencia. Axios (https://github.com/axios/axios, es una biblioteca externa, y como muchas otras cuenta con una definición de tipos propia para TypeScript, lo que hace que trabajar con bibliotecas externas facilite mucho el desarrollo. Aunque recordemos que TypeScript y JavaScript son 100% interoperables, con lo que no necesitamos que una biblioteca hecha en JavaScript tenga definición de tipos para poder usarla.

¿Y qué pasa si una biblioteca no tiene oficialmente una definición de tipos? ¿Tendremos que hacerla nosotros? ¿O habrá alguna ya disponbile? En la mayor parte de los casos sí, TypeScript cuenta con un repositorio enorme de definiciones de tipos que son llevadas por la comunidad de aquellos paquetes que no tienen sus propias definiciones. Este repositorio es DefinitelyTyped (https://github.com/DefinitelyTyped/DefinitelyTyped.

JavaScript:

TypeScript:

En este ejemplo vemos que hemos introducido tipos propios de TypeScript. Aunque no diste mucho de JavaScript solamente con introducir los tipos obtenemos todas las ventajas que ofrece el lenguaje. También vemos que en el fichero de TypeScript, en la línea 13, hay un if. Este condicional se ha introducido porque TypeScript al ser fuertemente tipado, muestra un error cuando invocas una función sobre un objeto o propiedad que puede ser nulo. En este caso querySelector puede retornar nulo, con lo cual en tiempo de compilación te ayuda a detectar esta casuística.

JavaScript:

TypeScript:

Con TypeScript podemos modelar los datos, como es el caso de la interface Image, con los que tratamos de tal forma que sabemos con qué elementos estamos trabajando y si estamos equivocados al llamar a una propiedad o método que no existe.

Además, en el primer ejemplo, para saber qué propiedades tiene el listado de imágenes que recuperamos tendríamos que consultar la API o la documentación. TypeScript sirve también como documentación viviente, con lo que aumenta considerablemente la velocidad de desarrollo.

8. Conclusión

TypeScript ofrece unas ventajas sobre JavaScript innegables, el overhead que introduce está más que justificado y la curva de aprendizaje es muy liviana, ya que recordemos, TypeScript no es más que un superset de JavaScript, dando a JavaScript nuevas funcionalidades pero respetando su esencia.

Viewing all 996 articles
Browse latest View live