VISITAS:

jueves, 22 de diciembre de 2016

¿Typescript en 30 minutos?

¿Es posible aprender Typescript en 30 minutos o menos? Depende, si ya conoces algo de Javascript y también algún lenguaje de programación orientado a objetos como Java o C#, seguro que sí. Si no, te costará un poco más, pero no mucho. Te lo voy a demostrar en este post. Abre una nueva pestaña en playground Pon el cronómetro en marcha y empieza a leer... Empezamos con el lenguaje de moda y, quizás, el lenguaje del futuro para la web.

Qué es Typescript

Typescript es un lenguaje de programación desarrollado y mantenido por Microsoft. La primera versión pública salió en Octubre de 2012 y la presentó Anders Hejlsberg, el creador de lenguajes tan conocidos como Turbo Pascal, Delphi y C#.
Typescript es un superconjunto de Javascript, que no se utiliza directamente como tal, sino que se convierte a Javascript, que ya se puede utilizar en cualquier navegador o en NodeJS. Cualquier código Javascript válido es también código Typescript válido. Soporta Javascript ES5, ES2015 y ES2016, e incluso está previsto el soporte ES2017.
Typescript proporciona chequeo de tipos, interfaces, clases y módulos, entre otros mecanismos propios de los lenguajes orientados a objetos.
Existe una web que proporciona los ahora tan de moda, playground para aprender y experimentar con el lenguaje Typescript.
Veamos un poco más despacio las nuevas características del lenguaje Typescript.

Tipos

Typescript soporta chequeo de tipos estático que permite detectar muchos errores en la fase de desarrollo.
Los chequeos de tipos se realizan en la fase de compilación (realmente conversión) de Typescript a Javascript.
Las anotaciones de tipos son opcionales. Si no se especifica un tipo, entonces no se efectuará ningún chequeo.
Los tipos soportados son: number, boolean, string y los arrays de estos tipos. También existe el tipo any para cualquier tipo, incluidas las estructuras de datos u objetos.
Ejemplo:

function multiply(a: number, b: number) : number {
    return a * b;
}

Funciones arrow

En Javascript podemos crear funciones anónimas de la siguiente forma:

setTimeout(function() {
    console.log("Timed out function");
}, 500);

Typescript permite una sintaxis, denominada arrow, para definir de una forma más sencilla las funciones anónimas:

setTimeout( () => { console.log("Timed out function"); }, 500);

Variables con ámbito de bloque

En Javascript, las variables declaradas con var tienen ámbito de la función donde se declaran. De hecho, aunque se declare la función en un punto intermedio de la función, Javascript traslada esa declaración al principio de la función. Esto puede llevar a situaciones extrañas, como por ejemplo:

var x = 3;
function show(param) {
    if (param == true) {
        return x;
    } else {
        var x = 4;
        return x;
    }
}
console.log(show(true));

Aunque podríamos pensar que el resultado en la consola es 3, realmente veremos "undefined". ¿Por qué? Porque el motor Javascript traslada la declaración var x al principio de la función. Es como si fuera:

var x = 3;
function show(param) {
    var x;
    if (param == true) {
        return x;
    } else {
        x = 4;
        return x;
    }
}
console.log(show(true));

Así se entiende porqué el valor retornado es undefined.

Typescript introduce la declaración de variables con let y con const.
let hace que la variable tenga ámbito de bloque y const es similar pero sólo se puede inicializar el valor y luego no se puede modificar.

Cambiemos el ejemplo anterior utilizando let y quedará mucho más claro:

let x = 3;
function show(param) {
    if (param == true) {
        return x;
    } else {
        let x = 4;
        return x;
    }
}
console.log(show(true));

En este caso, el valor mostrado en consola es 3, como era de esperar.

Se recomienda utilizar siempre let y const en lugar de var.

Patrones de cadenas

Muchas veces necesitamos concatenar cadenas con valores de variables:

let str : string = "( " + a + ", " + b +" )";

Con Typescript podemos conseguir el mismo efecto mediante patrones de cadenas, utilizando la comilla que va hacia la izquierda:

let str : string = `(  {a} , {b} )`;

Además, los patrones de cadenas en Typescript también permiten cadenas multilínea:

let str : string = `
    Esta cadena tiene
    múltiples líneas
    y así se guardará`;

Parámetros por defecto y opcionales

En Typescript podemos especificar valores por defecto para los parámetros de una función:

function add( a : number, b : number = 0): number  {
    return a + b;
}

add(3, 4);     // 7
add(3);        // 3

Los parámetros con valores por defecto se aplican cuando se pasa un valor undefined (no cuando se pasa null, y siempre son los últimos.
En Javascript todos los parámetros de una función son opcionales. Si un parámetro no se pasa al llamar a la función, la función recibe undefinedSin embargo, en Typescript se deben especificar los parámetros que son opcionales poniendo ? después del nombre del parámetro:

function add( a : number, b? : number): number  {
    if (b) {
        return a + b;
    } else {
        return a;
    }
}

add(3, 4);     // 7
add(3);        // 3

En Javascript se puede pasar un número indeterminado de argumentos a una función y ésta los recoge utilizando la variable llamada arguments. En Typescript esto se puede hacer con la siguiente sintaxis:

function add( a : number, ...other: number[] ) {
    let total = a;
    for (int i = 0; i < other.length; i++) {
        a += other[i];
    }
}

add(3, 4, 5, 6);    // 18

Interfaces

Los interfaces son una forma de definir contratos:

interface Graphic {
    toString() : string;
    getX() : number;
    getY() : number;
    setX(x:number) : void;
    setY(y:number) : void;
    draw() : void;
}

Este interfaz define las operaciones que tendrán los objetos gráficos.

Utilización de este interfaz:

function move(obj : Graphic, x : number, y : number) : void {
    obj.setX(x);
    obj.setY(y);
}

Para obligar a una clase a implementar la interfaz:

class Point implements Graphic {
    ...
}

Clases

La sintaxis para la definición de clases en Typescript es muy similar a la de los lenguajes orientados a objetos tradicionales:

class Rectangle {
    protected x : number;
    protected y : number;
    protected w : number;
    protected h : number;

    constructor(x: number, y: number, w: number, h: number) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }

    scale(factor: number): void {
        this.w = this.w * factor;
        this.h = this.h * factor;
    }
}

Podemos crear objetos de una clase utilizando new:

let r = new Rectangle(3, 2, 60, 50);
r.scale(1.3);

Se puede extender una clase mediante herencia:

class Square extends Rectangle {
    private name : string;
    constructor(x: number, y: number, s: number, name: string) {
        super(x, y, s, s);
        this.name = name;
    }
    getName() {
        return this.name;
    }
}

Los miembros de una clase (atributos y métodos) soportan los modificadores private, protected y public para definir el acceso. Por defecto, todos los miembros son public, y se puede acceder a ellos desde cualquier sitio. Los miembros protected sólo se pueden acceder desde la propia clase y desde sus clases derivadas. Por último, los miembros private sólo se pueden acceder desde la propia clase.

En Typescript se pueden definir métodos get y set para interceptar el acceso a atributos de una clase:

class Point {
    private _x : number;
    private _y : number;
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }
    public get x() : number {
        console.log("getting x value");
        return this._x;
    }
    public set x(v: number) {
        console.log("setting x value");
        this._x = v;
    }
    public get y() : number {
        return this._y;
    }
    public set y(v: number) {
        this._y = v;
    }
}

let point = new Point(3, 5);
point.x = 9;
console.log(point.x);

En la consola de Javascript veremos lo siguiente:

setting x value
getting x value
9

Se pueden definir miembros estáticos en las clases (tanto atributos como métodos):

class Types {
    static VALUE_1: number = 1;
    static VALUE_2: number = 2;
    static VALUE_3: number = 3;
    static add(a: number, b: number): number {
        return a + b;
    }
}

Types.add(Types.VALUE_1, 5);   // 6

Por último, en Typescript podemos definir clases abstractas, las cuales no se pueden instanciar, pero podemos definir clases que heredan de ellas:

abstract class BaseClass {
    protected _name: string;
    abstract toString(): string;
    public set name(n: string) {
        _name = n;
    }
}

Módulos

Cuando un proyecto empieza a crecer, suele ser una buena idea dividir nuestro código en ficheros diferentes, también llamados módulos. En Typescript cada módulo lo pondremos en un fichero.
Si queremos exportar algo desde un módulo, ya sea un interfaz, una clase, una función, etc, utilizamos la palabra clave export:
 
Módulo para validación de strings:

// stringvalidator.ts
export interface StringValidator {
    isValid(s: string) : boolean;
}

Otro módulo para validación de código postal:

// zipvalidator.ts
export class ZipValidator implements StringValidator {
    isValid(n: string) : boolean {
        return .......
    }
}

Podemos hacer un módulo de validaciones que exporte a los otros dos:

// validators.ts
export * from "./stringvalidator";
export * from "./zipvalidator";

Para importar desde otro módulo:

import { ZipValidator } from "./validators"
let v = new ZipValidator();
v.isValid("28041");

Se puede importar cambiando el nombre (una especie de alias):

import { ZipValidator as Zip } from "./validators"

Finalmente, podemos importar todo el contenido de un módulo y meterlo en una variable:

import * as validators from "./validators"
let s = new validators.StringValidator();

Genéricos

Las clases genéricas permiten:

  • Mayor seguridad de tipos: un Array sólo puede contener cadenas
  • El compilador chequea los tipos de los genéricos
  • Son más rápidas que utilizar cualquier tipo libremente
  • Podemos escribir código que es aplicable a muchos tipos
Ejemplo:

class Box {
    private _value: T;
    set value(v: T) {
        this._value = v;
    }
    get value() {
        return this._value;
    }
}

let strBox = new Box();
strBox.value = "hola";


























lunes, 12 de diciembre de 2016

NativeScript y múltiples tamaños de pantalla

NativeScript: múltiples tamaños de pantalla

Hoy voy a escribir sobre NativeScript y sobre el soporte a múltiples tamaños de pantalla.

Cuando definimos la UI con NativeScript, situamos los objetos y los dimensionamos utilizando puntos (dp). Estos puntos no se corresponden directamente con pixels de la pantalla. Cuando el móvil o tablet ejecuta nuestro programa, convierte estos puntos en pixels reales de la pantalla (px).

Cada dispositivo tiene una densidad de pixels (pixels por pulgada, dpi) diferente. Hay dispositivos con 90 dpi y hay dispositivos con 320 dpi.

Se establece el siguiente criterio: suponemos una densidad de pixels de 160 dpi, y sobre esa densidad definimos los puntos. O sea, los puntos se corresponden exactamente con pixels en pantallas con 160 dpi. Pero en pantallas de otra densidad, los puntos hay que transformarlos en pixels (si tenemos más de 160 dpi, se pintarán varios pixels por cada punto, y si tenemos menos de 160 dpi, necesitaremos varios puntos para completar un pixel).

En un dispositivo con densidad dpi, la conversión a pixels del dispositivo se hace con la siguiente fórmula:

    px = dp * dpi / 160

Al programar, se posiciona y se dimensiona todo en puntos, y luego esto se convierte a pixels en cada dispositivo según la fórmula anterior.

Por ejemplo, si creamos un círculo en la posición (300, 200) con 100 puntos de radio, se dibujará de la siguiente forma:

    - en un dispositivo de 160 dpi: radio 100 pixels, posición (300, 200)
    - en un dispositivo de 320 dpi: radio 200 pixels, posición (600, 400)
    - en un dispositivo de 220 dpi: radio 137 pixels, posición (412, 275)


el tamaño y posición del círculo será exactamente igual en ambos dispositivos.

Esto resuelve el problema de que cada dispositivo tiene una densidad de pixels distinta, y los objetos gráficos tendrán el mismo tamaño en todos los dispositivos.

Sin embargo, esto no resuelve el problema de los distintos tamaños de pantalla. Tenemos pantallas desde 3.5" hasta 5.5" y más. Aunque el círculo del ejemplo anterior se mostraría en la misma posición y con igual tamaño en cualquier dispositivo, sin embargo, en función del tamaño físico de la pantalla, el círculo estaría en el centro, o hacia la izquierda o hacia la derecha.

Este problema se resuelve con los layouts que posicionan los objetos y los redimensionan en función del tamaño de la pantalla.

Se considera que un dispositivo es una tablet cuando su dimensión más pequeña es superior a 3,75", o sea, cuando hay más de 600 dp en su dimensión más pequeña.

    Si la dimensión más pequeña es mayor que 3,75" (600 dp) => es una tablet

NativeScript sigue el siguiente criterio de nombrado para los ficheros:

    filename.qualifier*.extension

Por ejemplo: main_page.android.xml

Los cualificadores pueden ser:

  • cualificadores de orientación: port, land
  • cualificadores de plataforma: android, ios
  • cualificadores de tamaño de pantalla: minWHxxx la dimensión más pequeña debe ser al menos xxx dp's, minWxxx el ancho debe ser al menos xxx dp's, minHxxx el alto debe ser al menos xxx dp's


Con este criterio, podemos definir diseños de pantalla y estilos específicos para una orientación, para un tamaño o para un sistema operativo.

miércoles, 3 de agosto de 2016

Redis sorted sets

Qué son los sorted sets

Una de las estructuras de datos más potentes que tiene redis son los sorted sets (en castellano, conjuntos ordenados).
Los sorted sets son similares a los sets, o sea, son una colección de elementos (strings) únicos (no repetidos). La diferencia es que cada miembro de una lista ordenada tiene un score (puntuación), un valor en punto flotante que se utiliza para mantener ordenados los elementos, desde el más pequeño al más grande.
En resumen, un sorted set es un conjunto de elementos con un valor, que se mantiene ordenado por el valor.
Los elementos de un sorted set son únicos, pero los valores se pueden repetir.

Las operaciones de añadir, actualizar y borrar elemento+valor en un sorted set tienen un orden de computación de log(N), donde N es el número de elementos que tiene el set. O sea, es computacionalmente muy eficiente.

Ejemplos de uso

Ejemplos que se pueden modelar con sorted sets:

  • lista de records:
    • ZADD para añadir un nuevo record
    • ZRANGE para obtener las mejores puntuaciones
    • ZSCORE para saber los puntos de un usuario
    • ZRANK para saber el ranking de un usuario
  • indexar otros datos de redis, por ej, datos de usuarios
    • En un sorted set se puede almacenar element=userid y score=edad
    • ZRANGEBYSCORE para sacar los usuarios en un rango de edades

Ordenación

Los elementos en un sorted set se mantienen (siempre) ordenados, no se ordenan cuando se realiza una petición.
La ordenación sigue las siguientes reglas, suponiendo dos elementos A y B:
  • Si A.score != B.score, entonces se ordenan por score
  • Si A.score == B.score, entonces se ordenan lexicográficamente, es decir, por el elemento (A, B)

Comandos

A continuación se explican los comandos usados más frecuentemente para sorted sets. Aunque hay algunos más que se pueden consultar en: comandos redis para sorted sets

ZADD set-name score element

Añade un elemento con score a un sorted-set

ZRANGE set-name start stop [withscores]

Devuelve los elementos en las posiciones entre start y stop (inclusive). Los elementos van ordenados de menos a mayor (si se desea el orden inverso, utilizar ZREVRANGE). El primer elemento de la lista es el 0. Opcionalmente, WITHSCORES, puede devolver también el score asocado a cada elemento retornado.

ZRANGEBYSCORE set-name min max [WITHSCORES] [LIMIT offset count]

Devuelve los elementos con un score comprendido entre min y max (inclusive) ordenados de menos a mayor score (si se desea el orden inverso, utilizar ZREVRANGEBYSCORE). Se pueden utilizar los valores -inf y inf para min y max. Opcionalmente, WITHSCORES, puede devolver también el score asocado a cada elemento retornado. Opcionalmente también, se puede limitar el número de valores retornados con LIMIT (offset sería 0 y count el número máximo de elementos a retornar).

ZREM set-name element

Elimina un elemento del sorted set.

ZSCORE set-name element

Devuelve el score de un elemento.

ZCARD set-name

Devuelve el número de elementos en un sorted set.

ZCOUNT set-name min max

Devuelve el número de elementos con scores entre min y max.


miércoles, 30 de marzo de 2016

Funcionamiento de Redis Sentinel

Características de sentinel

Sentinel da alta disponibilidad a redis, permitiendo a redis recuperarse de algunos fallos sin intervención humana.
Estas son las características de sentinel:

  • Monitorización: sentinel está continuamente monitorizando si los redis master y slave están arriba y funcionando.
  • Notificación: sentinel puede avisar a los administradores del sistema o a otros programas, de caídas de instancias redis.
  • Failover automático: si la instancia master de redis no está funcionando adecuadamente, sentinel puede iniciar un proceso de failover donde un slave es promocionado a master y los demás slaves se reconfiguran para utilizar el nuevo master
  • Proveedor de configuración: sentinel actúa como descubridor de la instancia master de redis. Los clientes se conectan a sentinel, y éste les indica a qué instancia de redis se tienen que conectar. Cuando se realiza un failover, sentinel informa a sus clientes del nuevo master.

Sentinel es un sistema distribuido

Sentinel funciona de forma distribuida, es decir, varias instancias de sentinel se comunican entre sí cooperando. Las ventajas de tener varios sentinel ejecutando conjuntamente son:
  • la detección de fallos se realiza cuando varias instancias sentinel acuerdan que la instancia master ya no está disponible. Esto disminuye la probabilidad de falsos positivos.
  • sentinel puede funcionar incluso aunque no todas las instancias de sentinel estén disponibles, haciendo al sistema más robusto.

Ejecutar sentinel

Sentinel se ejecuta de la siguiente forma:

    redis-sentinel sentinel-config-file

Es obligatorio especificar un fichero de configuración para ejecutar sentinel, ya que este fichero puede ser reescrito por sentinel para guardar el estado actual, y se utilizará después de una caída. Además, el proceso sentinel tiene que tener permisos de escritura en este fichero.
Las distintas instancias de sentinel se comunican entre sí por el puerto configurado en sentinel.

Criterios de despliegue de sentinel

  1. Se recomienda desplgar al menos 3 instancias de sentinel
  2. Las 3 instancias de sentinel deben ejecutar en máquinas independientes desde el punto de vista de fallos
  3. No se garantiza que todas las escrituras permanezcan después de la caída de un master, ya que redis utiliza un sistema asíncrono de replicación
  4. No todas las librerías cliente soportan sentinel (jedis sí lo soporta)

Configuración de sentinel

La configuración mínima de una instancia sentinel sería como la siguiente:
sentinel monitor  127.0.0.1 6379 2
sentinel down-after-milliseconds  60000
sentinel failover-timeout  180000
sentinel parallel-syncs  1 1
Se especifica el nombre de un grupo de instancias redis. Este nombre es el que se utilizará para el grupo. Se especifica también la IP de la instancias redis master. No es necesario especificar las instancias slaves de redis, ya que sentinel es capaz de descubrirlas (preguntando al master). Cuando sentinel autodetecta esclavos, los escribe en su fichero de configuración:

    sentinel known-slave my_redis_group 168.190.1.45 6379

También se reescribe el fichero de configuración cuando después de un failover, cambia el master y cada vez que se descubre un nuevo sentinel:

    sentinel known-sentinel my_redis_group 168.190.1.47 26379 xxxxxxx

Detección de master caído

El último argumento de sentinel monitor, un 2 en este ejemplo, se denomina quorum, y su significado es el siguiente:

  • quorum es el número mínimo de instancias sentinel que es necesario que acuerden que un master no está disponible.
  • quorum sólo se utiliza para detectar un fallo del master, no para iniciar un failover. Para realmente iniciar un failover, una de las instancias sentinel es elegida como líder, pero debe ser autorizada para iniciar el failover. Esto sólo ocurre con el voto de la mayoría de las instancias sentinel.
Se puede entender con un ejemplo. Tenemos 5 instancias de sentinel vigilando un master reds y el quorum es 2. Si dos instancias de sentinel detectan que el master se ha caído, entonces uno de los dos es elegido para intentar iniciar el proceso de failover. Pero es necesario que al menos 3 instancias estén de acuerdo en iniciar el failover para realmente llevarlo a cabo.
Ya que es necesario que la mayoría de los sentinel decidan iniciar un failover, se RECOMIENDA/OBLIGA que haya al menos 3 instancias de sentinel en 3 máquinas distintas.

Otras opciones de sentinel

Las otras opciones de sentinel tienen el siguiente formato:

    sentinel

  • down-after-milliseconds es el tiempo que una instancia de redis no es alcanzable (no responde a pings o responde con error) para que sentinel piense que está caída
  • parallel-syncs determina el número de slaves que pueden ser reconfigurados en paralelo para usar el nuevo master después de un failover

Pérdidas de datos en failover

Ya que redis replica asíncronamente (no espera la confirmación), siempre hay un riesgo de perder algún dato cuando se cae el master, ya que éste puede caerse justo después de una escritura y justo antes de replicar esa escritura en los slaves.
Otro caso que puede ocurrir es que el master no se caiga, pero se interrumpa su conexión de red con los slaves. Entonces, podría ocurrir que los sentinel pensaran que se ha caído el master (ya que no responde, aunque realmente no se ha caído) y promocionaran a otra instancia de redis como master. El problema es que si había clientes conectados al master anterior y que no han perdido la conexión con él, pueden seguir escribiendo datos en el antiguo master. Mientras tanto, el master nuevo también puede recibir nuevos datos. Los datos escritos en el master antiguo se perderán cuando la conexión se arregle, ya que éste va a convertirse en slave y va a sincronizarse con el nuevo master.
Este problema puede reducirse bastante utilizando la siguiente configuración:

    min-slaves-to-write 1
    min-slaves-max-lag 10

Con esta configuración, una instancia redis actuando como master, dejará de aceptar escrituras si no puede escribir en al menos un slave en un máximo de 10 segundos. De esta forma, el antiguo master dejará de funcionar en 10 segundos. La desventaja es que si se pierde la conexión con los slaves, la instancia master dejará de funcionar.