Weblogs Código

Variable not found

¿Se pueden introducir directivas o lógica de inicialización en todas las vistas y páginas sin duplicar código?

enero 15, 2019 06:12

ASP.NET CoreComo sabemos, los archivos Razor _ViewImports.cshtml y _ViewStart.cshtml se emplean para introducir directivas y código de inicialización para todas las vistas o páginas que se encuentren por debajo en la estructura de carpetas del proyecto.

Es decir, si el archivo _ViewImports.cshtml se encuentra en la carpeta /Views, todas las vistas que hay por debajo heredarán las directivas que hayamos especificado en su interior, de la misma forma que /Pages/_ViewImports.cshtml afectara a las páginas presentes en la carpeta /Pages y descendientes.

Pues bien, hace unos días, un alumno del curso de ASP.NET Core en CampusMVP (que, por cierto, ha sido recientemente actualizado a la versión 2.2 del framework) planteaba una duda sobre un escenario algo más complejo. Si una aplicación tenía vistas en la carpeta /Views, también tenía vistas en áreas (carpeta /Areas), e incluso pudiera tener algunas páginas Razor en /Pages, la pregunta era si existía algún mecanismo para hacer que todas las vistas o páginas definidas en dichas carpetas compartieran directivas (como using o importaciones de tag helpers) sin tener que duplicar este código en sus respectivos _ViewImports.cshtml para cada una de ellas.

A priori pensé que quizás el planteamiento sería retocar ligeramente el motor de vistas, pero, tras estudiarlo un rato, vi que el tema era mucho más sencillo :)

Y es que, aunque no es algo muy intuitivo, resulta que los archivos _ViewImports.cshtml y _ViewStart.cshtml son procesados comenzando por el directorio raíz del proyecto, y no desde carpetas específicas para vistas, como podría suponerse.  Es decir, si se fuera a compilar una vista en /Areas/Dashboard/Views/Home/Index.cshtml, el framework incluirá en ella, sucesivamente, las directivas que encuentre en:
  • /_ViewImports.cshtml (la carpeta raíz del proyecto)
  • /Areas/_ViewImports.cshtml
  • /Areas/Dashboard/_ViewImports.cshtml
  • /Areas/Dashboard/Views/_ViewImports.cshtml
  • /Areas/Dashboard/Views/Home/_ViewImports.cshtml
Y lo mismo ocurre con el archivo _ViewStart.cshtml.

Por tanto, para introducir directivas o código de inicialización Razor de forma global para toda la aplicación, bastará con establecerlas en el raíz del proyecto.

En fin, son esos pequeños detalles que pueden venir bien en algunas ocasiones y es interesante conocer :)

Publicado en Variable not found.

» Leer más, comentarios, etc...

proyectos Ágiles

Cómo desescalar una organización – Un caso real

enero 15, 2019 06:00

En esta presentación se muestra un caso real de estrategia de transformación Agile INTEGRAL donde una de las premisas es el DESESCALADO. Adicionalmente, se identifican cambios clave a realizar como, por ejemplo, pasar de trabajar en formato “proyecto” a ser guiados por objetivos estratégicos, cómo articular una organización alrededor de estos objetivos introduciendo a la vez los necesarios cambios arquitectónicos para hacerlo viable técnicamente y cómo hacer un rediseño cultural para conseguir ownership del “sistema” por parte de la gente.

Para avanzar en toda esta transformación se necesita accionar una gran cantidad de áreas y muchas acciones se realimentan mutuamente. En esta presentación verás:

  • Una estrategia de gestión del cambio específica para Agile y un roadmap real, por qué estados se puede pasar, qué criterios utilizar para priorizar todo el trabajo a realizar.
  • Cuáles son los factores clave para avanzar.
  • Cuáles han sido los valores “no escritos” (o que hay que escribir 🙂 ) para ayudar a la transformación.
  • Qué hacer para que la transformación sea “de todos” (compartida, inclusiva y colaborativa).
  • Cómo hacer tracción desde un departamento online al resto de la empresa.
  • Qué errores puedes cometer y cómo evitarlos.
  • Cómo hacer visibles los resultados, cómo conseguir engagement y buenas conversaciones a todos los niveles (Dirección, técnicos, etc,).

Presentaciones relacionadas

Artículos relacionados:

» Leer más, comentarios, etc...

Poesía Binaria

Cómo utilizar PHP desde contenedores docker tanto de forma local como en producción

enero 14, 2019 09:25

Una de las medidas más importantes que debemos tomar a la hora de realizar proyectos de programación, sobre todo en equipo, es asegurarte de que todos tienen el mismo entorno de trabajo. Si nos referimos a un entorno de aplicaciones en PHP, todos los miembros involucrados deberán tener la misma versión de PHP, las mismas extensiones y la misma configuración del sistema. Además, esas mismas configuraciones deberían ser las mismas en los entornos de test y producción, aunque en producción quitemos las herramientas de debug o depuración. Así, cuando hay un problema, podemos evitar en la medida de lo posible el famoso: “En mi ordenador funciona” cuando al subir cambios al servidor, o al probarlos un compañero, no funcionan.

Tenemos varias opciones al respecto. Podemos utilizar máquinas virtuales con soluciones como Vagrant. Otra opción es utilizar Docker para ejecutar PHP. Incluso podemos ejecutar PHP desde nuestra terminal a través de docker con aplicaciones como composer, o artisan de Laravel.

Dockerfile

Vamos a basarnos en las imágenes oficiales de php, en este caso php7.2. Y vamos a instalar por defecto extensiones como xml, zip, curl, gettext o mcrypt (esta última debemos instalarla desde pecl.
En los contenedores vamos a vincular /var/www con un directorio local, que puede estar por ejemplo, en la $HOME del usuario actual, donde podremos tener nuestros desarrollos. Y, por otro lado, la configuración de PHP también la vincularemos con un directorio fuera del contenedor, por si tenemos que hacer cambios en algún momento. En teoría no deberíamos permitir esto, la configuración debería ser siempre fija… pero ya nos conocemos y siempre surge algo, lo mismo tenemos que elevar la memoria en algún momento, cambiar alguna directiva o alguna configuración extra.

Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
FROM php:7.2-fpm
ARG HOSTUID
ENV BUILD_DEPS="autoconf file gcc g++ libc-dev make pkg-config re2c libfreetype6-dev libjpeg62-turbo-dev libmcrypt-dev libpng-dev libssl-dev libc-client-dev libkrb5-dev zlib1g-dev libicu-dev libldap-dev libxml2-dev libxslt-dev libcurl4-openssl-dev libpq-dev libsqlite3-dev" \
    ETC_DIR="/usr/local/etc" \
    ETC_BACKUP_DIR="/usr/local/etc_backup"

RUN apt-get update && apt-get install -y less \
    procps \
    git \
    && pecl install redis \
    && pecl install xdebug \
    && docker-php-ext-enable redis xdebug \
    && apt-get install -y $BUILD_DEPS \
    && docker-php-ext-install -j$(nproc) iconv \
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
    && docker-php-ext-install -j$(nproc) gd \
    && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \
    && docker-php-ext-install -j$(nproc) imap \
    && docker-php-ext-install -j$(nproc) bcmath \
    && docker-php-ext-install -j$(nproc) calendar \
    && docker-php-ext-install -j$(nproc) exif \
    && docker-php-ext-install -j$(nproc) fileinfo \
    && docker-php-ext-install -j$(nproc) ftp \
    && docker-php-ext-install -j$(nproc) gettext \
    && docker-php-ext-install -j$(nproc) hash \
    && docker-php-ext-install -j$(nproc) intl \
    && docker-php-ext-install -j$(nproc) json \
    && docker-php-ext-install -j$(nproc) ldap \
    && docker-php-ext-install -j$(nproc) sysvshm \
    && docker-php-ext-install -j$(nproc) sysvsem \
    && docker-php-ext-install -j$(nproc) xml \
    && docker-php-ext-install -j$(nproc) zip \
    && docker-php-ext-install -j$(nproc) xsl \
    && docker-php-ext-install -j$(nproc) phar \
    && docker-php-ext-install -j$(nproc) ctype \
    && docker-php-ext-install -j$(nproc) curl \
    && docker-php-ext-install -j$(nproc) dom \
    && docker-php-ext-install -j$(nproc) soap \
    && docker-php-ext-install -j$(nproc) mbstring \
    && docker-php-ext-install -j$(nproc) posix \
    && docker-php-ext-install -j$(nproc) pdo_pgsql \
    && docker-php-ext-install -j$(nproc) pdo_sqlite \
    && docker-php-ext-install -j$(nproc) pdo_mysql \
    && yes | pecl install "channel://pecl.php.net/mcrypt-1.0.1" \
    && { \
    echo 'extension=mcrypt.so'; \
    } > $PHP_INI_DIR/conf.d/pecl-mcrypt.ini \
    && echo "Fin de instalaciones"
COPY docker-entry.sh /usr/local/binx

RUN mv $ETC_DIR $ETC_BACKUP_DIR \
    && chmod +x /usr/local/bin/docker-entry.sh \
    && rm /etc/localtime

RUN useradd -s /bin/bash -d /var/www -u $HOSTUID user

ENTRYPOINT ["/usr/local/bin/docker-entry.sh"]
CMD ["php-fpm"]

También tendremos un archivo docker-entry.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
echo "Iniciando contenedor"

VERSION="7.2"
CONFFILE=/etc/php/$VERSION/fpm/php-fpm.conf
DOCKERIP=$(hostname --ip-address)

if [ $(ls $ETC_DIR | wc -l) -eq 0 ]; then
        echo "Copiando configuración por defecto"
        cp -r "$ETC_BACKUP_DIR"/* "$ETC_DIR"
fi

/usr/local/bin/docker-php-entrypoint $@

Para construir la máquina podemos utilizar esto:

docker build -t myphp7.2-fpm --build-arg HOSTUID=”$(id -u)” --cpuset-cpus=”0-7″ .

Utilizo cpuset-cpus para delimitar los núcleos que vamos a utilizar para compilar los módulos. Esto puede tardar un poco y, si tenemos varios núcleos, puede interesarnos utilizar uno o dos, y mientras se construye PHP, utilizar el ordenador para navegar por Internet o algo así. Yo suelo crear un archivo build.sh con esa misma línea de antes.

Ahora, tendremos unos argumentos a la hora de lanzar el contenedor (run.sh)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
readonly SCRIPTPATH="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
readonly WWWPATH="
$HOME/www"

GATEWAY="
"
if [ -n "
$(which dnsmasq)" ]; then
    if [ -z "
$(pidof dnsmasq)" ]; then
        sudo dnsmasq --bind-interfaces
    fi
    GATEWAY="
--dns $(ip addr show docker0 | grep -Po 'inet \K[\d.]+')"
fi

pushd $SCRIPTPATH > /dev/null
docker run --rm --name myphp7.2-fpm -v /etc/localtime:/etc/localtime:ro -v $WWWPATH:/var/www:rw -v $(pwd)/conf:/usr/local/etc/:rw $GATEWAY --user www-data --cpuset-cpus="
7" -d myphp7.2-fpm

Este archivo podremos reescribirlo dependiendo de nuestra configuración local. En mi ordenador, utilizo dnsmasq como dns en la máquina host, de forma que si modifico mi /etc/hosts, pueda acceder a dichos nombres desde mi contenedor PHP. Además, es conveniente editar en este archivo la variable WWWPATH donde estableceremos la ruta base desde la que tendremos todos nuestros archivos PHP, a partir de la que serviremos con FPM los archivos.

Configurando un servidor web

Este PHP con FPM debemos configurarlo en un servidor web, para ello utilizaremos proxy_fcgi dejando el VirtualHost más o menos así (no he puesto configuración de SSL porque estoy en local, aunque también podríamos configurarla):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<VirtualHost *:80>
    ServerName prueba_de_mi_web.local

    ServerAdmin webmaster@localhost
    Define webpath /prueba_de_mi_web.com
    DocumentRoot /home/gaspy/www/${webpath}
    <Directory /home/gaspy/www/${webpath}/>
            Options +FollowSymLinks
        AllowOverride all
    </Directory>

    <IfModule proxy_fcgi_module>
         ProxyPassMatch "^/(.*\.ph(p[3457]?|t|tml))$" "fcgi://myphp7.2-fpm.docker.local:9000/var/www/${webpath}/$1"
         DirectoryIndex index.html index.php
    </IfModule>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Mi $HOME, en mi ordenador es /home/gaspy/ y en www almaceno todo el código de aplicaciones web. En mi /etc/hosts he hecho una entrada que apunta a la IP del contenedor. Para ello puedo utilizar este script que actualiza /etc/hosts con los dockers que hay en ejecución en este momento.

Ejecución desde línea de comandos

Una operación que realizo casi a diario en PHP es la ejecución de scripts desde la línea de comandos. Ya sea por una operación que se realiza en segundo plano, o ejecutar composer, o comandos propios de frameworks o plataformas como artisan de Laravel, occ de Owncloud, yii de su framework homónimo, etc.
Pero claro, para ejecutar los scripts, tengo que hacerlo desde dentro del contenedor, muchas veces con el usuario local y no como root, y generalmente los archivos a ejecutar se encontrarán en mi directorio local, pero dentro del contenedor estarán en /var/www. Además, tenemos que tener en cuenta que muchas veces ejecutaré código php de forma interactiva, es decir, ejecutando php y escribiendo el código, y otras veces haré algo así:

1
2
<?php
echo "Hola mundo!";

Para ello, tengo un script que ejecutará php (php7.sh):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash
readonly SCRIPTPATH="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"

CURRENT_DIR="
$(pwd)/"
BASE_DIR="
$HOME/www/"
DOCKER_DIR="
/var/www/"
CONTAINER_NAME="
myphp7.2-fpm"

TRANSLATED_DIR="
${CURRENT_DIR/$BASE_DIR/$DOCKER_DIR}"

if [ -z "
$(docker ps | grep $CONTAINER_NAME)" ]; then
    $SCRIPTPATH/run.sh
fi

if [ "
$1" == "-u" ]; then
    shift
        NUID=$(id -u "$1")
    shift
elif [ "
$1" == "-l" ]; then
    shift
    NUID=$UID
fi

if [ -n "
$1" ]; then
    set -- "
${1/$BASE_DIR/$DOCKER_DIR}" "${@:2}"
    QUOTED_ARGS="
$(printf " %q" "$@")"
fi

if [ -n "
$NUID" ]; then
    USER="
$(getent passwd 1000 | cut -f1 -d:)"
    DIR="
${DOCKER_DIR}${USER}"
    if [ ! -d "
${BASE_DIR}$USER" ]; then
        docker exec -u root -i myphp7.2-fpm bash -c "
mkdir "$DIR"; chown $NUID:$NUID "$DIR""
    fi
    docker exec -u "
$NUID:$NUID" -i myphp7.2-fpm bash -c "HOME="$DIR"; cd $TRANSLATED_DIR 2>/dev/null; exec php ${QUOTED_ARGS}"
else
    docker exec -i myphp7.2-fpm bash -c "
cd $TRANSLATED_DIR 2>/dev/null; exec php ${QUOTED_ARGS}"
fi

A este archivo, le podemos crear un enlace dentro de /usr/local/bin/ llamado php para que podamos ejecutarlo desde cualquier sitio:

sudo ln -s $(pwd)/php7.sh /usr/local/bin/php

El script, lo que hace primero es averiguar el directorio actual desde donde ejecutas el script, y transformará dentro de esa cadena la ruta $BASE_DIR (donde están los archivos php en mi ordenador) por la ruta $DOCKER_DIR (donde están los archivos php en el contenedor). De esta forma si, fuera de docker ejecutamos:

php archivo.php

Dicho archivo se buscará en el directorio correspondiente dentro del contenedor. Eso sí, fuera de $BASE_DIR no podremos ejecutar archivos. Adicionalmente podremos ejecutar:

php -u www-data archivo.php

Para ejecutar el archivo.php como el usuario www-data o también
php -l archivo.php

Si queremos ejecutar el archivo php como el usuario actual. Además, este script se encarga de lanzar la máquina docker (con run.sh) si no está en ejecución actualmente.

composer y otros scripts parecidos

Composer utilizará el script anterior de php para ejecutarse. Aunque podemos tener un problema con la salida en color. Así que podemos imponerla. Por otro lado, queremos ejecutar composer como el usuario actual, en lugar del usuario www-data, ya que todo lo que instala será código y el código no debe poder ser sobreescrito por ese usuario (generalmente).

Así que podemos crear este script en /usr/local/composer:

1
2
#!/bin/bash
php -u "$(whoami)" /home/gaspy/www/composer --ansi $@

Algunas posibilidades

Siempre podemos meter la pata en la configuración, por un lado, si queremos recargar la configuración, tendremos que parar el contenedor y reiniciarlo:

docker stop myphp7.2-fpm
php

Como vemos, simplemente cargando php se iniciará el contenedor de nuevo. Además, si hemos metido la pata en la configuración, podemos eliminar los archivos del directorio conf y reiniciar el contenedor para que automáticamente se restaure la configuración por defecto.

También podemos incluir nuestra propia configuración en el Dockerfile para que todo nuestro equipo tenga los mismos archivos de configuración la primera vez nada más construir la máquina.
Foto principal: unsplash-logoLuca Bravo

The post Cómo utilizar PHP desde contenedores docker tanto de forma local como en producción appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 345

enero 14, 2019 07:45

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en: www.variablenotfound.com.

» Leer más, comentarios, etc...

Una sinfonía en C#

Delegate, predicate, Action, Func, métodos anónimos, explicados para mortales.

enero 13, 2019 07:16

No es un tema nuevo ni mucho menos,  sin embargo no siempre es del todo bien comprendido y su correcta comprensión es muy importante ya que muchas otras características del framework se apoyan en los delegados, así que vamos a hablar un poco sobre ellos.

Pero, ¿Qué es un delegado?

La respuesta corta es “un delegado es una referencia a un método”, pero no confundir, no es una referencia como un puntero sino que en realidad un delegado nos permite definir una firma para poder luego pasar un método. Vemos un ejemplo.

var testDelegates = new TestDelegates();
testDelegates.DoSomething();

En este simple código tenemos un objeto testDelegates que tiene un método DoSomething y que hace algo, imaginemos que ese método, por ejemplo, hace una llamada a un servicio que ocurre de manera asíncrona y termina después de un tiempo indeterminado. La llamada se realiza pero queremos que de alguna manera este método nos informe que la ejecución ha finalizado. Bien, una forma de hacerlo sería indicarle a DoSomething  que luego de finalizar su ejecución invoque a otro método y nosotros hagamos algo en consecuencia. Más o menos cómo cuando hacemos una llamada AJAX desde Javascript donde tenemos una forma de ser informados de la finalización de la ejecución de la llamada.

Delegado simple

Para hacer esto necesitamos indicar a DoSomething cuál es el método que queremos que sea invocado al final su ejecución (exitosa o no, en este caso eso no nos preocupa) .NET nos permite hacer esto pero nos obliga de definir siempre la firma del método a ser llamado, es decir, tenemos de alguna manera que decir en DoSomething que podemos informar el final de la ejecución a un método pero con cierta firma (que reciba ciertos parámetros y devuelva cierto tipo de datos) acá es donde entran los delegados en acción, justamente nos permiten definir la firma del método.

public class TestDelegates
{
    // declaración del delegador, sin valor de retorno (void) y sin parámetros
    public delegate void DelegadoSimple();
    public void DoSomething(DelegadoSimple delegado)
    {
        // does something
        // llamar al la función
        delegado();
    }
}

Los primero que hacemos es definir el delegado de modo de que sea visible, como dijimos antes esta será la firma que deberá tener el método que pasaremos como parámetro, entonces lo ponemos en la clase que lo utilizará con visibilidad pública.

Luego ponemos como parámetro el delegado que funciona como un tipo y nos permite tener la referencia a la función.

Luego dentro del código podemos invocar la función, en este caso solo agregando los paréntesis porque no recibe parámetros.

private static void Main(string[] args)
{
    var testDelegates = new TestDelegates();
    testDelegates.DoSomething(FuncionSimple);
}

private static void FuncionSimple()
{
    // hacer algo
}

En cuando a la llamada a DoSomething simplemente agregamos el parámetro que es una función igual que el delegado, que no retorna valor (es void) y no recibe parámetros, notemos que no tiene parámetros porque no la invocamos solo pasamos una referencia.

Métodos anónimos

Qué pasaría si no quisiéramos crear una función con nombre y todo para que sea invocada. En este caso podemos crearla inline, de este modo:

private static void Main(string[] args)
{
    var testDelegates = new TestDelegates();
    // en este caso pasamos un método anónimo
    testDelegates.DoSomething(()=>
    {
        //hacer algo
    });
}

Lo que hicimos es declarar nuestro método en el mismo sitio que invocamos a DoSomething, vamos a analizar la sintaxis.

testDelegates.DoSomething(()=>
{
    //hacer algo
});

Primero tenemos que tenemos en cuenta que es necesario cumplir con el delegado, con lo cual el método anónimo no tiene valor de retorno y no recibe parámetros. entonces lo primero que vemos es que hay dos paréntesis vacíos, justamente porque no recibe parámetros y luego la “flecha” que indica que a continuación viene el cuerpo el método ()=>

Después simplemente tenemos las llaves que limitan el cuerpo del método anónimo y si tuviéramos código estaría por ahí, como el método no retorna valor no hay un return.

Es importante destacar que al ser anónimo (el método no tiene nombre) no puede ser invocado desde otra parte.

Imaginemos que lo que queremos hacer es escribir en la consola, el código quedaría así:

testDelegates.DoSomething(() =>
{
    Console.WriteLine("Ejecución finalizada");
});

Y qué ganamos creando un método anónimos? bien, lo principal es que es posible que no usemos ese método para nada más que para ser invocado por DoSomething, por otro lado tenemos visibilidad de las variables dentro del método donde declaramos el método anónimo, por ejemplo podemos hacer esto:

var testDelegates = new TestDelegates();
var variablePrivada = "valor original";
            // en este caso pasamos un método anónimo
testDelegates.DoSomething(() =>
{
    Console.WriteLine("Ejecución finalizada");
    Console.WriteLine($"El valor de la variable 'variablePrivada' es {variablePrivada}");
    variablePrivada = "valor nuevo";
    Console.WriteLine($"El nuevo valor de la variable 'variablePrivada' es {variablePrivada}");
});

No solamente podemos leer una variable privada del método donde declaramos el método anónimo sino que también podemos modificarla, algo similar a un closure.

Métodos anónimos con parámetros

public delegate void DelegadoSimple(int milisegundos);
public void DoSomething(DelegadoSimple delegado)
{
    // does something
    // llamar al la función
    delegado(20);
}

Primero cambiamos la definición del método anónimo para que reciba parámetros, en este caso el tiempo que demoró la ejecución de nuestro método DoSomething, esta vez cuando lo invocamos le pasamos el valor.

private static void Main(string[] args)
{
    var testDelegates = new TestDelegates();
    testDelegates.DoSomething(FuncionSimple);
}

private static void FuncionSimple(int milisegundos)
{
    Console.WriteLine("Ejecución finalizada");
    Console.WriteLine($"La ejecución tomó {milisegundos} milisegundos");
}

Modificamos el método para que reciba el parámetro y al ser ejecutada lo mostramos en pantalla, si lo hiciéramos anónimos sería así:

private static void Main(string[] args)
{
    var testDelegates = new TestDelegates();
    testDelegates.DoSomething((int milisegundos) =>
    {
        Console.WriteLine("Ejecución finalizada");
        Console.WriteLine($"La ejecución tomó {milisegundos} milisegundos");
    });
}

en este caso agregamos en parámetro dentro de los paréntesis.

¿Y qué pasa si tenemos que devolver valores en nuestros delegados?

Por supuesto que podemos tener un delegado que retorne un valor de cualquier tipo, un ejemplo sería el siguiente:

public delegate string DelegadoSimple(int milisegundos);
public void DoSomething(DelegadoSimple delegado)
{
    // does something
    // llamar al la función
    Console.Write(delegado(20));
}

Ahora cambiamos la declaración del delegado para que retorne un string y al invocar el delegado lo imprimimos, en código de la otra parte sería así:

testDelegates.DoSomething((int milisegundos) =>
{
    Console.WriteLine("Ejecución finalizada");
    return $"La ejecución tomó {milisegundos} milisegundos";
});

Muy sencillo.

Delegados predefinidos.

Existen algunos delegados predefinidos que nos permiten evitar declarar todo el tiempo los propios, lo más comunes son:

  • Predicate
  • Action
  • Func

El predicate se usa en muchos métodos de filtrado de colecciones, por ejemplo en la lista genérica:

public T Find(Predicate match)

Y si vemos la definición de predicate podemos comprender bien de qué se trata

public delegate bool Predicate(T obj)

Un predicate sirve para proveer al método Find de una filtro personalizado, es decir, definir nosotros cómo queremos filtrar los datos, vamos a ver el código interno del método Find

public T Find(Predicate match) {
	if( match == null) {
		ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
	}
	Contract.EndContractBlock();

	for(int i = 0 ; i < _size; i++) {
		if(match(_items[i])) {
			return _items[i];
		}
	}
	return default(T);
}

El predicate es la variable match y vemos que lo que hace este método Find es recorrer todos los elementos de la lista y si match es true, retorna el elemento que coincide. Esto nos demuestra la utilidad de los delegados, podemos nosotros definir el criterio de búsqueda.

Existen muchos otros métodos en la clase List<T> que aceptan un predicate y en otras clases también.

Action y Func

Action y Func son más simple, si miramos su definición nos damos cuenta para qué sirven

public delegate void Action();
public delegate void Action(T obj);
public delegate TResult Func();
public delegate TResult Func(T arg);

En el caso de Action es una delegado pre-definido que nos permite definir “acciones” es decir, métodos que no devuelven un valor, el ejemplo del principio lo podríamos haber hecho con Action.

public void DoSomething(Action delegado)
{
    delegado();
}

Lo mismo que si quisiéramos pasar uno o varios parámetros

public void DoSomething(Action delegado)
{
    delegado(20);
}

Existen muchas sobre cargas genéricas para poder pasar hasta 16 parámetros.

En el caso de Func es como un Action pero que devuelve un valor, entonces nuestro código final usando un Func quedaría así:

public void DoSomething(Func delegado)
{
    Console.WriteLine(delegado(20));
}

Simplemente Action y Func no son más que delegados pre-definidos que nos ahorran crear los nuestros en la mayor parte de los casos.

Es importante comprender bien los delegados porque están por todas partes, sobre todo en Linq, todo lo que sea asíncrono como HttpClient o lo relacionado con Threads.

Nos leemos.

» Leer más, comentarios, etc...

Blog Bitix

Compilar el código fuente y ejecutar con los comandos javac, java y jar en Java 8 o anteriores

enero 13, 2019 01:00

Java

Aún recuerdo cuando empecé a programar con el lenguaje Java sobre el año 1997 que la compilación y ejecución del código la hacía manualmente con los comandos javac, java y jar en un máquina Intel Pentium a 120 Mhz con tan solo 8 MiB, más tarde 32 MiB, con Windows 95 y Java 1.2, momento en el que ni siquiera había un IDE ni las herramientas de construcción modernas como Gradle, había que descargar manualmente las librerías de dependencias en forma de archivos jar que se requiriesen. Luego con JBuilder como IDE este se encargaba de realizar la compilación y ejecución y no hacía falta utilizar estos comandos directamente.

Ahora con herramientas como Gradle además de compilar y ejecutar el programa incluso las dependencias son descargadas de forma automática de repositorios donde se ubican versionadas incluso de forma transitiva, descargando las dependencias de las dependencias.

Usar estos dos comandos directamente ya no es necesario pero como curiosidad comentaré como es su uso. El comando javac sirve para compilar los archivos de código fuente, dado que los paquetes del código fuente de Java se corresponden con directorios en el sistema de archivos el código fuente se ha de ubicar de forma consistente entre la estructura de directorio y el código fuente. Suponiendo que que hay las siguientes clases que hacen uso de la librería log4j2 y están ubicadas en el directorio src/main/java con la misma convención que utiliza Gradle el comando para realizar la compilación y copiar los recursos es el siguiente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ tree
.
├── jar.sh
├── javac.sh
├── java-jar.sh
├── java.sh
├── libraries
│   ├── log4j-api-2.11.1.jar
│   └── log4j-core-2.11.1.jar
├── src
│   └── main
│   ├── java
│   │   └── io
│   │   └── github
│   │   └── picodotdev
│   │   └── blogbitix
│   │   └── java8
│   │   └── helloworld
│   │   └── Main.java
│   ├── misc
│   │   └── MANIFEST.MF
│   └── resources
│   └── log4j2.xml
└── target
└── classes
14 directories, 9 files
1
2
3
#!/usr/bin/env bash
javac -classpath "libraries/*" -sourcepath src/main/java -source 1.8 -target 1.8 -d target/classes src/main/java/io/github/picodotdev/blogbitix/java8/helloworld/Main.java
cp -r src/main/resources/* target/classes
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package io.github.picodotdev.blogbitix.java8.helloworld;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class Main {
private static Logger logger = LogManager.getLogger(Main.class);
public static void main(String[] args) {
logger.info("Arguments: {}", Arrays.asList(args).stream().collect(Collectors.joining(", ")));
}
}

Con el parámetro -classpath se indica la ubicación de las librerías o dependencias que requiriere el código fuente, con el parámetro -sourcepath el directorio raíz de los archivos de código fuente, el parámetro -source indica la versión del lenguaje del código fuente, -target la versión de la máquina virtual del bytecode que generará el compilador y con el parámetro -d el directorio donde generan los archivos class con el bytecode.

Una vez generados los archivos de bytecode a partir de la compilación del código fuente su ejecución se realiza con el comando java donde hay que indicar las ubicaciones del los archivos class y las librerías jar necesarias que necesiten, la clase principal con el punto de entrada del programa que contenga un método public static void main(String[] args) y los parámetros del programa que se reciben en el parámetro args del método main.

1
2
#!/usr/bin/env bash
java -classpath "target/classes:libraries/*" io.github.picodotdev.blogbitix.java8.helloworld.Main "$@"
1
2
$ ./java.sh arg1 arg2 arg3
16:13:45.386 [main] INFO io.github.picodotdev.blogbitix.java8.helloworld.Main Arguments: arg1, arg2, arg3

La distribución de los archivos class se suele realizar usando librerías jar y estas se construyen usando el comando jar. El archivo de manifiesto es un descriptor en el que se puede indicar la clase de entrada sin tener que especificarla en el comando java haciendo los archivo jar similar a un ejecutable.

1
2
#!/usr/bin/env bash
jar cvfm holamundojava8.jar src/main/misc/MANIFEST.MF -C target/classes .
1
2
3
Main-Class: io.github.picodotdev.blogbitix.java8.helloworld.Main
Class-Path: libraries/log4j-api-2.11.1.jar libraries/log4j-core-2.11.1.jar

Y la ejecución de del programa contenido en el archivo jar.

1
2
#!/usr/bin/env bash
java -jar holamundojava8.jar "$@"
1
2
$ ./java-jar.sh arg1 arg2 arg3
16:20:33.848 [main] INFO io.github.picodotdev.blogbitix.java8.helloworld.Main Arguments: arg1, arg2, arg3

Así es la compilación y ejecución de código Java en Java 8 y anteriores, con la introducción de la modularidad a partir de Java 9 esto cambia ya que el classpath queda obsoleto y es reemplazado por el equivalente con módulos module-path.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

» Leer más, comentarios, etc...

Blog Bitix

Acceder a hojas de cálculo de Google Docs mediante API desde una aplicación Java

enero 12, 2019 05:30

Java
Google

Google ofrece numerosos productos de desarrollo que permiten automatizar tareas e realizar integración con servicios, aplicaciones y documentos de Google creando un programa con un lenguaje de programacion. Las API que ofrece Google desde Drive, Sheets, Sides, GMail, Calendar, Contacts, Street View, AdSense, Analytics, Youtube, Speech y muchos más.

Para acceder a los servicios mediante APIs hay que obtener unas credenciales. Un ejemplo es el siguiente usando un API key para acceder a una hoja de cálculo compartida para cualquier usuario que tenga el enlace o identificativo del documento en Google Drive. Cada servicio de Google ofrece una API distinta según su contexto y datos que maneja.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.picodotdev.javagoogleapi;
...
public class Main {
...
private static Credential getCredentialsApiKey() {
return new GoogleCredential().createScoped(SCOPES);
}
...
public static void main(String... args) throws IOException, GeneralSecurityException {
NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
Sheets service = new Sheets.Builder(httpTransport, jsonFactory, getCredentialsApiKey()).setApplicationName(APPLICATION_NAME).build();
...
}
}

Las hojas de cálculo se utilizan para contener información, son fácilmente editables por los usuarios y se convierten casi en una forma de base de datos. Con las APIs que ofrece Google para Spreadsheets esta información es utilizable en una aplicación, un buen caso de uso es aquel en el que ciertos datos o parámetros potencialmente cambian cada cierto tiempo o según reglas de negocio. Por ejemplo, se puede crear una hoja de cálculo con los precios, descripciones, existencias, disponibilidad o gastos de envío de los productos e importar esta información en la base de datos de una aplicación usando una API de Google, en vez de crear una aplicación backoffice de edición a medida para editar esa información, la aplicación consistiría en procesar el documento e insertar su información en la base de datos.

Como contrapartida de estas integraciones hay que tener en cuenta que una aplicación se hace dependiente del servicio los servicios de Google que utilice, hay que evaluar si esta dependencia es deseable.

Otro posible aplicación es utilizar documentos de texto en Google Drive como plantillas de correos electrónicos, se permite una edición sencilla y posteriormente se importan en la aplicación para que los utilice. A un documento de Google Drive se accede mediante esta petición HTTP GET. Las hojas de cálculo tamibén están disponibles mediante una interfaz REST sin embargo usando las APIs que ofrece Google para cada lenguaje es más cómodo que tratar con los datos en crudo en formato JSON.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ curl -X GET "https://sheets.googleapis.com/v4/spreadsheets/1JhBPGW4F.../values/Hoja1?key=AIzaSyDGwW..."
{
"range": "Hoja1!A1:AA1000",
"majorDimension": "ROWS",
"values": [
[
"a1",
"a3"
],
[
"a2",
"a4"
]
]
}
1
2
3
4
$ curl -X GET "https://www.googleapis.com/drive/v3/files/1YCnD37w6p.../export?key=AIzaSyDGwW...&mimeType=text/plain"
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Para las hojas de cálculo hay dos formas de autorización para una aplicación. Mediante una API key con permisos de utilización de la API permite acceder a cualquier documento público, compartido de forma pública o para los usuarios que tengan el enlace o identificativo del documento. La otra más segura es creando una cuenta de servicio de forma que el documento se comparta únicamente con esa cuenta de servicio como si de cualquier otro usuario se tratase en vez de hacerlo público o para cualquiera que tenga el enlace.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.github.picodotdev.javagoogleapi;
...
public class Main {
...
private static final String CREDENTIALS_FILE_PATH = "/blogbitix-119471bc8ebf.json";
...
private static Credential getCredentialsServiceAccount(NetHttpTransport httpTransport, JsonFactory jsonFactory) throws IOException {
InputStream in = Main.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
return GoogleCredential.fromStream(in, httpTransport, jsonFactory).createScoped(SCOPES);
}
public static void main(String... args) throws IOException, GeneralSecurityException {
NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
Sheets service = new Sheets.Builder(httpTransport, jsonFactory, getCredentialsServiceAccount(httpTransport, jsonFactory)).setApplicationName(APPLICATION_NAME).build();
...
}
}

Laa API key se crean en la página de Credenciales para lo que previamente hay que crer un proyecto. Para leer el documento hay que compartirlo al menos para cualquiera que tenga acceso al enlace, al compartirlo se especifica si se hace en modo solo lectura o con permisos de ecritura.

Google Credentials, API Key y compartir documento

El enlace al compartir el documento o al editarlo contiene el identificativo de documento. Con la API key o cuenta de servicio, el identificativo del documento y el documento compartido al menos para cualquiera que tenga el enlace la información del documento está accesible para una aplicación mediante una API REST o de forma programática con una implementación de la API con Java, este programa Java imprime el contenido de las celdas de la hoja de cálculo en la terminal. Se necesita una expresión que identifique la hoja y el contenido de las celdas de las que se quieren datos con un rango en notación A1.

Hoja de cálculo y documento de Google
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.github.picodotdev.javagoogleapi;
...
public class Main {
...
public static void main(String... args) throws IOException, GeneralSecurityException {
...
Sheets service = new Sheets.Builder(httpTransport, jsonFactory, getCredentialsApiKey()).setApplicationName(APPLICATION_NAME).build();
ValueRange response = service.spreadsheets().values().get(SPREADSHEET_ID, RANGE).setKey(API_KEY).execute();
List<List<Object>> values = response.getValues();
if (values == null || values.isEmpty()) {
System.out.println("No data found.");
return;
}
for (List<Object> row : values) {
System.out.println(row.stream().map(Object::toString).collect(java.util.stream.Collectors.joining(", ")));
}
}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ ./gradlew run
> Task :compileJava
> Task :processResources UP-TO-DATE
> Task :classes
> Task :run
a1, a3
a2, a4

BUILD SUCCESSFUL in 1s
3 actionable tasks: 2 executed, 1 up-to-date

El método de API key obliga a hacer público el documento lo que no es deseable desde el punto de vista de seguridad aunque es un poco más simple que crear una cuenta de servicio. Para no hacer público el documento pero permitir acceder a una aplicación hay que crear una cuenta de servicio en la página Cuentas de servicio seleccionando o creando un proyecto.

Cuenta de servicio de Google

Al crear una cuenta de servicio y una clave se genera un archivo en formato JSON con las credenciales que hay que guardar y utilizar en una aplicación para acceder a los documentos compartidos con esta cuenta de servicio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
"type": "service_account",
"project_id": "blogbitix",
"private_key_id": "119471bc8...",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFQpNVknb3bRp9\n...\n-----END PRIVATE KEY-----\n",
"client_email": "blogbitix@blogbitix.iam.gserviceaccount.com",
"client_id": "110222042...",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/blogbitix%40blogbitix.iam.gserviceaccount.com"
}

En vez de compartir el documento con cualquiera que tenga en enlace, con una cuenta de servicio el documento se puede compartir únicamente con esa cuenta de servicio, la cuenta de servicio posee un correo electrónico que la identifica, el documento se puede compartir únicamente con esta cuenta de servicio como si de cualquier otro usuario se tratase.

Documento compartido con cuenta de servicio

El siguiente código Java accede a un documento utilizando las credenciales de una cuenta de servicio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package io.github.picodotdev.javagoogleapi;
....
public class Main {
private static final String APPLICATION_NAME = "JavaGoogleApi";
private static final String API_KEY = "AIzaSyDBZ...";
private static final String CREDENTIALS_FILE_PATH = "/blogbitix-119471bc8ebf.json";
private static final String SPREADSHEET_ID = "1JhBPGW4F...";
private static final String RANGE = "Hoja1";
private static final List<String> SCOPES = Collections.singletonList(SheetsScopes.SPREADSHEETS_READONLY);
private static Credential getCredentialsApiKey() {
return new GoogleCredential().createScoped(SCOPES);
}
private static Credential getCredentialsServiceAccount(NetHttpTransport httpTransport, JsonFactory jsonFactory) throws IOException {
InputStream in = Main.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
return GoogleCredential.fromStream(in, httpTransport, jsonFactory).createScoped(SCOPES);
}
public static void main(String... args) throws IOException, GeneralSecurityException {
NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
Sheets service = new Sheets.Builder(httpTransport, jsonFactory, getCredentialsServiceAccount(httpTransport, jsonFactory)).setApplicationName(APPLICATION_NAME).build();
ValueRange response = service.spreadsheets().values().get(SPREADSHEET_ID, RANGE).setKey(API_KEY).execute();
List<List<Object>> values = response.getValues();
if (values == null || values.isEmpty()) {
System.out.println("No data found.");
return;
}
for (List<Object> row : values) {
System.out.println(row.stream().map(Object::toString).collect(java.util.stream.Collectors.joining(", ")));
}
}
}

En el caso de Java hay que incluir la dependencia que proporciona la implementación de la API de Google Spreadsheets para Java como se muestra usando Gradle.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apply plugin: 'java'
apply plugin: 'application'
mainClassName = 'io.github.picodotdev.javagoogleapi.Main'
version = '1.0'
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.apis:google-api-services-sheets:v4-rev553-1.25.0'
}

Google ofrece un explorador para probar las peticiones y permisos de los documentos o explorar cualquier otra API de Google por ejemplo esta de Drive para exportar un documento en un formato determinado directamente desde una web sin tener que crear una aplicación, también se pueden hacer peticiones desde las páginas de documentación.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

» Leer más, comentarios, etc...

Fixed Buffer

Reconociendo a personas mediante Azure FaceAPI

enero 12, 2019 12:40

Azure

Han pasado ya las navidades, y toca la vuelta al trabajo, la vuelta a los viajes… y gracias a mi último viaje estuve cenando con un amigo, y me contó un proyecto que se trae entre manos, el cual requería del reconocimiento facial como parte de la identificación inequívoca de usuarios. En su momento, leí sobre los Cognitive Services de Azure, pero nunca me había aventurado a probarlos, y bueno, ya sabéis que soy curioso, así que en cuanto he tenido un momento, me he puesto a probarlo, ¡y la verdad es que ha sido realmente fácil! Vamos con ello:

Crear el servicio Azure FaceAPI

Lo primero que necesitamos, es tener una cuenta en Azure, no es necesario que sea de pago (aunque para confirmar la cuenta nos piden una tarjeta de crédito, no se efectúa ningún cargo).

Una vez que la tenemos, vamos a crear el servicio:

CrearServicio

Para ello, con eso, nos muestra una nueva ventana donde poner nuestros datos:

NombreServicio

En ella, basta con que le indiquemos el nombre, que suscripción queremos utilizar, donde queremos que este el servidor, el plan de precios (OJO!!, yo utilizo “S0” porque estoy haciendo otra prueba más en profundidad y tengo el “F0” ocupado, pero el gratuito es “F0”) y por último nos pide el grupo de recursos al que queremos que pertenezca (si no tenemos ningún grupo, lo podemos crear fácilmente con el botón “Crear nuevo”).

Con este paso, pulsamos sobre “Crear”, y como podemos ver en el dashboard, ya tenemos nuestro servicio creado:

dashboard

Solo nos queda conocer la url que debemos utilizar, y crear las claves para poder usarla, para eso, basta con pulsar sobre el servicio para ver su configuración:

overview

He indicado en amarillo la url de endpoint que tendremos que indicarle a la API en nuestro programa, en mi caso es:
https://westeurope.api.cognitive.microsoft.com/face/v1.0

Justo debajo, tenemos el botón para gestionar nuestras claves de acceso, lo que lanza una ventana donde nos las muestra:

keys

Debemos apuntarlas (o volver luego y consultarlas) al igual que la url del endpoint, ya que son datos que le vamos a tener que pasar a la API desde nuestro programa.

Creando nuestro programa

Para poder consumir nuestro servicio Azure FaceAPI, vamos a utilizar una aplicación de formularios en .Net Framework, pero por supuesto, podemos utilizarlo en nuestro proyecto NetCore (pero esta vez, la librería cambia de nombre). Para ello, creamos nuestro proyecto de formularios, y añadimos el paquete:

PM->Install-Package Microsoft.ProjectOxford.Face -v 1.4.0

Si estuviésemos en NetCore, por ejemplo en nuestra web ASP, el paquete seria:

PM->Install-Package Microsoft.ProjectOxford.Face.DotNetCore -Version 1.1.0

Al ser un código extenso, pongo el enlace para descargarlo desde GitHub, y aquí solo vamos a ver las partes claves de la API:

 
var faceServiceClient = new FaceServiceClient("Tu Key", "Url de la API");

Crearemos una instancia del cliente de la API, que luego utilizaremos.

 
var groups = await faceServiceClient.ListPersonGroupsAsync();

Listamos los grupos que ya existan creador previamente.

 
await faceServiceClient.DeletePersonGroupAsync(group.PersonGroupId);

Borramos un grupo.

 
await faceServiceClient.CreatePersonGroupAsync(GroupGUID, "FixedBuffer");

Creamos un grupo.

 
var personResult = await faceServiceClient.CreatePersonAsync(GroupGUID, personName);

Creamos una persona dentro del grupo.

 
var persistFace = await faceServiceClient.AddPersonFaceInPersonGroupAsync(GroupGUID, personResult.PersonId, fStream, file.FullName);

Añadimos una foto de training a la persona.

 
await faceServiceClient.TrainPersonGroupAsync(GroupGUID);

Hacemos el entrenamiento del grupo.

 
var faces = await faceServiceClient.DetectAsync(fStream);

Detectamos las caras de la imagen.

 
var results = await faceServiceClient.IdentifyAsync(GroupGUID, faces.Select(ff => ff.FaceId).ToArray());

Identificamos las caras dentro de la gente del grupo.

 
var person = await faceServiceClient.GetPersonAsync(GroupGUID, result.Candidates[0].PersonId);

Obtenemos a la persona por Guid.

Como se puede ver, la API nos provee de una manera de funcionar bastante lógica.
En primer lugar, tenemos que crear un grupo de personas, después añadir personas a ese grupo, añadir caras a esas personas, y después entrenar el grupo. Esto solo es necesario hacerlo una vez para entrenar el grupo, con eso, podemos pasar a identificar a personas.

Para identificar, los pasos son también sencillos, en primer lugar, detectar las caras en la imagen, en segundo lugar, detectar a quien puede pertenecer esa cara, y en último lugar (en caso de no haberlo almacenado locamente), obtener a la persona a quien pertenece esa cara.

Código completo:

 
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Configuration;
using System.Windows.Forms;
using Microsoft.ProjectOxford.Face;
using System.IO;

namespace PostAzureFaceAPI
{
    public partial class MainForm : Form
    {
        string FaceAPIKey = ConfigurationManager.AppSettings["FaceAPIKey"];
        string FaceAPIEndPoint = ConfigurationManager.AppSettings["FaceAPIEndPoint"];
        string GroupGUID = Guid.NewGuid().ToString();

        public MainForm()
        {
            InitializeComponent();
        }

        private async void btn_Train_Click(object sender, EventArgs e)
        {
            //Abrimos un dialogo de seleccion de carpetas
            FolderBrowserDialog dialog = new FolderBrowserDialog();
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                //Si se ha seleccionado un directorio, hacemos su info
                DirectoryInfo directory = new DirectoryInfo(dialog.SelectedPath);

                //Comprobamos que el directorio tiene carpetas de personas
                if (directory.GetDirectories().Count() == 0)
                    return;

                //=====Empezamos a crear el grupo de trabajo
                //Creamos el cliente
                var faceServiceClient = new FaceServiceClient(FaceAPIKey, FaceAPIEndPoint);

                //Vamos a trabajar desde 0 siempre, asi que comprobamos si hay grupos, y si los hay los borramos
                var groups = await faceServiceClient.ListPersonGroupsAsync();
                foreach (var group in groups)
                {
                    await faceServiceClient.DeletePersonGroupAsync(group.PersonGroupId);
                }
                //Creamos un grupo
                await faceServiceClient.CreatePersonGroupAsync(GroupGUID, "FixedBuffer");

                foreach (var person in directory.GetDirectories())
                {
                    //Comprobamos que tenga imagenes
                    if (person.GetFiles().Count() == 0)
                        return;

                    //Obtenemos el nombre que le vamos a dar a la persona
                    var personName = person.Name;

                    lbl_Status.Text = $"Entrenando a {personName}";

                    //Añadimos a una persona al grupo
                    var personResult = await faceServiceClient.CreatePersonAsync(GroupGUID, personName);

                    //Añadimos todas las fotos a la persona
                    foreach (var file in person.GetFiles())
                    {
                        using (var fStream = File.OpenRead(file.FullName))
                        {
                            try
                            {
                                //Cargamos la imagen en el pictureBox
                                pct_Imagen.Image = new Bitmap(fStream);
                                //Reiniciamos el Stream
                                fStream.Seek(0, SeekOrigin.Begin);
                                // Actualizamos las caras en el servidor
                                var persistFace = await faceServiceClient.AddPersonFaceInPersonGroupAsync(GroupGUID, personResult.PersonId, fStream, file.FullName);
                            }
                            catch (FaceAPIException ex)
                            {
                                lbl_Status.Text = "";
                                MessageBox.Show($"Imposible seguir, razón:{ex.ErrorMessage}");
                                return;
                            }
                        }
                    }
                }

                try
                {
                    //Entrenamos el grupo con todas las personas que hemos metido
                    await faceServiceClient.TrainPersonGroupAsync(GroupGUID);

                    // Esperamos a que el entrenamiento acabe
                    while (true)
                    {
                        await Task.Delay(1000);
                        var status = await faceServiceClient.GetPersonGroupTrainingStatusAsync(GroupGUID);
                        if (status.Status != Microsoft.ProjectOxford.Face.Contract.Status.Running)
                        {
                            break;
                        }
                    }

                    //Si hemos llegado hasta aqui, el entrenamiento se ha completado
                    btn_Find.Enabled = true;
                    lbl_Status.Text = $"Entrenamiento completado";
                }
                catch (FaceAPIException ex)
                {
                    lbl_Status.Text = "";
                    MessageBox.Show($"Response: {ex.ErrorCode}. {ex.ErrorMessage}");
                }
                GC.Collect();
            }
        }

        private async void btn_Find_Click(object sender, EventArgs e)
        {
            lbl_Status.Text = "";
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.DefaultExt = ".jpg";
            dialog.Filter = "Image files(*.jpg, *.png, *.bmp, *.gif) | *.jpg; *.png; *.bmp; *.gif";
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                var imagePath = dialog.FileName;

                //Creamos el cliente
                var faceServiceClient = new FaceServiceClient(FaceAPIKey, FaceAPIEndPoint);

                using (var fStream = File.OpenRead(imagePath))
                {
                    //Cargamos la imagen en el pictureBox
                    pct_Imagen.Image = new Bitmap(fStream);
                    //Reiniciamos el Stream
                    fStream.Seek(0, SeekOrigin.Begin);

                    try
                    {
                        //Detectamos las caras
                        var faces = await faceServiceClient.DetectAsync(fStream);

                        //Detectamos a las personas
                        var results = await faceServiceClient.IdentifyAsync(GroupGUID, faces.Select(ff => ff.FaceId).ToArray());

                        //Creamos una lista de caras y nombres asociados
                        List<(Guid, string)> detections = new List<(Guid, string)>();
                        foreach (var result in results)
                        {
                            //En caso de no haber encontrado un candidato, nos lo saltamos
                            if (result.Candidates.Length == 0)
                                continue;
                            var faceId = faces.FirstOrDefault(f => f.FaceId == result.FaceId).FaceId;
                            //Consultamos los datos de la persona detectada
                            var person = await faceServiceClient.GetPersonAsync(GroupGUID, result.Candidates[0].PersonId);

                            //Añadimos a la lista la relacion
                            detections.Add((faceId,person.Name));            
                        }

                        var faceBitmap = new Bitmap(pct_Imagen.Image);

                        using (var g = Graphics.FromImage(faceBitmap))
                        {                           

                            var br = new SolidBrush(Color.FromArgb(200, Color.LightGreen));

                            // Por cada cara reconocida
                            foreach (var face in faces)
                            {
                                var fr = face.FaceRectangle;
                                var fa = face.FaceAttributes;

                                var faceRect = new Rectangle(fr.Left, fr.Top, fr.Width, fr.Height);
                                Pen p = new Pen(br);
                                p.Width = 50;
                                g.DrawRectangle(p, faceRect);                                

                                // Calculamos la posicon del rectangulo
                                int rectTop = fr.Top + fr.Height + 10;
                                if (rectTop + 45 > faceBitmap.Height) rectTop = fr.Top - 30;

                                // Calculamos las dimensiones del rectangulo                     
                                g.FillRectangle(br, fr.Left - 10, rectTop, fr.Width < 120 ? 120 : fr.Width + 20, 125);

                                //Buscamos en la lista de relaciones cara persona
                                var person = detections.Where(x => x.Item1 == face.FaceId).FirstOrDefault();
                                var personName = person.Item2;
                                Font font = new Font(Font.FontFamily,90);
                                //Pintamos el nombre en la imagen
                                g.DrawString($"{personName}",
                                             font, Brushes.Black,
                                             fr.Left - 8,
                                             rectTop + 4);
                            }
                        }
                        pct_Imagen.Image = faceBitmap;
                    }
                    catch (FaceAPIException ex)
                    {

                    }
                }
            }
        }
    }
}

Para poder utilizar el código, debemos modificar el App.config cambiando las claves:

 
  <appSettings> 
    <add key="FaceAPIKey" value=""/> 
    <add key="FaceAPIEndPoint" value=""/> 
  </appSettings> 

Por las que hemos obtenido al crear el servicio. Como se puede ver en el funcionamiento, la carga se hace desde seleccionando una carpeta, que a su vez tenga dentro una carpeta por cada persona que queramos entrenar y que se llame como la persona, será dentro de cada una de estas carpetas donde metamos las fotos de cada persona:

train

Una vez que hayamos completado el entrenamiento, podremos seleccionar una imagen en la que identificar a la persona:

resultado

Como hemos podido ver, es muy sencillo empezar a utilizar la API de reconocimiento e identificación facial. En próximas entradas, seguiremos profundizando en los Cognitive Services de Azure, tanto conociendo el resto de los servicios, como profundizando en este, que ofrece muchas más opciones aparte de la identificación de personas.

**La entrada Reconociendo a personas mediante Azure FaceAPI se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 344

enero 09, 2019 07:50

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

    Otros

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Variable not found

    Top Posts 2018 en Variable Not Found

    enero 08, 2019 08:04

    Top Posts 2018 en Variable Not Found Desde hace una década, el primer post de enero siempre lo reservo para revisar qué publicaciones del blog han sido las más visitadas del año recién finalizado, así que vamos a continuar con esta arraigada tradición una vez más ;)

    También, como no podía ser de otra forma, aprovecho también para desearos a todos un año 2019 lleno de alegrías tanto en el terreno personal como en el profesional, deseo que podéis considerar extensible para a todos los que os rodean y os importan :)

    Y dicho esto, vamos al tema...

    Top ten 2018 en Variable not found

    Comenzando por el final, en décima posición encontramos el post donde echábamos un vistazo al "filtro [ApiController] en ASP.NET Core MVC, una novedad presentada con ASP.NET Core 2.1, que promete acompañarnos durante mucho tiempo gracias a la introducción de convenciones bastante razonables para los desarrolladores de APIs web.

    La novena página más visitada es un artículo de reflexión y autoayuda titulado "Mi controlador tiene muchos parámetros en el constructor, ¿estoy haciendo algo mal?" ;) Es una pregunta que seguro muchos nos hemos hecho al ver que nuestro código desprende un cierto tufillo, y en este post vemos cómo detectarlo y eliminar estos síntomas aromáticos que pueden indicar que algo no va bien.

    ¿Qué podría salir mal?¿Cómo? ¿Que a estas alturas no comparo con null de forma totalmente correcta? Pues eso habéis debido pensar muchos al leer este post, el octavo más leído, donde se muestra un escenario en el que la comparación con doble igualdad del tipo x==null no se comportaría como esperábamos. Aunque se trate de un caso extremo, creo que vale la pena conocer cómo es posible que esto ocurra.

    En séptima posición como contenido más visitado, encontramos cómo incluir scripts en la página desde vistas parciales ASP.NET Core MVC con DynamicSections, la presentación de una mis pequeñas contribuciones a la humanidad en forma de paquete NuGet, que permite registrar bloques de script desde cualquier punto del proceso de una petición (renderizado de parciales, filtros, controladores...) e incluirlos finalmente en la página generada. Algo parecido a los bloques @section de Razor, pero resuelto de tiempo de ejecución, de forma dinámica.

    Ah, las redirecciones HTTP, esas grandes desconocidas ;) Aunque todos hemos utilizado cientos de veces los códigos de estado HTTP 301 y 302 para redirigir al cliente a nuevas URL, parece que no nos sonaban tanto los estados HTTP 303, 307 y 308, códigos definidos en el estándar HTTP que aportan mayor riqueza al resultado. ¿Los conocíais?

    A continuación, otro post que ha despertado bastante interés es el dedicado al estándar Problem details (RFC7807), que describe cómo retornar errores desde APIs HTTP de forma normalizada, y veíamos su uso en ASP.NET Core. Creo que, aunque sólo sea para pillar algunas ideas, vale la pena echarle el vistazo.

    En el tercer y cuarto puesto encontramos posts que resumían, respectivamente, las novedades llegadas con las últimas actualizaciones del framework, ASP.NET Core 2.1 y ASP.NET Core 2.2. Es bueno saber que a los desarrolladores nos gusta estar al tanto de estas cosas :)

    En segunda posición, un post donde trataba la extensión de claims de usuarios en ASP.NET Core, un interesante mecanismo que permite aligerar el peso de los tokens JWT o cookies permitiendo la modificación manual del Principal asociado a las peticiones. Sin duda, algo que vale la pena conocer si utilizáis este tipo de autenticación en vuestras aplicaciones.

    And the winner is... sorprendentemente, el post más visitado de este pasado año es "¿Se pueden desarrollar aplicaciones ASP.NET Core con Visual Basic .NET?", una prueba de que este lenguaje sigue manteniendo su tirón y hay muchos usuarios interesados en utilizarlo en aplicaciones ASP.NET Core, o al menos en conocer su futuro. Amigos de Microsoft: si estáis leyendo esto, tomad nota y haced algo al respecto, anda ;)

    Como mención especial, creo que vale la pena citar al ya famoso operador virgulilla de C#, que aunque no ha llegado a situarse dentro del top ten, se ha quedado bastante cerca y sobre todo teniendo en cuenta que se publicó un par de días antes de terminar el año. Su propósito era sólo sorprender y divertir a esa gente rarilla como nosotros, los que disfrutamos cada vez que hay novedades en los lenguajes o herramientas con los que trabajamos a diario.

    Y ahora, ¡a por 2019!

    Publicado en Variable not found
    (Imagen de portada original de Pixabay)

    » Leer más, comentarios, etc...

    Koalite

    Mis tecnologías de 2018

    enero 08, 2019 05:06

    Entre los post tradicionales de este blog se encuentra el repaso de las tecnologías que he usado profesionalmente a lo largo del año. Si alguien tiene curiosidad por ver la evolución, puede revisar lo que usé en el 2015, 2016 y 2017.

    Como el contexto es importante, vamos a centrar un poco desde qué perspectiva está escrito este post.

    Sólo voy a mencionar tecnologías que uso profesionalmente, no aquellas con la que haya podido jugar un rato pero no haya tenido en producción. Para eso ya están otros posts de este blog.

    También es fundamental recordar que me dedico a productos, no proyectos. Además son productos (software para puntos de venta) que se despliegan localmente en las máquinas y en los cuales no controlamos el proceso de despliegue, el cual se realiza de forma manual no por una cuestión técnica sino de negocio: nuestros clientes son distribuidores que se ganan la vida vendiendo nuestro producto y cobrando por cosas como ésta. Si a esto le unes la importancia de mantener la compatibilidad con versiones anteriores del software, y sistemas operativos entre antiguos y obsoletos, el margen de maniobra que nos queda para jugar con nuevas tecnologías es menor y preferimos garantizar la estabilidad de todo el sistema.

    .NET, servidores y escritorio

    La base de nuestras aplicaciones está desarrollada usando .NET Framework 3.5 SP1. Sí, es antiguo, pero tenemos mucho parque instalado cuya actualización no es trivial (o ni siquiera está soportada). También tenemos algunos desarrollos de servidor “puro” en los que controlamos el despliegue y usamos versiones más modernas de .NET Framework (tradicional, no .NET Core), pero representa una parte mucho menor de nuestro código.

    Aquí miro con envidia algunas funcionalidades nuevas que ofrecen las últimas versiones de C#, pero para usarlas tendríamos que actualizar el tooling (seguimos con Visual Studio 2013 y el msbuild y Resharper de aquella época) y, hasta ahora, no nos ha compensado.

    Mantenemos nuestro stack clásico basado en NHibernate, Castle.Windsor, log4net y NUnit. Son piezas que nos funcionan muy bien, las conocemos perfectamente para explotar sus ventajas y minimizar sus inconvenientes, y la migración a otras librerías no creemos que nos aporte lo suficiente como para compensar el coste de hacerlo.

    Como servidor de base de datos seguimos usando Microsoft SQL Server, fundamentalmente las versiones 2005 y 2014. La realidad es que tampoco usamos nada especial del 2014 (más allá de que en la versión Express es algo más generoso en tamaño de base de datos) y nos ceñimos a lo soportado por 2005 para mantener compatibilidad. Tampoco es que tengamos unos requisitos muy elevados en este aspecto.

    Clientes web

    Cada vez más hemos ido migrando las aplicacones escritas en .NET (usando Windows Forms) a tecnologías web.
    Ahí se nota más la evolución a lo largo de los años (se puede ver también en los posts que he ido escribiendo sobre tecnologías front) y tenemos variedad de librerías, frameworks y lenguajes. Eso complica el mantenimiento, pero de momento no lo suficiente como para justificar la reescritura de aplicaciones, que están funcionando bien y generando negocio, en aras de usar The Best Platform™.

    Tenemos cosas con AngularJS 1.x (estamos deseando quitárnoslo de encima, pero es difícil de justificar económicamente) y con varias versiones de ReactJS (todas relativamente añejas). Tenemos cosas con ES5, otras con ES2015 y otras con TypeScript.

    Para hacerlo más entretenido, cada una usa el sistema de compilación que mejor nos pareció en cada momento, incluyendo Grunt, Webpack o simples scripts npm.

    En la parte de testing en frontend usamos estrategias variadas, pero al final utilizamos las librerías típicas: jasmine, mocha, chai, protractor y karma (para la parte de angular), nightwatch para tests de extremo a extremo con React. Con Enzyme ya el año anterior vimos que acabábamos teniendo tests que nos resultaban poco útiles y creo que este año no hemos escrito ningún test nuevo con él (incluso hemos borrado algunos existentes para no tener que mantenerlos).

    La nube

    Este año hemos empezado a prestar más atención a la nube. Nos hemos encontrado con la necesidad de ofrecer determinados servicios mantenidos por nosotros y eso nos ha llevado a aprovechar la parte de IaaS para levantar servidores dedicados para algunos clientes.

    De momento estamos usando como proveedor Azure (paso lógico dado nuestro bagaje), y por el camino hemos comenzado también a utilizar algunos servicios concretos de Azure, como Azure Blobs. Imagino que poco a poco iremos usando más servicios de Azure y atándonos más a él. No es algo que me deje demasiado tranquilo, pero bueno.

    En nuestra (limitadísima) experiencia con la nube y, especialmente, con Azure, ha habido cosas que nos han gustado más y cosas que nos han gustado menos, como era de esperar.

    Por una parte, la flexibilidad para levantar máquinas, aumentar o disminuir su potencia en función de las necesidades, gestionar backups, infraestructura de red, etc., es muy práctica. Como todas las cosas para pobres, lo barato acaba saliendo caro a la larga y los costes cuando empiezas a mirar a medio plazo pueden ser más elevados que con sistemas propios, pero la comodidad y la disminución de la barrera de entrada compensa en muchos casos.

    En la parte mala, a veces nos quedamos con la idea de que avanza todo un poco demasiado rápido. Y eso no es necesariamente malo, sino fuese porque te quedas con la impresión de producto a medio hacer. Que estén cambiando continuamente el UI del portal, que tengas unas cuantas cosas en preview, otras marcadas como classic (que suena a que las vas a descontinuar pasado mañana), otras con v1, v2, v3…

    Genera cierta sensación de inseguridad y no saber realmente qué puedes usar y hasta cuándo. Nos ocurrió por ejemplo con la parte de RBAC que nos gustó mucho pero cuando fuimos a implementarla nos encontramos con algunos problemas por no estar completamente integrada con otros servicios.

    (D)VCS e integración continua

    En esta parte es donde más cambios hemos introducido este año. Después de diez años usando Subversion para gestionar el código y CruiseControl.NET para lanzar nuestros scripts de compilación, hemos dado el salto a GitLab.

    No es que hubiera dejado de funcionar lo que teníamos, pero empezaba a suponer demasiada fricción para algunas cosas que queríamos hacer.

    El rendimiento de SVN ya se hacía difícil de soportar con el volumen de código que teníamos. Por su parte, CCNET nos complicaba la parte de compilar varias ramas de una misma aplicación. Lo hacíamos, pero de forma muy manual. Eso, unido a una suite de tests grande que tarda mucho en ejecutarse hacía que la rama principal se rompiese con frecuencia porque para evitar lanzar los tests en nuestros equipos de desarrollo subíamos directamente y dejábamos que se pasaran arriba.

    Con GitLab conseguimos un sistema de control de versiones más rápido y potente, que nos deja hacer más cosas, y de paso la gestión de pipelines de compilación.

    La migración de la parte de código no ha existido (adiós a la historia, que sigue viviendo en SVN) y ha consistido más en empezar a usar git “en serio”. Todos conocíamos git, pero no lo habíamos explotado realmente. De momento está siendo una buena experiencia.

    Adaptar nuestros scripts de compilación ha sido sencillo porque siempre hemos apostado por independizarlos de la herramienta que los lanza. Eso nos ha permitido aprovechar para automatizar un par de procesos manuales que nos quedaban y montar algo parecido a un sistema de entrega continua hacia la gente de QA que nos ayuda a detectar bugs antes.

    Después de probar unas cuantas (bastantes) herramientas para usar git, todos hemos acabado adoptando Git Bash como interfaz principal, apoyándonos en alguna otra cosa para tareas puntuales. Aquí me quedo con la espinita clavada de magit, que me encantó por filosofía y usabilidad, pero que es inmanejable con repositorios grandes en Windows.

    Resumen

    Viendo la evolución tecnológica que hemos tenido en los últimos años está claro que no formamos un equipo que esté trabajando siempre con lo último de lo último. Como decía al principio, el contexto en que nos movemos hace que eso no sea práctico y necesitemos ser más conservadores.

    Sin embargo, que tengamos que ser conservadores al adoptar nuevas tecnologías no nos exime de tener que estar pendientes de lo que va surgiendo y conocer qué nos ofrece esa nueva moda superinteresante de la que todo habla (spoiler: muchas veces, más de lo mismo). Puede que no lo pongamos en producción en la versión 1, y ni siquiera en la versión 2, pero si realmente nos soluciona un problema, es probable que acabemos usándolo.

    En lo que sí tenemos mucho cuidado es en evitar la tecnología por la tecnología. Jugar con cosas nuevas y cambiar de plataformas cada cierto tiempo es muy divertido y tentador, pero desde un punto de vista puramente económico (y no olvidemos que aquí estamos hablando de tecnologías que usamos profesionalmente), casi nunca es rentable.

    Posts relacionados:

    1. Resumen 2018
    2. Mis tecnologías del 2017
    3. Mis tecnologías del 2016

    » Leer más, comentarios, etc...

    Variable not found

    El operador virgulilla "~" en C#

    enero 07, 2019 08:42

    .NET CoreDesde que apareció Roslyn, C# ha ido evolucionando a pasos agigantados. Tanto es así que es frecuente encontrar desarrolladores que, aunque lo usan a diario, desconocen todo su potencial porque la velocidad de introducción de cambios en el lenguaje es mayor que la de asimilación de novedades por parte de los profesionales que nos dedicamos a esto.

    Por ejemplo, en las consultorías técnicas que realizo en empresas es frecuente encontrar equipos de trabajo en los que aún no está generalizado el uso de construcciones tan útiles como el null coalescing operator (fullName ?? "Anonymous"), safe navigation operator (person?.Address?.Street), el at object operator (Address@person), o características tan potentes como las funciones locales, interpolación de cadenas, tuplas o muchas otras.

    Sin embargo, creo que el rey de los desconocidos es el operador virgulilla "~" de C#. Introducido con C#7 es probablemente uno de los operadores menos utilizados y, sin embargo, de los más potentes ofrecidos por el lenguaje.
    Nota de traducción: el nombre original del operador es "tilde operator", y como he encontrado poca literatura al respecto en nuestro idioma, me he tomado la libertad de traducirlo como operador virgulilla (¡sí, esa palabra existe!). También, en entornos más informales lo encontraréis con el nombre "wormy operator" (operador gusanillo) o como "soft similarity operator" (que podríamos traducir como operador de similitud relajada).

    El operador virgulilla

    Originalmente el carácter "~" ya era utilizado desde los comienzos de C# para realizar la operación de complemento bit a bit, pero, dado su poco uso, en C#7 reescribieron el operador para alinear el lenguaje con las nuevas corrientes de popularización de los sistemas basados en inteligencia artificial.

    Básicamente, el operador virgulilla retorna un valor booleano resultado de evaluar una comparación entre dos objetos o valores, pero, a diferencia de las comparaciones "tradicionales", utilizando criterios basados en lógica borrosa y aprendizaje automático o machine learning.

    Creo que la mejor forma de entender rápidamente en qué consiste es viendo código, así que vamos con el primer ejemplo:
    var n1 = 987654320;
    var n2 = 987654321;

    Console.WriteLine(n1 == n2); // n1 equals n2? --> false
    Console.WriteLine(n1 ~ n2); // n1 is similar to n2? --> true!
    Efectivamente, como podéis suponer, el operador ~ comprueba si los dos operandos son similares, retornando true en caso afirmativo.

    Para ajustar en cada escenario el nivel de precisión que deseamos aplicar a las operaciones de comparación, C# permite encadenar hasta tres virgulillas para indicar que queremos aplicar un grado mayor de precisión en la evaluación de similitud:
    var n1 = 987654320;
    var n2 = 987654321;

    Console.WriteLine(n1 == n2); // n1 is equals to n2? --> false
    Console.WriteLine(n1 ~ n2); // n1 is similar to n2? --> true
    Console.WriteLine(n1 ~~ n2); // n1 is very similar to n2? --> true
    Console.WriteLine(n1 ~~~ n2); // n1 is very very similar to n2? --> false!
    Observad que en muchos escenarios puede librarnos del típico off by one, uno de los errores más habituales en el mundo de la programación.
    Los ejemplos anteriores pueden verse más o menos claros porque estamos comparando números. Pero la cosa va más allá; veamos otro ejemplo, usando la sobrecarga del operador para tipos string:
    string s1 = "Hello, what is your name?";
    string s2 = "Hello, wat is your name?";

    Console.WriteLine(s1 == s2); // s1 equals s2? --> false
    Console.WriteLine(s1 ~ s2); // s1 is similar to s2? --> true
    Empleando bien esta característica podemos simplificar bastante las comparaciones de cadenas y, sobre todo, el tratamiento de valores nulos, vacíos o blancos, como en el siguiente ejemplo:
    string strNull = null;
    string strEmpty = "";
    string strSpaces = " ";

    Console.WriteLine(strNull == strEmpty); // false
    Console.WriteLine(strNull ~ strEmpty); // true
    Console.WriteLine(strNull ~ strSpaces); // true
    Console.WriteLine(strSpaces ~ null); // true: Bye bye, string.IsNullOrEmpty()!
    Y como operador completo que es, también podemos utilizar composición de gusanillos para expresar comprobaciones complejas de forma más concisa:
    Console.WriteLine(strNull ~ strEmpty ~ strSpaces);   // true

    Comparaciones AI-Based

    Este operador también tiene la capacidad de comparar valores de distinto tipo, y es donde encontramos probablemente su utilidad más espectacular porque con ello se ponen en marcha los mecanismos de inteligencia artificial integrados en el compilador. Fijaos en esto:
    string strRed = "red";
    string strRgbRed = "ff0000";
    Color colorRed = Color.Red;
    int intRgbRed = 0xff0000;

    Console.WriteLine(strRed ~ strRgbRed ~ colorRed ~ intRgbRed); // true!!
    Internamente, el sistema es capaz de detectar la similitud gracias a la siguiente cadena de decisiones:
    • Sabe que Color.Red es un miembro de un enum con un valor entero equivalente a 0xff0000.
    • Por esta razón, sabe que colorRed ~ intRgbRed.
    • También sabe que "ff0000" es la representación textual de 0xff0000, y teniendo en cuenta los pasos anteriores, deduce que strRgbRed ~ colorRed ~ intRgbRed.
    • El texto "red" se encuentra en el nombre de la constante Color.Red, por lo que ya puede concluir que strRed ~ strRgbRed ~ colorRed ~ intRgbRed.
    Brutal, ¿eh?

    Pues subamos la apuesta ;) La comparación de similitud ofrecida por el operador también está integrada con los sistemas NLP (procesamiento de lenguaje natural) incluidos en el compilador. Observad en el siguiente ejemplo, donde evaluamos la similitud de cadenas de caracteres y enteros utilizando comparación semántica:
    using Microsoft.Extensions.Operators.Tilde.Nlp;
    ...
    int numberTen = 10;
    string strDiez = "diez";
    string strDecena = "una decena";
    string strDecimoDeCien = "la décima parte de cien";

    Console.WriteLine(strDiez ~ numberTen ~ strDecena ~ strDecimoDeCien); // true!
    Nota: para que esto funcione correctamente es necesario incluir el espacio de nombres Microsoft.Extensions.Operators.Tilde.Nlp.
    Incluso podemos sacar partido fácilmente a la traducción en tiempo de compilación a distintos locales. Fijaos que en este caso son dos los namespaces los que debemos utilizar:
    using Microsoft.Extensions.Operators.Tilde.Nlp;
    using Microsoft.Extensions.Operators.Tilde.Nlp.I18N;
    ...
    string strTen = "ten";
    string strDiez = "diez";

    Console.WriteLine(strTen ~ strDiez); // true; usando auto-locale
    Console.WriteLine(strDiez ~en-us~ strTen); // true; forzando el locale en-us
    Console.WriteLine(strDiez ~fr-fr~ strTen); // false (el locale forzado es incorrecto)

    Activación en Visual Studio 2017 u otros IDE

    Sin duda, este operador es uno de los grandes desconocidos por la controvertida decisión de Microsoft de hacer que éste sólo esté disponible cuando es habilitado explícitamente en el entorno de desarrollo. Es decir, no podemos utilizarlo en nuestro código hasta que lo activemos expresamente en el proyecto.

    Para conseguirlo en Visual Studio 2017, basta con acudir a las propiedades del proyecto, pestaña "Build", clicar el botón "Avanzado" y asegurarse de que está marcado Enable similarity operator, como se muestra en la siguiente captura de pantalla:


    Activación del operador virgulilla en Visual Studio 2017
    Si no utilizamos Visual Studio, también podemos acudir al archivo .csproj del proyecto e insertar manualmente las siguientes líneas (es posible que requiera reiniciar VS tras hacerlo):
    <PropertyGroup>
    <LanguageVersion>Latest</AssemblyName>
    <EnableSimilarityOperator>true</EnableSimilarityOperator>
    </PropertyGroup>

    ¿Y está disponible este operador en Visual Basic .NET?

    Nota: Este punto no estaba inicialmente en el post, pero lo he actualizado para responder a la pregunta que me hacía @visualbutnotbasic vía Twitter nada más publicarlo.
    Pues aún no, pero lo estará pronto. Según Victor Bellino, principal program manager en Redmond, se está trabajando duro para llevar este operador a Visual Basic, aunque probablemente bajo otra denominación para alinearlo sintácticamente con las construcciones habituales de este lenguaje:
    If x Is Similar To y And Similar To z Then
    ...
    End If

    En conclusión

    Desconozco las razones que han llevado a Microsoft a mantener el operador virgulilla semioculto tras opciones de compilación, y a no haberlo publicitado más. Probablemente existan reticencias por que, como todo gran poder, su uso exige una gran responsabilidad y es posible que piensen que los desarrolladores no estamos aún preparados para asumirla.

    Pero en cualquier caso, sin duda, el operador virgulilla es una de las incorporaciones al lenguaje C# más interesantes de los últimos años, pues nos abre la puerta a la construcción de sistemas más fiables, inteligentes y flexibles.

    El operador virgulilla es a la inteligencia artificial lo que LINQ fue en su momento a las bases de datos: una integración dentro del lenguaje de funcionalidades que tradicionalmente pertenecían a un mundo aparte. De ahí que podamos considerarlo como una herramienta totalmente disruptiva para los desarrolladores.

    Nota para despistados: lamentablemente (o afortunadamente, no estoy seguro), el operador virgulilla no es real, se trata sólo de una broma del Día de los Inocentes (28-dic). Pero si os gusta la idea y pensáis que podría ser una aportación interesante a C#, siempre podéis proponerlo en el repositorio oficial del lenguaje y quién sabe, quizás algún día podamos disfrutarlo ;)

    Publicado en ~Varible not fund.

    » Leer más, comentarios, etc...

    Blog Bitix

    Las contraseñas e información sensible en el código fuente o bytecode de Java no son seguras

    enero 05, 2019 12:00

    Java

    En Java el código fuente se compila a una representación en bytecode independiente de la arquitectura del procesador y sistema operativo donde posteriormente se ejecuta. Este bytecode es un formato binario pero que puede ser decompilado fácilmente con la herramienta javap incluida en el propio JDK o examinado su contenido simplemente con un editor de texto hexadecimal. Con estas herramientas es fácil ver las instrucciones del programa para la máquina virtual y los caracteres de las cadenas que fueron incluidas en el código fuente.

    Lógicamente, de este modo hardcodear una contraseña en el código fuente hace que el código fuente sea inseguro pero es que incluso distribuir el binario compilado no es seguro ya que cualquier usuario que tenga acceso al binario de la aplicación es potencialmente capaz de recuperar la contraseña, tener acceso al binario quizá no sea sencillo pero aparte de eso no hay ninguna medida de seguridad adicional que añada más dificultad. Quien dice contraseña dice igualmente una clave privada de cifrado simétrico usada para cifrar o descifrar datos o un bearer token de OAuth. En definitiva es un problema de seguridad.

    Compilado el programa y utilizando la herramienta javap se puede obtener el valor de la contraseña. ¿Adivinas cual es la contraseña en este archivo binario de bytecode examinado el contenido?

    Contenido hexadecimal de un archivo binario de bytecode Java y C

    El siguiente ejemplo sencillo de un programa Java incluye una cadena con una supuesta contraseña. Se observa que en el archivo visualizado en formato hexadecimal o decompilado los caracteres de la cadena son fácilmente reconocibles.

    1
    2
    3
    4
    5
    6
    
    public class Main {
    public static void main(String[] args) {
    String password = "Mz6K3P9rDZ7G6wH";
    System.out.println("Hello World!");
    }
    }

    Para compilar este pequeño programa se utiliza el comando javac que genera el archivo de bytecode Main.class.

    1
    2
    
    $ javac Main.java
    $ xxd Main.class

    Para decompilar este pequeño programa se utiliza el comando javap, con él se ven las instrucciones interpretadas por la máquina virtual de Java y la cadena con la contraseña.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    [picodotdev@archlinux Descargas]$ javap -c Main.class
    Compiled from "Main.java"
    public class Main {
    public Main();
    Code:
    0: aload_0
    1: invokespecial #1 // Method java/lang/Object."<init>":()V
    4: return
    public static void main(java.lang.String[]);
    Code:
    0: ldc #2 // String Mz6K3P9rDZ7G6wH
    2: astore_1
    3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
    6: ldc #4 // String Hello World!
    8: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    11: return
    }

    Que el contenido de la constante de las cadenas del programa sea incluido en el binario y examinable con un editor hexadecimal no es exclusivo de Java, en otros lenguajes de programación como C y formatos de ejecutables como ELF para Linux se da el mismo caso al examinar el binario como se observa en las imágenes anteriores. Seguramente en la mayoría de lenguajes, como C#, ocurra lo mismo.

    1
    2
    3
    4
    5
    6
    7
    8
    
    #include <stdio.h>
    
    int main()
    {
    const char *password = "Mz6K3P9rDZ7G6wH";
    printf("Hello World!");
    return 0;
    }
    1
    2
    
    $ gcc Main.c -o Main
    $ xxd Main

    Una solución para evitar este problema de seguridad es ubicar la contraseña a un archivo de configuración incluso con los valores sensibles cifrados y que sean descifrados únicamente por la aplicación en el momento de iniciarse. En el caso de ubicar este archivo de configuración en un servidor se puede proteger mediante permisos para que solo los administradores o algunos desarrolladores tenga acceso a él y no cualquier usuario que consiga acceso al sistema.

    » Leer más, comentarios, etc...

    Arragonán

    Un repaso a mi 2018

    enero 04, 2019 12:00

    Este año tuve un cambio vital importante que vino dado por una combinación de circunstancias y tras un par de meses de conversaciones, terminé mudándome en Junio a A Coruña para trabajar en Zara.com. Así que cuando estuve repasando hace unos días los objetivos del año anterior, pasó lo que era de esperar, que fue bastante fiasco:

    En Coding Stones como muchas otras empresas (aunque jamás nos constituyéramos como una), no conseguimos llegar y superar el tercer año de vida. Con las reglas de juego que nos marcamos sabíamos que era un modelo difícil, exige a los socios estar muy alineados y cuesta horrores vender a los clientes. Para mí haber formado parte de esta banda-cooperativa fue una experiencia que me hizo crecer en muchos aspectos, resulta imposible no tener morriña de mis exsocios de vez en cuando.

    Respecto a Outreach Tool una de cal y otra de arena. El software tras la reescritura es mucho más extensible y pude ir implementando cambios y mejoras más fácilmente, aunque con mi cambio profesional se quedaron bastantes cosas en el tintero, principalmente relacionadas con temas del rediseño de UI. Ahora mismo la evolución (y explotación) del software está un poco en el limbo, quizás puedan seguir haciéndolo otras personas.

    A pesar del parón en febrero por la lesión de una costilla flotante durante una sesión de guanteo, los primeros meses del año tuve una muy buena rutina de entrenamiento y empecé a sentir que (además de mantenerme bien físicamente) técnicamente mejoré bastante. Pena que con el lío del cambio de ciudad no reservé apenas tiempo para buscar un gimnasio en el que seguir aprendiendo, finalmente en noviembre encontré uno que pintaba muy bien, aunque aún he ido muy poco.

    En 2018 no hice charlas como tal, sólo la que preparé con la excusa del lanzamiento del curso de Codely de BDD con Cucumber. Facilité coding dojos en el trabajo y en la CommitConf, además del Global day of Code Retreat en Coruña. Experimenté con el formato screencast y me moló bastante. Pero apenas escribí 2 posts en el medium de Coding Stones, este me gustaría escribir más. De nuevo formé parte de la organización del Startup Open Space Zaragoza.

    Lo de sacar un DNDzgz como PWA quedó en un fail total. Tengo una versión funcional desplegada que les pasé a un puñado de amigos pero le faltan afinar muchas cosas, así que por el momento se queda como otro petproject que iré tocando cuando me de el puntazo de mejorar algo.

    Y bueno, ni mucho menos fue un objetivo, pero algo tendré que contar de Zara.com. Llegué con la idea de dedicar la mayor parte de mi tiempo a hacer producto e involucrarme hasta la cocina en todo lo que me dejaran y fuera capaz respecto a ello. Esto es que no quiero sólo desarrollarlo, sino estar desde las fases de conceptualización y aterrizaje hasta el aprendizaje de tenerlo en producción, y acompañar en hacerlo crecer y evolucionarlo. En el camino estoy haciendo variedad de trabajo, yendo desde cuestiones puramente técnicas hasta temas más relacionados con producto o gestión, así que no me aburro ;)

    Como de costumbre, más cosas del año y sin ningún orden en particular:

    • Leí mucho este año, pero casi todo fueron libros técnicos.
    • Estuve unos días en Roma.
    • Repetí participación en Movember.
    • No fui a tantos conciertos como en los últimos años, pero fui a un buen puñado.
    • Fui a algún partido a la Romareda y a Riazor (aunque no pudo ser al Depor-Zaragoza).
    • Pude asistir a BilboStack, From The Trenches en Donosti, NOS Day y XantarJ en Santiago, Software Crafters Madrid y Monitoring Day en Zaragoza.
    • Vi un par de veladas de boxeo y me quedé con ganas de ir a alguna grande.
    • Por avisado que fuera, me sorprendió lo masificada de la noche de San Juan en Coruña.
    • Volví a tener vértigos.
    • Colaboré (poco) con la fundación canem.
    • En verano hice unas vacaciones típicas aragonesas: costa daurada y fiestas del pueblo.
    • Al fin vi el Guernica de Picasso.
    • Montamos un eventito con Cachirulo Valley.
    • Hice un curso de product owning con las buenas gentes de Makoto.
    • Sobreviví a 3 bodas.
    • Vanessa también se vino a vivir a Coruña.
    • Me hicieron una fiesta de despedida sorpresa que lo moló todo.

    Objetivos 2019

    • Patear la zona noroeste de la península. Tanto zonas costeras atlántica y cantábrica como visitar ciudades y pueblos más hacia el interior; son zonas que apenas he tenido oportunidad de conocer.
    • Colaborar con grupos locales a través de talleres, coding dojos… Ya tengo algunas cosillas medio apalabradas con varias personas que organizan iniciativas por aquí.
    • Conseguir tener una rutina de entrenamiento similar a la que tuve los primeros meses de 2018 para al menos recuperar técnica y aprender trucos nuevos.
    • Escribir una media de un post al mes. Llevo un tiempo en el que me apetece escribir sobre varios temas sobre los que voy trabajando, pero aunque no sean temas muy sesudos sé que me cuesta horrores y termino no dedicándole tiempo.

    Respecto a mi actual trabajo, más que marcarme objetivos lo que tengo son retos. Retos que me resultan muy interesantes para este 2019, junto a varios que se avecinan en el horizonte y otros que seguro que irán surgiendo.

    ¡Feliz an nou!

    » Leer más, comentarios, etc...

    Poesía Binaria

    ¡El 2018 se escapa! Posts, estadísticas, cambios, retos, programas, amigos, y más

    diciembre 30, 2018 08:09

    2018 con bombilla.

    ¡Ya casi estamos en 2019! Y, como siempre, no he escrito ni la mitad de posts que quería escribir, ni he empezado ni la mitad de los proyectos que quería empezar. Aunque, estoy con ganas, fuerzas, e ilusión para llevarlo todo a cabo en 2019. Aunque, a primeros de año empecé con muchas ganas, las circunstancias han hecho que me tire unos tres meses sin escribir nada. Incluso aunque tengo un par de series de posts empezadas, no he podido continuarlas. Espero que este año sí que pueda hacerlo.

    Y, como todos los años, quiero compartir algunas de las estadísticas que me han llamado la atención. Aunque, este año, quiero copiarme de @ochobitsunbyte y hacer mención a algunos de los proyectos que he seguido este año. Que, aunque no he publicado mucho, ni comentado mucho, entro, leo y curioseo 🙂

    Redes sociales

    ¡Tengo 14 seguidores menos en Facebook! Es el primer año que termino con menos seguidores de los que tenía cuando empezó. Se nota que no le he dedicado mucho tiempo a Facebook este año. Le puedo echar la culpa a los escándalos que ha tenido Facebook, es verdad que tengo varios contactos que ya no están, y como debían, por ser amigos, tenían el Me Gusta dado a la página, podría echarle la culpa a las restricciones que tiene la API de Facebook y que me incomoda un poco a la hora de promocionar posts y eso… pero bueno, tendré que dedicar algo de tiempo a esta red.

    También me está dando últimamente por publicar píldoras de Bash en redes sociales, aprovechando que me he hecho un Instagram para el blog. Iré comentando poco a poco todas las píldoras. La idea de montar estas imágenes la saqué de @hacemoswebserie, por lo que con un pequeño (bueno, no tan pequeño) script con ImageMagick voy montando las imágenes.

    Los posts más populares en Facebook

    1. Foto de Debian en mi móvil Android. Por cierto, publiqué un vídeo de la instalación.
    2. Rescatando capturas de pantalla perdidas en el disco duro.
    3. Mi configuración personal de Emacs
    4. Trabajar en equipo a través de Internet utilizando herramientas libres
    5. Script para hacer capturas de pantalla

    Twitter

    En lo que respecta a Twitter, sigo con la tarea de automatización pendiente, aunque algo he hecho y he probado en Twitter y LinkedIn. Pero no la he utilizado activamente. Tengo que agradecer a @juldelagbeta, @almu_hs, @m4r14h, @HighLordIron, @currolinux, @hacemoswebserie, @daboblog, @PGNalda, @CalafontNatural, @otixunil y a muchísimos más por hacer mi Twitter mucho más ameno. En serio, el día que me ponga a monitorizar Twitter os pondré a todos y no se me olvidará nadie 🙂

    También quiero agradecer a @guilleamodeo, que aunque no mira mucho Twitter. Me enseñó muchísimas cosas cuando nos conocimos y me hizo un logo muy chulo que tengo en Facebook y he puesto por aquí alguna vez. Y a Romualdo, que tampoco pisa mucho Twitter, agradezco los comentarios que me deja en la página de Facebook.

    Blogs y más

    Yo soy de ese tipo de personas que tienen más de 500 pestañas abiertas a la vez, en concreto, 1186 pestañas abiertas en cuatro perfiles (entre Firefox y Chrome). Lo sé, es una barbaridad, porque suelo dejar las cosas abiertas para volver luego. Lo malo, es que cuando vuelvo, al cabo de los dos o tres meses, algunas páginas han desaparecido. Me ha pasado también con muchos enlaces que he hecho desde esta página… problemas del cibermundo.

    Este año, quiero aprovechar para mencionar algunos de los blogs que he seguido este 2018. Es verdad que cada año va variando, pero siempre encuentras alguno recurrente, y me parece perfecto, al menos hacer mención a su gran labor. Y, por favor, si estos blogs tienen publicidad, desactivemos los bloqueadores, y miremos sin nos interesa, porque una pequeña gratificación económica a los autores no viene mal, sobre todo si no nos cuesta nada. Hombre, ya donar dinero al autor, contratar posts patrocinados o un espacio de publicidad mucho mejor. Ahí va mi lista de este año:

    • ochobitshacenunbyte: Una gran referencia para el mundo sysadmin. Tanto para mis andanzas laborales como frikeos personales. En más de una ocasión, me he encontrado geniales posts de @ochobitsunbyte. De esos
    • Linuxito: Otro blog donde encontramos muchísima información para los que pasamos el día entre servidores de Internet.
    • El Array de Jota: Otra más de sysadmin. Muchos consejos, tutoriales y “marrones” de trabajo que pueden ser divertidos y llevaderos.
    • System Inside: Instalaciones, videotutoriales, reviews y mucho más, en formato vídeo.
    • ¿Hacemos una webserie? Cine, teatro, literatura, cultura y la importancia de la automatización con el software en formato podcast. También tiene muchos vídeos de ImageMagick y si buscamos, muchos tutoriales sobre Bash e ImageMagick.

    También quiero hacer mención a LinuxCenter por la labor que están haciendo con el software libre y su divulgación (tengo que subir una foto con la camiseta que me regalaron); Podcast Linux, lo escucho algo menos a menudo que el año pasado, pero suelo escuchar todos los audios que han subido desde la última vez; atareao con contenidos para parar un tren… Me dejo muchos blogs y personas sin mencionar. Poco a poco quiero ir haciendo un blogroll decente.

    Programas que me han salvado la vida

    Otros años le he dedicado un post completo a esto. Aunque, tengo algo especial pensado. Me he estado dedicando un poco más a la administración de sistemas. Así que docker, ansible, puppet y netdata son tres de los proyectos que más he utilizado. Y tengo bastante contenido en el tintero de cada uno de ellos.
    Pero otra de mis grandes pasiones, el vídeo, no puedo dejarla atrás. Y veo que Blender, cada vez es más estable. Y ahora, por fin, tienen un nuevo encargado del desarrollo del secuenciador, por lo que esperemos que tengan novedades al respecto. Revisaré y enviaré de nuevo algunas modificaciones que propuse y daré un poco de guerra.

    Lo más visitado

    Aunque @oscaragugav insiste en que ponga Google Analytics, yo utilizo Piwik. Dentro de lo que cabe, intento no utilizar demasiados servicios externos, aunque sean gratis, quiero que la información me pertenezca. Es cierto que también tengo Adsense, y que Google puede monitorizar del mismo modo las visualizaciones y quedarse con la información que le dé la gana de mi blog, pero bueno, con Piwik tengo algo más de control de las analíticas web. Aunque me gustaría montar mi propio sistema de publicidad, eso sería un trabajazo que me llevaría demasiado tiempo y no me da para vivir.

    Por tanto, lo más visitado este año en mi blog ha sido:

    1. Algoritmos: Formas de transformar un entero a cadena en C y C++
    2. Cómo procesar múltiples argmentos de entrada en scripts para Bash.
    3. Cómo extraer ruta, nombre de fichero y extensión en Bash
    4. Bucles y cursores en MySQL con ejemplos.
    5. 9 trucos para manejar cadenas en Bash y no morir en el intento.

    Parece que Bash se lleva la mayoría de las visitas.

    Navegadores más usados

    • Google Chrome (65%) sube 5% con respecto al año pasado.
    • Mozilla Firefox (22%) se mantiene con respecto al año pasado.
    • Opera (3%) sube 1% con respecto al año pasado.
    • Chrome Mobile (2%) Baja 4%.
    • Safari (2%) se mantiene con respecto al año pasado.

    Sistemas Operativos más usados

    • Windows (68%) sube 6% con respecto al año pasado.
    • GNU/Linux (cualquier distribución) (21%) sube 3% con respecto al año pasado
    • Mac OSX (5%) se mantiene
    • Android (3%) baja 6%

    Creo que dado a que mucha gente entra por móvil gracias a redes sociales. Al no haber hecho mucho énfasis este año en ellas, más usuarios han entrado desde ordenador.

    Enlaces entrantes

    En este listado, dejando fuera las entradas desde redes sociales como Facebook, Twitter, Google+, Youtube y LinkedIn:

    Países

    • España (61.5%) baja 1.5%
    • EEUU (14.9%) baja 2.1%
    • México (6.7%) sube 0.7%
    • Argentina (2.9%) baja 0.1%
    • Reino Unido (2.2%) sube 1%

    Lo más comentado

    Empezamos con las consultas a mi WordPress. Aunque no las pondré todas, porque en este post están todas. Los posts más comentados del año son los siguientes:

    Otras curiosidades

    Este año se han publicado 30 posts y 2 páginas (0.6 por semana) de los cuales 24 de ellos contienen código y 20 de ellos ejemplos de uso de terminal (con el plugin SimTerm). Además, he modificado 8 posts de años anteriores. Además, he invertido unas 183 horas (algo más de semana y media) escribiendo para el blog.

    En total 110 fragmentos de código (71 menos que el año pasado). De los cuales:

    • 2 son de C
    • 24 son de PHP
    • 30 son de BASH
    • 10 son de Lisp
    • 3 son de Python

    Los demás, son JSON, Javascript, XML, archivos de configuración, SQL y algunos más.

    Cada post tiene una media de 2391.82 (472 palabras más que el año pasado) o unas 17402.7586 letras (2681 letras más que el año pasado), ¡enrollándome aún más que otros años!. También he publicado 212 enlaces (174 menos que en 2017) de los cuales 109 son internos (103 externos).

    Los posts más largos

    Los posts más cortos

    Este año ha habido algunos posts cortos, sobre todo para comentar fotos de mi Instagram, y algunas pequeñas píldoras que quería publicar sin enrollarme demasiado.

    Posts en otros lugares

    Este año escribí tres posts en LinuxCenter:

    El segundo es un post original para LinuxCenter, mientras que los otros dos están extraídos de antiguos posts de este blog, aunque reescritos.

    Retos del 2019

    El año pasado quería llegar a los 150 posts, pero creo que a unos 100 posts, dado que de media son bastante extensos puedo llegar. Tengo muchos proyectos que quiero ir ordenando. Me gustaría empezar un podcast, ahora que está de moda, y darle un poco de movimiento al canal de Youtube, puesto que me parece un buen lugar para compartir ciertos contenidos. Ya tengo un micrófono listo para eso, no es de lo mejor en micrófonos, pero se escucha mejor que el micrófono de la cámara, y no tengo que gritar tanto.

    The post ¡El 2018 se escapa! Posts, estadísticas, cambios, retos, programas, amigos, y más appeared first on Poesía Binaria.

    » Leer más, comentarios, etc...

    Blog Bitix

    Hemeroteca #14

    diciembre 30, 2018 06:00

    Casi 9 años publicando de forma constante un artículo a la semana, dos artículos algunas semanas. Durante casi está década creo que ha habido pocas semanas en las que no he publicado. Este último años no ha sido distinto, el siguiente espero… espero que sea parecido o mejor ahora que tengo mi propio equipo personal que he estado mucho tiempo esperando hasta que fuese comercializado, un Intel NUC Bean Canyon del que en este semestre he publicado un artículo con de su desempaquetado junto con otros artículos del monitor Benq PD2700Q y teclado ratón pero también comentando las opciones que he barajado.

    Casi 9 años publicando artículos algunos mejor que otros pero siempre intentando compartir lo que sé o voy aprendiendo, para llegar a esta cifra hay que tener constancia para llegar a escribir tanto tiempo, dedicación, no abandonar fácil para no empezar una cosa y dejarlo al cabo de poco e incluso de publicar aún cuando el ánimo no es el de los mejores momentos, planificación y organización para saber que publicar, tiempo ya que cuesta escribir los artículos, motivación y perseverancia para tener más visitas, comentarios de algunos usuarios, más ingresos de AdSense, compartir, … No es fácil, en mi caso sabiendo que mucho de lo que escribo no lo aplico en el trabajo, donde actualmente tengo un Mac por obligación y no trabajo con Java y mis temas principales son GNU/Linux y Java, con lo que a veces tengo la sensación de no serme útil. Al finalizar este 2018 ya van 371 artículos publicados desde finales del 2013 que empecé a hacerlo en Blog Bitix.

    Ahora que tengo mi propio equipo y como de nuevo estoy usando Arch Linux podré publicar más artículos sobre GNU/Linux, he empezado por publicar como jugar a Diablo 3 u otros juegos de Blizzard en Linux con Wine.

    Durante este semestre he escrito varios relacionados con el ordenador que necesitaba y finalmente he comprado al final de año pero del que había noticias desde muchos meses antes, la espera de varios meses hasta que se comercializase se me hizo larga.

    He completado con algún artículo más la serie sobre GraphQL.

    También he escrito varios artículos de la serie sobre Spring Cloud para escribir microservicios en Java.

    Algunos pocos artículos sobre GNU/Linux.

    Sobre Java o programación algunos más.

    Y varios de opinión o noticias relevantes como la caída de Sun hace ya años o la compra de Red Hat por parte de IBM.

    Revisando los artículos que he publicado en otras hemerotecas hay algunos que parece no los haya escrito hace 2 y 3 años sino mucho menos. El crecimiento tanto en visitas como en ingresos no ha sido tanto como años anteriores, supongo que ya llegado un cierto umbral es difícil conservar los mismos ratios de crecimiento. Todos estos nuevos artículos es nuevo contenido que hace que reciba algunas visitas más y que los ingresos sean algo mayores. Dependiendo de si miro en Analytics o AdSense unas 520K páginas vistas este 2018 no muchas más que las casi 500K del 2017. En cualquier caso en las siguientes imágenes están las estadísticas de este blog por si a alguien le sirven para comparar con el suyo o como previsión de una web similar, a mi me servirá para consultarlo en un futuro y ver la evolución de los datos, también el tiempo necesario para conseguirlo o el número de artículos requeridos.

    Evolución visitas e ingresos en 2018

    Incluyo además algunas métricas que proporciona AdSense de RPM, clics y CPC. Este final de año ha sido especialmente bueno con la semana del black friday como punto álgido llegando casi a 70 € en un mes, en todo el año han sido casi 500 € contra los 450 € del 2017.

    Métricas de AdSense

    Además, de AdSense incluyo enlaces de Amazon de algunos productos que compro y de los que publico algún artículo. Este año a través de los enlaces de afiliado de Amazon ha facturado a través de ellos casi 900 € y he recibido a cambio 50 € en comisiones.

    Como siempre mis deseos de que empecéis bien el nuevo año, en este caso el 2019, ¡buen 2019!.

    ¡Buen 2019!. Fuente: www.klowner.com

    » Leer más, comentarios, etc...

    Koalite

    Resumen 2018

    diciembre 26, 2018 05:06

    Da un poco de vergüenza que después de no escribir nada de julio la forma de reactivar el blog sea este post, pero mi intención es retomar el blog durante el 2019 y para ello nada mejor que respetar las costumbres y preparar el resumen anual.

    Estadísticas

    Me había marcado como objetivo escribir 24 posts y está bastante claro que no lo he conseguido. Han sido en total 14 posts, todos (excepto éste) escritos antes del verano. Para el año que viene no me atrevo a marcar un objetivo en cuanto a número de posts porque no tengo ni idea de si podré cumplirlo o no; mi objetivo real es mantener el blog vivo, aunque sea para escribir algo de vez en cuando sin periodicidad fija.

    Según Google Analytics, a lo largo del año han visitado este blog unos 140.000 usuarios, en 180.000 sesiones que les han llevado a ver 225.000 páginas. Es una disminución de entre el 20% y el 25% con respecto al año pasado dependiendo de la métrica que tomes.

    Si no me preocupaban mucho estas cosas cuando el blog era una parte más importante en mi vida, obviamente tampoco voy a empezra a preocuparme por ello ahora. Teniendo en cuenta los pocos posts que he escrito este año y la temática de muchos de ellos, es más que razonable que el interés, y sobre todo el posicionamiento en Google, vaya cayendo.

    Quizá afecten también temas puros de SEO (como no tener configurado el acceso https al blog), pero si dedico tiempo al blog será más para escribir, que es lo que me gusta, que para andar jugando con cuestiones puramente técnicas.

    Lo más visitado del año

    El top 5 del año es:

    1. Programar no es desarrollar
    2. Ofuscar el código para proteger… ¿qué?
    3. Lo mínimo a entender sobre transacciones
    4. Ingenieros o artesanos
    5. APIs con Node, TypeScript, Koa y TypeORM

    El post sobre lo que realmente implica desarrollar software es también mi post preferido del año, y además creo es uno de los más aprovechables de este blog si quieres dedicarte a desarrollar software. Entre los que se quedan fuera, me gustó repasar las diferencias entre tipado nominal y estructural y diseñar builders basados en funciones para TypeScript.

    Lo más visitado de siempre

    Echando la vista atrás y buscando los posts más visitados desde el origen del blog, se nota mucho lo que pesa el tráfico de motores de búsqueda y se cuelan ahí posts que no son especialmente interesantes pero que están bien posicionados (y que a día de hoy ni siquiera sé si siguen valiendo para algo con los cambios de versiones de librerías):

    1. 5 cosas que deberías aprender para dedicarte al desarrollo de software
    2. Lectura de Códigos de Barras con HTML5 y Javascript
    3. ¿Qué es la Programación Lógica?
    4. Cómo Generar Códigos QR en C#
    5. Paso de parámetros entre controladores en AngularJS

    De todos estos posts el único que merece la pena releer es el primero y, si no sabes lo que es la programación lógica, el tercero. Me sorprende mucho que ese post tenga tantas visitas; estoy casi seguro de que debe de haber otra cosa que la gente llama programación lógica que les lleva hacia él, porque dudo que prolog tenga tantos seguidores en el mundo.

    Hasta el año que viene

    Y hasta aquí este irregular 2018 en el blog. Espero que el 2019 pueda tener más tiempo para disfrutar escribiendo.

    ¡Feliz 2019 a todos!

    Posts relacionados:

    1. Resumen 2016
    2. Resumen 2013
    3. Resumen 2017

    » Leer más, comentarios, etc...

    Variable not found

    Otras formas de obtener dependencias en controladores ASP.NET Core MVC

    diciembre 25, 2018 09:49

    ASP.NET Core MVC Hace unos días publicaba un post sobre la mala idea que era tener controladores con demasiadas responsabilidades y cómo un constructor con demasiadas dependencias podía ser una señal (un 'code smell') de que las cosas no estaban yendo bien en ese sentido.

    Por ejemplo, echando un vistazo al siguiente controlador podemos ver claramente una violación del Principio de Responsabilidad Única (SRP) en un controlador que conoce demasiados detalles sobre la forma de proceder al registrar un pedido:
    public class OrderController: Controller
    {
    private readonly IOrderServices _orderServices;
    [...] // Other private members

    public OrderController(IOrderServices orderServices, IUserServices userServices,
    IMailingService mailingServices, ISmsServices smsServices,
    IPdfGenerator pdfGenerator, IMapper mapper
    )
    {
    _orderServices = orderServices;
    _userServices = userServices;
    [...] // Other assignments...
    }

    [HttpPost]
    public Task<IActionResult> Submit(OrderViewModel orderViewModel)
    {
    if(!ModelState.IsValid)
    {
    return View(orderViewModel);
    }
    var orderDto = _mapper.Map<OrderDto>(orderViewModel);
    var orderResult = await _orderServices.AddAsync(orderDto);
    if(!orderResult.Success)
    {
    return RedirecToAction("OrderError", new { error = orderResult.Error }));
    }
    var userPreferences = await _userServices.GetNotificationPreferencesAsync(CurrentUserId);
    var pdfUrl = await _pdfGenerator.GenerateOrderAsync(orderResult.Details);
    if(userPreferences.NotificationMode == NotificationMode.Sms)
    {
    await _smsServices.NotifyNewOrderAsync(orderResult.Details, pdfUrl);
    }
    else
    {
    await _mailingServices.NotifyNewOrderAsync(orderResult.Details);
    }
    return RedirectToAction("ThankYou", new { id = orderResult.Details.OrderId } );
    }
    ...
    }
    En dicho post comentaba también algunas cosas que podíamos hacer para solucionar el problema, así como una recomendación de lo que no debíamos hacer: disimular dependencias instanciando directamente componentes o utilizando otros "sabores" de inyección que hicieran menos evidente la relación de nuestra clase con otras de las que depende.

    Pues bien, como hoy estamos algo rebeldes, vamos a ver las técnicas que nos permitirían hacerlo cuando sea necesario o, dicho de otra forma, qué alternativas tenemos para usar dependencias desde los controladores sin que estas sean suministradas mediante inyección en su constructor.
    Precaución: estas técnicas son consideradas antipatrones o, como mínimo, code smells en la mayoría de los escenarios, así que usadlas poco, con precaución y siempre con conocimiento de causa ;)

    Instanciación manual de componentes

    En primer lugar, atentando directamente contra las buenas prácticas de diseño con bajo acoplamiento y la mismísima "D" de los principios SOLID, tenemos la posibilidad de instanciar los objetos que necesitemos, justo en el momento en que lo necesitemos.

    El siguiente código muestra cómo podríamos reescribir el ejemplo anterior utilizando esta técnica:
    public OrderController()
    {
    // Nothing to do...
    }

    [HttpPost]
    public Task<IActionResult> Submit(OrderViewModel orderViewModel)
    {
    if(!ModelState.IsValid)
    {
    return View(orderViewModel);
    }

    var mapper = new Mapper();
    var orderDto = mapper.Map<OrderDto>(orderViewModel);

    using (var ctx = new ShoppingCartContext())
    {
    var orderServices = new OrderServices(ctx);
    var orderResult = await orderServices.AddAsync(orderDto);
    if(!orderResult.Success)
    {
    return RedirecToAction("OrderError", new { error = orderResult.Error }));
    }
    var userServices = new UserServices(ctx);
    var userPreferences = await userServices.GetNotificationPreferencesAsync(CurrentUserId);
    var prfGenerator = new PdfGenerator();
    var pdfUrl = await pdfGenerator.GenerateOrderAsync(orderResult.Details);
    if(userPreferences.NotificationMode == NotificationMode.Sms)
    {
    var smsServices = new SmsServices();
    await smsServices.NotifyNewOrderAsync(orderResult.Details, pdfUrl);
    }
    else
    {
    var mailingServices = new MailingServices();
    await mailingServices.NotifyNewOrderAsync(orderResult.Details);
    }
    return RedirectToAction("ThankYou", new { id = orderResult.Details.OrderId } );
    }
    }
    Lo primero que habréis notado es que, obviamente, tendremos controladores con menos dependencias en el constructor, o incluso ninguna. También, las dependencias son creadas sólo cuando las necesitamos, por lo que se evitará la creación innecesaria de objetos que ocurre si nuestro constructor tiene muchos parámetros.

    Sin embargo, hemos creado un monolito, una clase totalmente acoplada a sus dependencias. También hemos disminuido notablemente su legibilidad, al añadir mucha más complejidad al código. Ahora tenemos que encargarnos nosotros de gestionar instancias, sus liberaciones, suministrar las dependencias requeridas... todo un trabajazo que difícilmente será justificable teniendo otras opciones más sencillas como el uso de contenedores de inversión de control.

    Y por último, no olvidemos que no podremos conocer las dependencias de un controlador a no ser que leamos detalladamente el código, puesto que éstas no estarán visibles en el constructor.

    En general, por la gran cantidad de problemas que puede generar y el trabajo que supone, es una técnica que deberíamos usar rara vez en nuestros componentes para consumir dependencias.

    Uso de Service locator

    En segundo lugar, como una evolución de la anterior en un entorno guiado por la inyección de dependencias como es ASP.NET Core, podemos optar por el uso del patrón (y antipatrón al mismo tiempo) service locator.

    Básicamente, esta técnica consigue desacoplar la clase de sus dependencias eliminando los new que la atan a implementaciones específicas mediante el uso de inversión de control. En lugar de instanciar nosotros las dependencias, usaremos un contenedor, en este caso el proveedor de servicios de .NET Core, para que se encargue de crear estos objetos y gestionar su ciclo de vida completo de forma automática.

    Para ello, a nivel de constructor lo único que necesitaríamos en este caso es recibir una referencia hacia el proveedor de servicios que usaremos para obtener dependencias, como sigue:
    public OrderController(IServiceProvider serviceProvider)
    {
    _serviceProvider = serviceProvider;
    }
    Y hecho esto, en las acciones simplemente lo usaremos en los puntos donde necesitemos obtener una referencia a dependencias para utilizarlas desde nuestro código:
    // Before:
    ...
    using (var ctx = new ShoppingCartContext())
    {
    var userServices = new UserServices(ctx);
    var userPreferences = await userServices.GetNotificationPreferencesAsync(CurrentUserId);
    ...
    }

    // Now:
    ...
    var userServices = _serviceProvider.GetService<IUserServices>();
    var userPreferences = await userServices.GetNotificationPreferencesAsync(CurrentUserId);
    ...
    Recordad que para que esto sea posible debemos registrar y configurar apropiadamente los servicios en el contenedor de ASP.NET Core.
    Como ventaja sobre la primera opción que hemos visto (instanciación manual), nuestro controlador estará desacoplado de sus dependencias y el código será más limpio porque todo el trabajo sucio de creación y liberación de objetos lo realizará el contenedor de servicios proporcionado por la infraestructura.

    Eso sí, al igual que ocurría antes, seguiremos teniendo una clase cuyas dependencias no se manifiestan de forma explícita y clara, y para conocerlas habría que leer todo el código.

    Asimismo, añadimos problemas derivados del uso de un service locator, como la posibilidad de que se produzcan errores en tiempo de ejecución, por ejemplo, si las dependencias requeridas en determinados escenarios no pueden ser satisfechas.

    Inyección en parámetros de acciones

    En tercer lugar, como una leve mejora respecto al punto anterior, es interesante saber que ASP.NET Core nos permite aplicar inyección de dependencias directamente como parámetros de las acciones.

    Para ello, basta con decorar con [FromServices] los parámetros que deseamos inyectar desde el contenedor de servicios de ASP.NET Core, como en el siguiente ejemplo:
    [HttpPost]
    public Task<IActionResult> Submit(OrderViewModel orderViewModel,
    [FromServices] IOrderServices orderServices,
    [FromServices] IUserServices userServices,
    [FromServices] IMapper mapper,
    ...
    )
    {
    if(!ModelState.IsValid)
    {
    return View(orderViewModel);
    }

    var orderDto = _mapper.Map<OrderDto>(orderViewModel);
    var orderResult = await orderServices.AddAsync(orderDto);
    if(!orderResult.Success)
    {
    return RedirecToAction("OrderError", new { error = orderResult.Error }));
    }
    var userPreferences = await userServices.GetNotificationPreferencesAsync(CurrentUserId);
    ...
    }
    Bien, aunque básicamente seguimos teniendo los mismos problemas que antes, es cierto que las dependencias quedan un poco más a la vista porque forman parte de la firma de las acciones.

    Por último, es interesante destacar que, obviamente, [FromServices] se puede usar en acciones de controladores que también reciben dependencias en el constructor, lo cual puede sernos de utilidad cuando queramos hilar muy fino en términos de rendimiento. Por ejemplo, podríamos hacer que un controlador reciba en su constructor las dependencias que usaremos de forma general en todas sus acciones, y luego añadir con [FromServices] las dependencias exclusivas de cada acción:
    public class OrderController: Controller
    {
    ...
    public OrderController(IOrderServices orderServices, IUserServices userServices)
    {
    // We need these dependencies in all actions
    _orderServices = orderServices;
    _userServices = userServices;
    }

    public Task<IActionResult> Submit([FromServices] IPaymentServices paymentServices)
    {
    // We use payment services only when submitting an order
    ...
    }
    }

    En resumen

    En este post hemos visto fórmulas para consumir dependencias desde un controlador sin necesidad de que éstas nos sean suministradas en su constructor: instanciación directa, service locator e inyección de dependencias en acciones.

    Estas técnicas pueden ser interesantes en determinados escenarios, pero recordad que si el constructor de un controlador recibe más parámetros de la cuenta, probablemente se trata de un smell que nos está avisando de la ruptura del Principio de Responsabilidad Única y de que estamos creando una clase demasiado compleja. Y como vimos en su momento, la solución a este escenario es refactorizar.

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Blog Bitix

    Permitir o denegar tráfico de red con el firewall UFW en GNU/Linux

    diciembre 24, 2018 12:00

    GNU
    Linux

    Un cortafuegos o firewall hace que un sistema sea más seguro analizando todo el tráfico de red que se recibe y envía impidiéndolo o permitiéndolo según las reglas que se hayan definido. Aunque los routers con los que nos conectamos a internet también hacen la función de firewall es aconsejable instalar un cortafuegos en cada sistema, sobre todo en sistemas portátiles que podemos conectar a redes WIFI ajenas a las que pueden conectarse al mismo tiempo otros muchos usuarios que desconocemos.

    En GNU/Linux un firewall con el que las reglas de tráfico de red se puede definir de forma sencilla es Uncomplicated Firewall o UFW. En la distribución Arch Linux hay que instalar su paquete y habilitarlo para que se inicie con el sistema.

    1
    2
    
    # pacman -S ufw
    
    1
    2
    
    # systemctl enable ufw.service
    # ufw enable

    En la wiki de Arch Linux hay una buena página explicativa de su uso.

    Instalado y activado hay que definir las reglas de tráfico permitidas. Por defecto, se deniega el tŕafico proveniente de un sistema que no sea el local. Por ejemplo, con el cortafuegos activado para que un servidor web sea accesible desde otro equipo en la misma red o desde internet hay crear una regla que permita todo el tráfico entrante los puertos por defecto 80 para http y 443 para https.

    1
    2
    
    # ufw allow 80/tcp
    # ufw allow 443/tcp

    Para permitir acceso al servidor web únicamente desde la red local a la que está conectado el equipo hay que indicar la dirección IP de la red, los casos habituales son 192.168.0.0/24 o 192.168.1.0/24. En vez de usar la directiva allow se puede emplear la directiva deny para denegar el tráfico.

    1
    2
    
    # ufw allow from 192.168.1.0/24 to any port 80 proto tcp
    # ufw allow from 192.168.1.0/24 to any port 443 proto tcp

    Definidas algunas reglas se pueden listar y de forma numerada en el caso de querer eliminar alguna.

    1
    2
    
    # ufw status
    # ufw status numbered

    Para eliminar reglas hay que hacerlo según su número de la lista anterior.

    1
    2
    
    # ufw delete 1
    

    Ciertas aplicaciones usan un puerto conocido como el ejemplo de servidor web en el puerto 443 o 80, SSH en el 22 o descarga y compartición de archivos mediante P2P por Bittorrent que dependiendo de la aplicación se usa uno u otro, en el caso de Transmission es 51413. Sabiendo la aplicación para la que se quiere permitir el tráfico no es necesario conocer su puerto, ufw ya incorpora varias aplicaciones conocidas que se listan y activan con un comando.

    1
    2
    
    # ufw app list
    
    1
    2
    3
    
    # ufw allow Transmission
    # ufw allow from 192.168.1.0/24 to any app "WWW"
    # ufw allow from 192.168.1.0/24 to any app "WWW Secure"

    En el caso de este ejemplo se permite el tráfico para la aplicación Transmission desde cualquier ordenador y a las aplicaciones de servidor web tanto en el puerto seguro como inseguro y en el puerto 8080 que usan algunos servidores de aplicaciones limitando su acceso únicamente desde la red local.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    # ufw status
    Status: active
    To Action From
    -- ------ ----
    Transmission ALLOW Anywhere
    8080/tcp ALLOW 192.168.1.0/24
    WWW Secure ALLOW 192.168.1.0/24
    WWW ALLOW 192.168.1.0/24
    Transmission (v6) ALLOW Anywhere (v6)

    Los comandos anteriores aunque cambiando los valores de los puertos o dirección IP son suficientes para hacer un sistema más seguro no permitiendo tráfico de red y evitando que se puedan establecer conexiones desde otro sistema en la misma red de área local a ciertos puertos de red abiertos sin ser conscientes de que los están.

    En algunos casos como usando una Raspberry Pi sin interfaz gráfica para minimizar el consumo de recursos es necesario conocer o consultar los comandos de ufw.

    Referencia:

    » Leer más, comentarios, etc...

    Arragonán

    El juego de la vida en la Commit Conf

    diciembre 23, 2018 12:00

    En Junio se acababa la fecha límite para enviar propuestas a la Commit Conf, el viñarock del software (evento organizado por las mismas personas que Codemotion hasta 2018). No me apetecía proponer una charla, más a tanto tiempo vista celebrándose en Noviembre; pero finalmente me animé a enviar una propuesta de coding dojo para practicar TDD y pair programming a través del juego de la vida de Conway.

    Tuve bastantes dudas en hacerlo: Lo propuse en una ocasión en un evento de agile y no cuadró, así que no tenía claro como encajaría en un evento más técnico pero un tanto generalista. Sólo había participado/facilitado coding dojos en eventos pequeños. La kata da más juego con más tiempo, a partir de 4 iteraciones o así suele haber más mezcla y se va observando mucha evolución en las soluciones. Y que podía ser un fail tanto por soledad como por superpoblación ;).

    Así que procuré poner una descripción para todos los públicos y finalmente la aceptaron.

    Aunque es una kata que había practicado bastante y la había facilitado en un par de code retreats la prefería rodar. Tuve la oportunidad de hacerlo en un coding dojo interno con compañeros de Zara.com, y me tomé como cierto rodaje las 2 primeras iteraciones en el Global day of Code Retrat de A Coruña.

    De modo que pude acortar y refinar un poco la pequeña presentación para ayudarme a contextualizar, ya que suponía que habría gente que nunca habría participado en un coding dojo o hubiera utilizado katas como ejercicios de práctica deliberada.

    Y mientras que en los code retreats prefiero ir improvisando constraints dependiendo de lo que se va viendo en cada iteración. En este caso como no había mucho tiempo ni margen de maniobra, preferí llevarlo preparado en la presentación.

    Siendo el segundo día del evento y a primera hora de la mañana había alrededor de una treintena de personas. Y que en general vi a la gente muy concentrada en el ejercicio, mucha comunicación entre pares y tríos, personas desconocidas relacionándose entre ellas, gente descubriendo el testing automático o haciendo sus pinitos con TDD, programando en distintos lenguajes…

    Como anécdotas os diré que yo tenía que gritar para dar instrucciones y a veces me sentía ignorado (eso que no tengo mal volumen de voz), y que uno de los fotógrafos de la organización me preguntó si era algún tipo de concurso al ver a todo el mundo tan engorilado.

    Así que, aún más acordándome de mis dudas en Junio, al acabar me quedé con un buen sabor de boca :).

    » Leer más, comentarios, etc...

    Fixed Buffer

    ¡Feliz navidad!

    diciembre 21, 2018 01:00

    Feliz Navidad

    Bueno, han pasado ya 3 meses desde que empece con este proyecto, y la verdad, cuando empece no pensaba que las cosas iban a ir tan bien como están yendo! Casi mil personas habéis entrado y leído el contenido, mis ideas (locas y no tan locas). Muchas gracias a todos por la gran acogida que me habéis dado, ¡la verdad es que es un gran apoyo para seguir!

    Ahora, toca parar y coger fuerzas, disfrutar de la familia, el turrón y los villancicos. Aprovecho esta entrada para desearos unas felices fiestas, y un prospero año 2019.

    ¡Nos vemos en enero con las pilas cargadas y muchas ideas nuevas, muchas cosas que contar, y alguna colaboración que se esta gestando!

    **La entrada ¡Feliz navidad! se publicó primero en Fixed Buffer.**

    » Leer más, comentarios, etc...

    Blog Bitix

    Exponer las métricas de Hystrix en Grafana con Prometheus de una aplicación Spring Boot

    diciembre 21, 2018 10:30

    Java
    Spring

    Hystrix es una implementación del patrón circuit breaker para hacer que un servicio sea tolerante fallos cuando aquellos que utiliza fallan. Es conveniente tener una herramienta de monitorización para conocer el estado del sistema y actuar pronto o conocer si el comportamiento del sistema es diferente al hacer algún cambio. Hystrix proporciona varios datos como el número de peticiones realizadas, cuantas han fallado o cual es el estado del patrón circuit breaker. Prometheus es una herramienta de monitorización que recoge las métricas de los servicios de forma periódica y las almacena para una consulta posterior, Grafana es otra herramienta de monitorización que permite visualizar en gráficas las métricas almacenadas en Prometheus y observar los valores a lo largo del tiempo.

    En el artículo Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix explicaba como crear un servicio de Spring que implementa el patrón circuit breaker con Hystrix y en el artículo Monitorizar una aplicación Java con Spring Boot, Micrometer, Prometheus y Grafana explicaba como exportar las métricas de Spring Boot Actuator a Prometheus y como crear gráficas en Grafana.

    Hystrix ofrece un dashboard algo espartano con los datos de Hystrix de la propia aplicación. Los datos de las métricas de Hystrix por defecto no se exponen en Spring Boot Actuator pero se pueden añadir creando un bean HystrixMetricsBinder en la configuración de Spring.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    package io.github.picodotdev.blogbitix.springcloud.client;
    ...
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableCircuitBreaker
    @EnableHystrixDashboard
    public class Main implements CommandLineRunner {
    ...
    @Bean
    HystrixMetricsBinder hystrixMetricsBinder() {
    return new HystrixMetricsBinder();
    }
    ...
    public static void main(String[] args) {
    SpringApplication application = new SpringApplication(Main.class);
    application.setApplicationContextClass(AnnotationConfigApplicationContext.class);
    SpringApplication.run(Main.class, args);
    }
    }
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    ...
    dependencies {
    // Spring
     def excludeSpringBootStarterLogging = { exclude(group: 'org.springframework.boot', module: 'spring-boot-starter-logging') }
    compile('org.springframework.boot:spring-boot-starter', excludeSpringBootStarterLogging)
    compile('org.springframework.boot:spring-boot-starter-web', excludeSpringBootStarterLogging)
    compile('org.springframework.boot:spring-boot-starter-log4j2', excludeSpringBootStarterLogging)
    compile('org.springframework.boot:spring-boot-starter-actuator', excludeSpringBootStarterLogging)
    compile('org.springframework.cloud:spring-cloud-starter-config', excludeSpringBootStarterLogging)
    compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client', excludeSpringBootStarterLogging)
    compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon', excludeSpringBootStarterLogging)
    compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix', excludeSpringBootStarterLogging)
    compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix-dashboard', excludeSpringBootStarterLogging)
    compile('io.micrometer:micrometer-registry-prometheus:1.0.8')
    ...
    }
    

    Una vez hecho esto Spring en el endpoint /actuator/metrics se exponen las métricas de Hystrix, si además se configura Spring añadiendo la dependencia io.micrometer:micrometer-registry-prometheus para exponer las métricas en el formato para que Prometheus las recolecta también se añaden en el endpoint /actuator/prometheus.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    $ curl http://client.127.0.0.1.xip.io:8095/actuator/metrics
    $ curl http://client.127.0.0.1.xip.io:8095/actuator/metrics/hystrix.requests
    $ curl http://client.127.0.0.1.xip.io:8095/actuator/metrics/hystrix.circuit.breaker.open
    $ curl http://client.127.0.0.1.xip.io:8095/actuator/metrics/hystrix.fallback
    $ http://client.127.0.0.1.xip.io:8095/actuator/metrics/hystrix.latency.total
    $ http://client.127.0.0.1.xip.io:8095/actuator/metrics/hystrix.errors
    $ curl http://client.127.0.0.1.xip.io:8095/actuator/prometheus
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    {
    "names": [
    ...
    "hystrix.requests",
    "hystrix.command.other",
    "hystrix.circuit.breaker.open",
    "hystrix.fallback",
    "hystrix.latency.execution",
    "hystrix.execution",
    "hystrix.latency.total",
    "hystrix.threadpool.concurrent.execution.rolling.max",
    "hystrix.errors",
    "hystrix.threadpool.concurrent.execution.current",
    ...
    ]
    }
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    
    ...
    # HELP hystrix_threadpool_concurrent_execution_current
    # TYPE hystrix_threadpool_concurrent_execution_current gauge
    hystrix_threadpool_concurrent_execution_current{group="ClientService",key="get",threadpool="ClientService",} 0.0
    # HELP hystrix_requests_total
    # TYPE hystrix_requests_total counter
    hystrix_requests_total{group="ClientService",key="get",} 137.0
    # HELP hystrix_circuit_breaker_open
    # TYPE hystrix_circuit_breaker_open gauge
    hystrix_circuit_breaker_open{group="ClientService",key="get",} 0.0
    # HELP hystrix_latency_execution_seconds_max
    # TYPE hystrix_latency_execution_seconds_max gauge
    hystrix_latency_execution_seconds_max{group="ClientService",key="get",} 0.472
    # HELP hystrix_latency_execution_seconds
    # TYPE hystrix_latency_execution_seconds summary
    hystrix_latency_execution_seconds_count{group="ClientService",key="get",} 137.0
    hystrix_latency_execution_seconds_sum{group="ClientService",key="get",} 2.839
    # HELP hystrix_execution_total Execution results. See https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#command-execution-event-types-comnetflixhystrixhystrixeventtype for type definitions
    # TYPE hystrix_execution_total counter
    hystrix_execution_total{event="emit",group="ClientService",key="get",} 0.0
    hystrix_execution_total{event="success",group="ClientService",key="get",} 137.0
    hystrix_execution_total{event="failure",group="ClientService",key="get",} 0.0
    hystrix_execution_total{event="timeout",group="ClientService",key="get",} 0.0
    hystrix_execution_total{event="bad_request",group="ClientService",key="get",} 0.0
    hystrix_execution_total{event="short_circuited",group="ClientService",key="get",} 0.0
    hystrix_execution_total{event="thread_pool_rejected",group="ClientService",key="get",} 0.0
    hystrix_execution_total{event="semaphore_rejected",group="ClientService",key="get",} 0.0
    # HELP hystrix_threadpool_concurrent_execution_rolling_max
    # TYPE hystrix_threadpool_concurrent_execution_rolling_max gauge
    hystrix_threadpool_concurrent_execution_rolling_max{group="ClientService",key="get",threadpool="ClientService",} 0.0
    # HELP hystrix_command_other_total Other execution results. See https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#other-command-event-types-comnetflixhystrixhystrixeventtype for type definitions
    # TYPE hystrix_command_other_total counter
    hystrix_command_other_total{event="exception_thrown",group="ClientService",key="get",} 0.0
    hystrix_command_other_total{event="response_from_cache",group="ClientService",key="get",} 0.0
    hystrix_command_other_total{event="cancelled",group="ClientService",key="get",} 0.0
    hystrix_command_other_total{event="collapsed",group="ClientService",key="get",} 0.0
    hystrix_command_other_total{event="command_max_active",group="ClientService",key="get",} 0.0
    # HELP hystrix_latency_total_seconds_max
    # TYPE hystrix_latency_total_seconds_max gauge
    hystrix_latency_total_seconds_max{group="ClientService",key="get",} 0.474
    # HELP hystrix_latency_total_seconds
    # TYPE hystrix_latency_total_seconds summary
    hystrix_latency_total_seconds_count{group="ClientService",key="get",} 137.0
    hystrix_latency_total_seconds_sum{group="ClientService",key="get",} 2.857
    # HELP hystrix_errors_total
    # TYPE hystrix_errors_total counter
    hystrix_errors_total{group="ClientService",key="get",} 0.0
    # HELP hystrix_fallback_total Fallback execution results. See https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#command-fallback-event-types-comnetflixhystrixhystrixeventtype for type definitions
    # TYPE hystrix_fallback_total counter
    hystrix_fallback_total{event="fallback_emit",group="ClientService",key="get",} 0.0
    hystrix_fallback_total{event="fallback_success",group="ClientService",key="get",} 0.0
    hystrix_fallback_total{event="fallback_failure",group="ClientService",key="get",} 0.0
    hystrix_fallback_total{event="fallback_rejection",group="ClientService",key="get",} 0.0
    hystrix_fallback_total{event="fallback_missing",group="ClientService",key="get",} 0.0
    ...

    Con estas métricas recolectadas por Prometheus se pueden visualizar en gráficas por Grafana. Hay algunos paneles de Grafana para Hystrix como el 7145 pero que necesitan ser adaptados según la nomenclatura de las propiedades expuestas por Spring Boot. En este caso se monitoriza el número de peticiones realizadas, el tiempo de latencia, si los circuitos están abiertos, los fallos, éxitos y tiemouts así como el estado de los thread pools que utiliza Hystrix para realizar las peticiones de un cliente a un servicio.

    Panel de Grafana para métricas de Hystrix

    Exponer las métricas en una aplicación de Spring Boot para Prometheus es muy sencillo y con Grafana se puede observar el estado del sistema de forma tan detallada como lo sean las métricas expuestas por la aplicación. Por defecto Spring Boot ya expone una buena cantidad de métricas del estado del servicio como uso de CPU, memoria, hilos o recolector de basura.

    1
    2
    3
    4
    
    $ ./gradlew discoveryserver:run --args="--port=8761"
    $ ./gradlew configserver:run --args="--port=8090"
    $ ./gradlew service:run --args="--port=8080"
    $ ./gradlew client:run

    El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradle-run.sh.

    » Leer más, comentarios, etc...

    Fixed Buffer

    ClosedXML, una manera fácil de dar formato a nuestros .xlsx

    diciembre 18, 2018 09:00

    closedxml

    Hace ya unos meses que empezamos en este blog, con una entrada sobre ClosedXML, pero si que es cierto, que se puedo quedar un poco corta, aunque daba las ideas más básicas. Hoy, vamos a ampliar esa pequeña píldora sobre ClosedXML aprendiendo a dar formato a nuestras hojas.

    Nuestro objetivo

    Vamos a intentar hacer una tabla de colores como la que tenemos a continuación:

    Resultado ClosedXML

    Creación del proyecto

    Por cambiar un poco desde de la primera parte, vamos a crear un proyecto en .NetCore (ahora que también sabemos instalar el framework en linux o como depurar sobre SSH), para ello, creamos un proyecto de consola:

    consola .net core

    O desde el CLI de NetCore:

    dotnet new console

    Lo siguiente que tenemos que hacer es añadir el paquete ClosedXML a través de nuget. Esto se puede hacer a través de la “Consola de Administrador de Paquetes” con el comando:

    PM->Install-Package ClosedXML

    O también desde el CLI de NetCore:

    dotnet add package ClosedXML

    Como siempre, utilizando el administrador que integra VS:

    nuget

    Generando y formateando el fichero con ClosedXML

    Teniendo claro el objetivo, vamos a ponernos con en faena y analizar el código que hemos utilizado:

     
    using ClosedXML.Excel;
    using System.Collections.Generic;
    
    namespace PostClosedXML2
    {
      class Program
      {    
        static IEnumerable GetColors()
        {
          yield return XLColor.Red;
          yield return XLColor.Amber;
          yield return XLColor.AppleGreen;
          yield return XLColor.AtomicTangerine;
          yield return XLColor.BallBlue;
          yield return XLColor.Bittersweet;
          yield return XLColor.CalPolyPomonaGreen;
          yield return XLColor.CosmicLatte;
          yield return XLColor.DimGray;
          yield return XLColor.ZinnwalditeBrown;
        }
    
        static void Main(string[] args)
        {
          using (var workbook = new XLWorkbook())
          {
            //Generamos la hoja
            var worksheet = workbook.Worksheets.Add("FixedBuffer");
            //Generamos la cabecera
            worksheet.Cell("A1").Value = "Nombre";
            worksheet.Cell("B1").Value = "Color";
    
            //-----------Le damos el formato a la cabecera----------------
            var rango = worksheet.Range("A1:B1"); //Seleccionamos un rango
            rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
            rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
            rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; //Alineamos horizontalmente
            rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente
            rango.Style.Font.FontSize = 14; //Indicamos el tamaño de la fuente
            rango.Style.Fill.BackgroundColor = XLColor.AliceBlue; //Indicamos el color de background
            
    
            //-----------Genero la tabla de colores-----------
            int nRow = 2;
            foreach (var color in GetColors())
            {
              worksheet.Cell(nRow, 1).Value = color.ToString(); //Indicamos el valor en la celda nRow, 1
              worksheet.Cell(nRow, 2).Style.Fill.BackgroundColor = color; //Cambiamos el color de background de la celda nRow,2
              nRow++;
            }
    
            //Aplico los formatos
            rango = worksheet.Range(2, 1, nRow-1, 2); //Seleccionamos un rango
            rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
            rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
            rango.Style.Font.SetFontName("Courier New"); //Utilizo una fuente monoespacio
            rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right; //Alineamos horizontalmente
            rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente
    
    
            worksheet.Columns(1, 2).AdjustToContents(); //Ajustamos el ancho de las columnas para que se muestren todos los contenidos
    
            workbook.SaveAs("CellFormating.xlsx");  //Guardamos el fichero
          }
        }
      }
    }
    

    Lo primero de todo, hemos creado una lista con los colores que queremos utilizar para nuestra tabla. Para ello, utilizamos los colores propios del paquete, que trae una lista enorme de colores:

     
    static IEnumerable GetColors()
    {
      yield return XLColor.Red;
      yield return XLColor.Amber;
      yield return XLColor.AppleGreen;
      yield return XLColor.AtomicTangerine;
      yield return XLColor.BallBlue;
      yield return XLColor.Bittersweet;
      yield return XLColor.CalPolyPomonaGreen;
      yield return XLColor.CosmicLatte;
      yield return XLColor.DimGray;
      yield return XLColor.ZinnwalditeBrown;
    }
    

    Una vez que tenemos eso controlado, vamos a entrar al jugo del código. En primero lugar, podemos ver que todo el código esta dentro de un using:

     
    using (var workbook = new XLWorkbook())
    {
       //Código
    }
    

    Con esto conseguimos que los recursos utilizados para generar el fichero se liberen correctamente al acabar de usarlos. Lo siguiente que vemos, es como generamos la hoja dentro del libro:

     
    //Generamos la hoja
    var worksheet = workbook.Worksheets.Add("FixedBuffer");
    

    Una vez que tenemos la hoja creada, añadimos las dos primeras celdas que nos servirán de cabecera para la tabla:

     
    //Generamos la cabecera
    worksheet.Cell("A1").Value = "Nombre";
    worksheet.Cell("B1").Value = "Color";
    

    Vamos a darle formato a la cabecera de la tabla:

     
    //-----------Le damos el formato a la cabecera----------------
    var rango = worksheet.Range("A1:B1"); //Seleccionamos un rango
    rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
    rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
    rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; //Alineamos horizontalmente
    rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente
    rango.Style.Font.FontSize = 14; //Indicamos el tamaño de la fuente
    rango.Style.Fill.BackgroundColor = XLColor.AliceBlue; //Indicamos el color de background
    

    Para trabajar con mayor comodidad, seleccionamos un rango (A1:B1), de modo que todo lo que hagamos sobre el rango, se va a aplicar a todas las celdas del rango, en este caso a A1 y a B1. Lo que hacemos es asignarle los bordes extreriores e interiores, alinear el contenido horizontal y verticalemnte, le modificamos el tamaño de la fuente, y por ultimo, pintamos el color de fondo. Sin ningun problema, generamos la tabla de colores:

     
    
    //-----------Genero la tabla de colores-----------
    int nRow = 2;
    foreach (var color in GetColors())
    {
        worksheet.Cell(nRow, 1).Value = color.ToString(); //Indicamos el valor en la celda nRow, 1
        worksheet.Cell(nRow, 2).Style.Fill.BackgroundColor = color; //Cambiamos el color de background de la celda nRow,2
        nRow++;
    }
    
    

    Como en casos anteriores, asignamos un valor a una celda, y un backcolor a la otra. Con esto, solo nos queda aplicar los formatos y guardar. Vamos a aplicar los formatos:

     
    //Aplico los formatos
        rango = worksheet.Range(2, 1, nRow-1, 2); //Seleccionamos un rango
        rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
        rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
        rango.Style.Font.SetFontName("Courier New"); //Utilizo una fuente monoespacio
        rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right; //Alineamos horizontalmente
        rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente
    

    Pero ojo, esta vez, hemos asignado también la fuente:

     
    rango.Style.Font.SetFontName("Courier New");
    

    Esto lo hacemos para que la tabla nos quede perfectamente cuadrada al utilizar letras monoespacio. Por ultimo, queremos que las celdas se queden perfectamente a la vista todas, aunque el contenido sea mayor que el ancho de la columna, por lo tanto, vamos a ajustar el ancho de columnas. Eso lo hacemos con:

     
    worksheet.Columns(1, 2).AdjustToContents(); //Ajustamos el ancho de las columnas para que se muestren todos los contenidos
    

    Lo que conseguimos así, es que el ancho de las columnas 1 y 2, se ajuste de modo que se vea el contenido de todas las celdas. Con todo hecho, simplemente guardamos:

     
    workbook.SaveAs("CellFormating.xlsx");  //Guardamos el fichero
    

    Y al abrir el excel, veremos que se nos ha quedado una tabla como la que presentábamos al principio. Como siempre, si queréis probar el código fuente, os dejo el enlace al repositorio de GitHub. Esto solo es una pequeña pincelada de todo los que se puede conseguir con ClosedXML y con OpenXML en general. En futuras entradas, seguiremos ampliando su uso.

    **La entrada ClosedXML, una manera fácil de dar formato a nuestros .xlsx se publicó primero en Fixed Buffer.**

    » Leer más, comentarios, etc...

    Adrianistán

    Algoritmos genéticos: un caso práctico

    diciembre 18, 2018 08:32

    Existen muchos tipos de algoritmos. Hoy vamos a hablar de un tipo de algoritmo no demasiado conocido, los algoritmos genéticos, que son de la familia de los algoritmos evolutivos. Además veremos su aplicación práctica en el vectorizado de imágenes con un programa que he realizado en Rust.

    ¿Qué es un algoritmo genético?

    Un algoritmo genético es un algoritmo inspirado en el mecanismo de la naturaleza de la evolución biológica, descrita por Darwin allá por 1859 en el libro El Origen de las Especies. La idea es tener un conjunto de individuos (posibles soluciones). Estos individuos son evaluados para ver qué tan buenos son. Quedarnos con los mejores y proceder a la creación de nuevos individuos como resultados de la recombinación genética de dos individuos (como en la reproducción sexual). Posteriormente añadir alguna mutación genética para explorar nuevas posibilidades ligeramente diferentes. Proceder de nuevo a la selección natural hasta que tengamos individuos lo suficientemente buenos para nosotros.

    Normalmente no son los algoritmos más eficientes ni tienen por qué dar un resultado óptimo, pero pueden servir para dar aproximaciones bastante buenas al resultado óptimo. Existen estrategias para mejorar los algoritmos genéticos como la gestión de la antigüedad de los individuos, para evitar máximos locales; la conservación de individuos con mal desempeño, para conservar mayor variedad genética; …

    Como veremos más adelante, uno de los elementos más importante de estos algoritmos es la función de evaluación, que muchas veces es más complicada de programar de lo que parece.

    Un caso práctico: vectorizado de imágenes

    Para ver como estos conceptos teóricos funcionan en la práctica, vamos a hacer un programa que vectorice imágenes. ¿Esto qué es? Digamos que hay dos tipos de imágenes en informática. Por un lado tenemos las imágenes que almacenan los colores en píxeles (rasterizadas) y por otro lado aquellas que almacenan la imagen como fórmulas matemáticas, que se aplican cuando se quiere ver la imagen (vectoriales). Los formatos más comunes de imágenes rasterizadas son JPEG, PNG, GIF y WEBP. Los formatos más comunes de imágenes vectoriales son SVG y AI.

    A las imágenes rasterizadas no se les puede hacer zoom hasta el infinito, se ven borrosas
    A las imágenes vectoriales se les puede hacer zoom infinito, no pierden calidad

    Pasar de una imagen vectorial a una rasterizada es trivial, pero el proceso inverso no lo es, y es justo donde vamos a aplicar el algoritmo genético.

    En nuestro caso, vamos a tomar siluetas, negras sobre fondo blanco y tratar de vectorizarlas con curvas de Bézier.


    Ejemplo de ejecución en la versión final de Mendel Vectorizer. La curva azul es la imagen vectorial generada.

    Curvas de Bézier

    En los años 60, Pierre Bézier, que trabajaba para Renault, diseñó un tipo de curva para el diseño asistido por ordenador (CAD). Estas son las conocidas como curvas de Bézier. Nuestro algoritmo tratará de encontrar la curva de Bézier más similar a la curva de la imagen rasterizada. Para ello necesitamos un punto de inicio, un punto final y dos puntos de control.

    Curva cúbica de Bézier

    En nuestro algoritmo, las curvas serán los individuos, y las coordenadas de los puntos de control serán los genes (habrá 4 genes por tanto).

    Este es el código que he usado para definir las curvas de Bézier en Rust.

    #[derive(Copy, Clone)]
    pub struct Point {
        pub x: f64,
        pub y: f64,
    }
    impl Point {
        pub fn distance(&self, other: &Point) -> f64 {
            ((self.x - other.x).powf(2.0) + (self.y - other.y).powf(2.0)).sqrt()
        }
    
        pub fn middle(&self, other: &Point) -> Point {
            Point {
                x: (self.x + other.x) / 2.0,
                y: (self.y + other.y) / 2.0,
            }
        }
    }
    
    #[derive(Clone)]
    /* Bezier */
    pub struct Bezier {
        pub start: Point,
        pub control1: Point,
        pub control2: Point,
        pub end: Point,
    }
    
    impl<'a> Bezier {
        pub fn iter(&self) -> BezierIter {
            BezierIter {
                bezier: self,
                position: 0.0,
            }
        }
    }
    
    pub struct BezierIter<'a> {
        bezier: &'a Bezier,
        position: f64,
    }
    
    impl<'a> Iterator for BezierIter<'a> {
        type Item = Point;
    
        fn next(&mut self) -> Option<Point> {
            if self.position > 1.0 {
                return None;
            }
            let x = self.bezier.start.x * (1.0 - self.position).powf(3.0)
                + 3.0 * self.bezier.control1.x * self.position * (1.0 - self.position).powf(2.0)
                + 3.0 * self.bezier.control2.x * self.position.powf(2.0) * (1.0 - self.position)
                + self.bezier.end.x * self.position.powf(3.0);
    
            let y = self.bezier.start.y * (1.0 - self.position).powf(3.0)
                + 3.0 * self.bezier.control1.y * self.position * (1.0 - self.position).powf(2.0)
                + 3.0 * self.bezier.control2.y * self.position.powf(2.0) * (1.0 - self.position)
                + self.bezier.end.y * self.position.powf(3.0);
            self.position += 0.01;
            Some(Point { x, y })
        }
    }

    Encontrar puntos iniciales

    El primer paso de nuestro algoritmo es buscar los puntos iniciales (y finales) de las curvas. En definitiva esto es un problema de búsqueda de esquinas.

    Ejemplo de aplicación de FAST9 a una imagen

    Existen varios algoritmos de búsqueda de esquinas: Harris, FAST9, FAST12, … No obstante, no tuve muy buenos resultados en las imágenes con las que trabajaba. Así que esta parte del algoritmo se la dejo al humano. El humano se encargará de poner los puntos, en orden, que tiene que tratar de unir el algoritmo con curvas de Bézier.

    Función de evaluación

    Muchas veces la función de evaluación es el punto más delicado de estos algoritmos. En este caso la idea fundamental es identificar si la curva de Bézier está encima de la curva original. Para ello tomamos 100 puntos equidistantes de la curva de Bézier (con la ecuación paramétrica de la curva es muy sencillo).

    \(\mathbf{B}(t)=\mathbf{P}_0(1-t)^3+3\mathbf{P}_1t(1-t)^2+3\mathbf{P}_2t^2(1-t)+\mathbf{P}_3t^3 \mbox{ , } t \in [0,1].\)

    Estos puntos se comparan con la imagen real, si ahí en la imagen original había una línea no pasa nada, si no, se resta 100. De este modo se penaliza gravemente salirse de la curva. Esto se hace así ya que la otra opción evidente (bonificar el estar sobre en la línea) podría dar lugar a resultados inesperados.

    Ejemplificación de la función de evaluación. Los puntos amarillos están dentro de la línea. Los puntos verdes están fuera de la línea, penalizando a la curva en su “adaptación al medio”.

    Pongamos como ejemplo una función de evaluación que bonifique por estar sobre la línea y no penalice por salirse de esta. Una línea bien adaptada a estas condiciones podría recorrerse toda la imagen, cruzando todas las líneas posibles, generando un garabato totalmente inútil pero muy bueno según esta función. Por ello, nuestra función de evaluación se basa en penalizar las salidas de la línea.

    La función de evaluación presentada no es perfecta, ya que puede pasar de largo el punto final y dar la vuelta. Esta curva es más larga que la óptima, pero al estar completamente dentro de la línea negra original, la función de evaluación no la puede clasificar como peor que otras alternativas. Para solventar este problema una idea es que la longitud de la curva tenga una implicación en la función. No obstante, el cálculo de la longitud de una curva de Bezier es demasiado complejo y no lo he codificado. También podría aproximarse a través de segmentos rectos entre los 100 puntos calculados anteriormente.

    Ejemplo de curva con puntuación máxima pero no óptima desde el punto de vista humano
    pub fn evaluate(image: &amp;GrayImage, line: &amp;Bezier) -> f64 {
        let mut eval = 0.0;
        for point in line.iter() { // va generando los 100 puntos equidistantes
            let x = point.x as u32;
            let y = point.y as u32;
            if image.in_bounds(x, y) {
                let pixel = image.get_pixel(x, y);
                if pixel.data[0] > 200 {
                    eval -= 100.0;
                }
            } else {
                eval -= 100.0;
            }
        }
        eval
    }

    Selección natural

    La función de selección natural deja únicamente las 500 mejores curvas, de acuerdo a la función de evaluación, es decir, las mejor adaptadas de momento. Para la ordenación, Rust usa un algoritmo denominado Timsort, con coste O(nlogn). Sin embargo, en todo el algoritmo trabajamos con poblciones finitas, por lo que puede asimilarse a una constante, con coste O(1). 

    pub fn natural_selection(image: &GrayImage, mut population: Vec<Bezier>) -> Vec<Bezier> {
        population.sort_by(|a, b| {
            let a = evaluate(&image, &a);
            let b = evaluate(&image, &b);
            b.partial_cmp(&a).unwrap()
        });
    
        population.into_iter().take(500).collect()
    }

    Población inicial

    La población inicial se forma con 1000 curvas generadas con parámetros totalmente aleatorios. Los valores de cada coordenada, eso sí, están comprendidos entre -d y d siendo d la distancia en línea recta entre los puntos de inicio y final.

    let mut population = Vec::new();
            let mut rng = thread_rng();
            let distancia = start.distance(&amp;end);
            for _ in 0..1000 {
                let xrand: f64 = rng.gen_range(-distancia, distancia);
                let yrand: f64 = rng.gen_range(-distancia, distancia);
                let mut control1 = start.middle(&amp;end);
                control1.x += xrand;
                control1.y += yrand;
                let mut control2 = start.middle(&amp;end);
                control2.x += xrand;
                control2.y += yrand;
                population.push(Bezier {
                    start,
                    end,
                    control1,
                    control2,
                });
            }

    Recombinado

    El proceso de recombinación permite mezclar los mejores especímenes tratando de conseguir uno mejor. Este algoritmo genético es de tipo RCGA (Real Coded Genetic Algorithm) ya que los genes son números reales, en contraposición a los típicos genes binarios.
    Para estos algoritmos existen distintas variantes, aquí se usa el sistema de blend. El sistema de blend implica que de entre los dos padres se toman los valores mínimos y máximos para cada coordenada. Posteriormente se elige un nuevo valor de forma aleatoria con la condición de que esté dentro del rango de mínimo y máximo definido anteriormente.

    // CROSSOVER
                
                let mut i: usize = 0;
                let mut babies = Vec::new();
                while i < GOOD_ONES {
                    // 250 extra
                    let line1 = &population[i];
                    let line2 = &population[i + 1];
    
                    let min_x = line1.control1.x.min(line2.control1.x);
                    let max_x = line1.control1.x.max(line2.control1.x);
                    let min_y = line1.control1.y.min(line2.control1.y);
                    let max_y = line1.control1.y.max(line2.control1.y);
                    let control1 = Point {
                        x: rng.gen_range(min_x, max_x),
                        y: rng.gen_range(min_y, max_y),
                    };
    
                    let min_x = line1.control2.x.min(line2.control2.x);
                    let max_x = line1.control2.x.max(line2.control2.x);
                    let min_y = line1.control2.y.min(line2.control2.y);
                    let max_y = line1.control2.y.max(line2.control2.y);
                    let control2 = Point {
                        x: rng.gen_range(min_x, max_x),
                        y: rng.gen_range(min_y, max_y),
                    };
    
                    babies.push(Bezier {
                        start,
                        end,
                        control1,
                        control2,
                    });
    
                    i += 2;
                }
                population.append(&mut babies);

    Mutación

    La fase de mutación permite generar pequeñas variaciones aleatorias respecto a la población. Afecta al 10% de la población aunque solo afecta a una coordenada a la vez.

    Al ser un algoritmo RCGA, no vale simplemente con cambiar el valor de un bit. El enfoque utilizado en este algoritmo es el de una distribución normal de cambios de media 0. La distribución tiene la forma N(0,d/2). Esto implica que en la mayoría de las ocasiones la variación (tanto positiva como negativa) en la coordenada será bastante pequeña.

    Distribución normal, aunque esta no es de media 0
    // MUTATION
                // TASA DE MUTACION DEL 10%
                population = population
                    .into_iter()
                    .map(|mut line| {
                        if rng.gen::<f64>() < 0.10 {
                            let normal = Normal::new(0.0, distancia / 2.0);
                            let mutation_where: u32 = rng.gen_range(1, 5);
                            // Solo muta un gen, respecto a una Normal
                            match mutation_where {
                                1 => line.control1.x += rng.sample(normal),
                                2 => line.control1.y += rng.sample(normal),
                                3 => line.control2.x += rng.sample(normal),
                                4 => line.control2.y += rng.sample(normal),
                                _ => (),
                            }
                        }
                        line
                    })
                    .collect();

    El programa: Mendel Vectorizer

    El programa definitivo, Mendel Vectorizer, disponible en GitHub, tiene más detalles. La interfaz gráfica está hecha usando GTK, la interfaz de línea de comandos usa Clap y el algoritmo se ejecuta en paralelo usando paso de mensajes entre los hilos. El programa genera un fichero SVG resultado que puede ser abierto en programas como Inkscape o Adobe Illustrator.

    El fichero generado por Mendel Vectorizer en Inkscape

    Para usar Mendel Vectorizer en tu ordenador, sigue los siguientes pasos.

    Descárgate una copia del código con Git. Compila con Rust (una edición que soporte la edición 2018, mínimo la 1.31) y ejecútalo.

    git clone https://github.com/aarroyoc/mendel-vectorizer
    cd mendel-vectorizer
    cargo build
    cargo run

    También podéis descargaros la memoria en PDF del programa.

    La entrada Algoritmos genéticos: un caso práctico se publicó primero en Adrianistán.

    » Leer más, comentarios, etc...

    Metodologías ágiles. De lo racional a la inspiración.

    Si llevo Kanban, también llevo los principios ágiles

    diciembre 17, 2018 10:59

    Recientemente se está hablando de si kanban es una metodología ágil o no. Creo que la primera vez que me lo plantee fue al leer una serie de magníficos posts de Michael Sahota sobre la cultura de las organizaciones. NOTA: Es realmente curioso que un tema tan específico haya llegado a una revista como Forbes. Steve Denning, al que debeis seguir atentamente si no lo haceis ya, está "fusilando" las

    » Leer más, comentarios, etc...

    Meta-Info

    ¿Que es?

    Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

    rss subscripción

    Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
    planetacodigo

    planetacodigo

    Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

    Idea: Juanjo Navarro

    Diseño: Albin