martes, 28 de febrero de 2017

Chat con Websockets - Aurelia , Express, Mocha para tests unitarios e Istanbul (Parte 6 - ¿por qué hacer tests?)

Hola amigos, ya creamos una parte super inicial del proyecto, y ahora es momento de pensar en como mantener el proyecto a largo plazo. Dado que se quiere agregar muchas funcionalidades más, es posible que la aplicación se rompa en el proceso y que sea necesario refactorizar algunas partes; por lo tanto, es el momento de introducir los tests unitarios y de integración (TDD).

Primero, es muy importante reconocer que siempre estamos haciendo tests manualmente y probamos si el código que estamos realizando esta funcionando como queremos, así que no estaremos realizando algo completamente nuevo sino que simplemente automatizaremos el proceso.

Como un favor para nuestro yo futuro (de nada, futuro yo :D), dejemos pruebas automáticas que se puedan correr para verificar la funcionalidad de nuestro proyecto. Sin pruebas, realizar modificaciones sin producir cambios de comportamiento de la aplicación o agregando bugs es prácticamente imposible, aún si es un equipo pequeño de desarrollo o solamente una persona. 

No siempre tendremos en mente todos los requerimientos de la aplicación o los casos extremos, así que para evitar dolores de cabeza en el futuro, siempre es mejor comenzar realizando tests.

Primero definamos algunos conceptos:
  • Pruebas unitarias: son pruebas que realizaremos de funcionalidad específica de la aplicación (pruebas de las tareas pequeñas). Son ideales para verificar el comportamiento de las funciones que crearemos.
  • Pruebas de integración: son pruebas que  engloban varias características de la aplicación (pruebas del conjunto de tareas) para verificar el comportamiento de las partes como un todo.
  • TDD: es una técnica para desarrollar que se basa en el concepto de la realización de pruebas antes de desarrollar cualquier funcionalidad. El proceso de desarrollo con TDD es:
    • Realizar una prueba (que fallará obviamente porque aún no tenemos código escrito).
    • Escribir la funcionalidad necesaria para que la prueba pase exitosamente (no escribir nada más, solamente lo necesario para que la prueba pase, aún si es una respuesta hardcoded).
    • Iterar (repetir los anteriores pasos).
Y las herramientas que utilizaremos:
  • Mocha: es un framework que nos ayudará a realizar las pruebas de una manera sencilla, es prácticamente plug and play, por lo que no necesita demasiadas configuraciones.
  • Chai: que nos permitirá afirmar que realmente el resultado que obtenemos es el que esperamos (assert).
  • Istanbul: herramienta que nos generará un reporte de la cobertura de nuestros tests sobre el código fuente.
  • Nyc: línea de comandos para Istanbul que nos permitirá correr con ecma6
Primero, descarguemos el proyecto hasta el último post:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/twitter-api

Ahora instalemos todo lo necesario para realizar las pruebas y la cobertura:

$ npm install mocha --save-dev
$ npm install chai --save-dev
$ npm install istanbul --save-dev
$ npm install babel-core --save-dev
$ npm install nyc --save-dev

Además, agregaremos al archivo package.json la instrucción para realizar los tests con mocha (añadimos --compilers para que babel compile nuestros tests escritos con ecma6 :D):

  "scripts": {
    "start": "DEBUG=chat-backend babel-watch -L index.js",
    "eslint": "eslint .",
    "test": "mocha tests/**/* --compilers js:babel-core/register"
  },

También crearemos un archivo (vacío, por el momento) llamado mocha.opts para configurar los tests con mocha en el futuro.

Por último, realizaremos pruebas de nuestro servicio que consume la api de twitter, lo único que nos interesa es que el contenido de los mensajes tenga el tag que requerimos y que la cantidad de mensajes devueltos sea correcta. Existen dos "agrupadores" importantes para los tests, describe e itdescribe permite hacer grupos de tests o de funcionalidades (puede anidarse), mientras que it engloba al test que vamos a realizar.

Creemos pues dos tests, uno que verifica que el contenido de los mensajes sea el que pedimos, y otro que verifique la cantidad de mensajes devueltos (Si bien podríamos realizar los tests en uno solo, la idea de cada test es probar una funcionalidad particular, es por eso que realizamos dos tests en este caso).

/* global describe, it*/
import chai from 'chai';
import twitterService from '../services/twitter';

const assert = chai.assert;

describe('Api de consumo de twitter', () => {
  describe('Búsqueda por tag', () => {
    it('Debe retornar datos con el tag nodejs', (done) => {
      twitterService.searchByTag('nodejs')
        .then((data) => {
          data.statuses.forEach((status) => {
            assert.include(status.text.toLowerCase(),
              'nodejs',
              'El texto del mensaje contiene la palabra nodejs');
          });
          done();
        })
        .catch((err) => {
          done(err);
        });
    });

    it('Debe retornar la cantidad de datos solicitada', (done) => {
      twitterService.searchByTag('nodejs', 2)
        .then((data) => {
          const totalDeDatosDevueltos = data.search_metadata.count;
          assert.equal(totalDeDatosDevueltos, 2, 'Debe retornar dos datos');
          done();
        })
        .catch((err) => {
          done(err);
        });
    });

  });
});


Primero, registramos como variables globales describe e it para que el linter no nos muestre errores, luego agrupamos los test con los mensajes Api de consumo de twitter y búsqueda por tag, y dentro creamos los tests; el primero para verificar que la palabra nodejs se encuentre en cada estado devuelto por twitter (assert.include se utiliza para buscar el segundo parámetro en el primer parámetro, funciona con arrays y cadenas) y el segundo para asegurarnos que la cantidad de datos devueltos es la correcta (assert.equal verifica la igualdad de dos valores). Es necesario siempre llamar a la función done() cuando el test termina, y si terminara con error se llama a la misma función pero enviándole el error obtenido done(error).

La estructura de carpetas debería quedar de la siguiente manera:
Carpetas para los tests
Ahora es momento de verificar que los tests estén corriendo correctamente:

$ npm test

Ejemplo de test lanzados de manera correcta
Además, solo para verificar cómo se ve un test con error, haré que la api devuelva un dato más de lo debido:

Error
Como se puede observar en la imagen, el mensaje de error es claro, debía devolver dos datos pero esta devolviendo tres.

Por último, vamos a mostrar la cobertura de nuestras pruebas sobre el código, esto implica que podemos ver cuánto código está siendo probado realmente (por ejemplo en un if puede que solamente estemos probando el lado verdadero de la condición y no el falso), esto implica ver si nuestros tests están pasando por todas las rutas de código posibles. Modificaremos nuevamente nuestro package.json y le agregaremos la opción de ver la cobertura de los tests sobre nuestro código:

"scripts": {
    "start": "DEBUG=chat-backend babel-watch -L index.js",
    "eslint": "eslint .",
    "test": "nyc --require babel-core/register -x tests/**/* 
             node_modules/.bin/_mocha tests/**/* --compilers js:babel-core/register",
    "gen--html-coverage-report": "nyc report --reporter=html"
  },

Nota: La línea de test no debe tener saltos de línea (enter), la bajé para que se note y no sobresalga demasiado del template del blog.

Ahora podemos hacer correr los tests nuevamente y veremos un reporte de la cobertura de nuestros tests:

$ npm test

Respuesta de los tests con cobertura de istanbul

Por último, podemos generar el reporte en html y verificar qué líneas de código no estamos recorriendo con nuestros tests (el reporte se genera dentro de la carpeta coverage).

$ npm run gen-html-coverage-report

Y podemos observar el reporte en html:

Reporte general de istanbul
Reporte detallado
Podemos observar en el reporte a detalle que la parte sin tests (las líneas de código que no estamos probando) son el caso de error al llamar a la api de twitter, sin embargo, no siempre es necesario tener un 100% de cobertura de la aplicación, por ahora lo dejaremos con la cobertura actual y agregaremos en caso de ser necesario (se debe tomar en cuenta que el tiempo para realizar una cobertura del 100% suele ser demasiado para los product managers, por lo tanto es bueno quedar en una cobertura realista como de 70% u 80%).

Y eso es todo por ahora, fue más de lo que esperaba para un post pero en definitiva es muy útil para cualquier tipo de proyecto.

Como en todos los demás posts, puedes bajar el proyecto directamente de git con:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/test-y-cobertura

Y eso es todo por ahora, recuerden, sigan programando >_<

Nota: Si necesitan algún detalle extra de nodejs u otros, simplemente escriban un comentario en el post.









domingo, 26 de febrero de 2017

¿Qué es REST? - Introducción y buenas prácticas

Hello again!!!, es hora de familiarizarnos un poco con REST (REpresentational State Transfer) y de tomar algunas buenas prácticas para su utilización. Antes de comenzar les recordaré que esto no es una caceria de brujas :D, no importa la manera en que se implemente mientras se mantenga la consistencia a lo largo de la API (aunque lo mejor es mantener una coherencia con las apis ya existentes para que sea mucho más simple consumir la API para un desarrollador, sean amables con ustedes mismos XD  , un excelente ejemplo es la api de github que es genial >_< )

Ahora a lo nuestro, llevo meses pensando en escribir este post y nunca parece haber el tiempo así que a darle átomos:

En general REST describe como se puede comunicar un sistema con otro (es un estilo de arquitectura para diseñar aplicaciones que se comunicarán unas con otras a través de la red).

En REST utilizamos el protocolo Http para obtener un recurso o realizar transformaciones en el mismo. Por ejemplo, si quiero obtener los datos de un Usuario, utilizaría una petición GET al siguiente URL:

http://api.rest.com/usuarios     - (GET para obtener datos)

Pero no solamente necesitamos obtener recursos, también necesitamos transformarlos, y para ello utilizamos otros verbos. Por ejemplo, para crear el recurso Usuario (solo recordar que el crear no siempre implica una base de datos), se utiliza el verbo POST y se envía la información del recurso en el cuerpo (body) del mensaje:

http://api.rest.com/usuarios - (POST para guardar un dato)
body:
{
  "usuario": "juan",
  "nombre": "Juan Perez",
  "permisos": ["lectura", "escritura"]
}

En el caso de eliminar un recurso de tipo Usuario, utilizamos el verbo DELETE agregándole además, un identificador del recurso que se quiere eliminar. Para el ejemplo, eliminaremos el recurso que guardamos con POST anteriormente:

http://api.rest.com/usuarios/juan - (DELETE para guardar un dato)

También necesitamos actualizar o modificar recursos, para esto, utilizamos los verbos PUT o PATCH y enviamos los datos para modificar en el cuerpo del mensaje. Por ejemplo, si se quiere modificar el recurso que agregamos anteriormente y cambiarle el nombre:

http://api.rest.com/usuarios/juan - (PATCH para guardar un dato)
body:
{
  "nombre": "Juan Perez Ochoa"
}

En general:

VERBO USO CARACTERÍSTICAS
GET Para obtener los datos de un recurso Idempotente*
POST Para crear un nuevo recurso
PUT Para actualizar un recurso completo, también puede utilizarse para crear un nuevo recurso en caso de no existir. Idempotente*
PATCH Para cambiar el recurso parcialmente, o sea para cambiar soamente algunos valores del recurso
DELETE Para eliminar un recurso Idempotente*

* Idempotente: es la propiedad que indica que en caso de realizarse la misma operación varias veces sobre el mismo recurso, el resultado será el mismo en cuanto al estado del sistema (por ejemplo, si bien la respuesta a un delete es 201 la primera vez, la siguiente será 404 porque el recurso ya no existe, sin embargo, el estado del sistema es el mismo ya que de una u otra manera el recurso ya no existe).

Algunas consideraciones para tomarse en cuenta a la hora de realizar una API REST:
  • Los URI deben ser plurales, y para separar varias palabras utilizar '-' o camelCase (No importa cual se utilice, pero mantener a través de la API el mismo estilo).
  • Debe existir una clara separación de funciones (backend y frontend)
  • No se debe guardar estado entre peticiones (requests), no debe existir una sesión que almacene datos sobre la interacción del usuario.
  • Se debe mantener una cache para aminorar la carga de la red e incrementar el rendimiento de la aplicación.
  • Dada su arquitectura, es ideal para tener capas (cache, load balancer, proxy, cdn). El cliente no puede determinar si esta comunicandose directamente al servidor o a un software intermedio.
  • Es necesario tener una interfaz uniforme (no importa que convenciones se tenga, mantenerlas en toda la API)
  • Al decir creación de un recurso, no siempre se implica que se este creando un dato en la base de datos, pueden existir apis que de hecho no utilicen una Base de Datos, sin embargo, no es necesario ser tan "puritanos" al respecto :) 
En el caso de las respuestas de una API REST, las más comunes son:

  • 200 (ok), código básico que indica que la operación se realizo de forma exitosa
  • 201 (created), indica que el recurso se ha creado correctamente, es una típica respuesta de una petición POST o una petición PUT que ha creado un recurso.
  • 204 (no content), se utiliza típicamente para una petición que ha sido exitosa pero no existe ningún contenido para mostrar. Comunmente se utiliza como respuesta a un DELETE exitoso.
  • 400 (bad request), código que indica que ha ocurrido un error al procesar la petición (el usuario no debe vovler a realizar la petición sin haber modificado antes sus parámetros)
  • 401 (unauthorized), se utiliza para indicarle al usuario que sus credenciales con erroneas.
  • 403 (forbidden), indica que, si bien se sabe quien eres, no tienes los permisos para consumir el recurso en específico.
  • 404 (not found), código utilizado para señalar que el recurso no existe.
  • 500 (internal server error), se utiliza para señalar que ha ocurrido un error no previsto en la aplicación, del cuál no se puede dar un detalle más específico.


Algunos ejemplos :

- Obtener el detalle de un usuario en particular (usuario con id esteban)

http://api.rest.com/usuarios/esteban

- Obtener un listado de usuarios 

http://api.rest.com/usuarios

- En caso de paginación, es necesario utilizar dos parámetros en el uri, uno que indica cuantos datos se desea obtener y el otro desde que registro; además, en caso de ser necesario se puede enviar el total de datos como un header especial que comienze con X para indicar que es un header especial (por ejemplo X-Total-Registros o cualquier otro nombre, pero comenzando con X).
Si queremos obtener 10 registros a partir del registro 30, de un total de 100 registros, podriamos utilizar cualquiera de los siguientes ejemplos, pero es importante mantener el mismo estilo para todos los recursos de la API:

http://api.rest.com/usuarios?intervalo=30&limite=10

http://api.rest.com/usuarios?pagina=3&por_pagina=10

- En caso de búsquedas, existen dos opciones de acuerdo a la necesidad. Para búsquedas simples se pueden realizar sobre el mismo recurso, por ejemplo para buscar un usaurio cuyo nombre es Juan Perez:

http://api.rest.com/usuarios?nombre=juan%20perez

Sin embargo, en caso de busquedas avanzadas, y para no complicar demasiado una ruta, se puede utilizar un recurso llamado busquedas, y tanto un verbo GET como POST (con POST, estariamos creando un recurso llamado búsqueda). Por ejemplo con get, si queremos buscar al usuario con nombre juan peres y con edad mayor a 30(tomar en cuanta que > se códifica como %3e)

http://api.rest.com/busquedas?nombre=juan+perez&edad=%3e30

Con POST, podemos mandar datos en el cuerpo del mensaje y hacerlo más legible (recuerden que no es posible hacerlo de muchas maneras, pero lo mejor es mantenerlo consistente en la API):

http://api.rest.com/busquedas
body:
{
  "nombre": "juan perez",
  "edad": {
    "gt": 30
  }
}

Y eso es todo por ahora!!! en próximos posts extenderé el tema, keep coding >_<

lunes, 20 de febrero de 2017

Chat con Websockets - Aurelia , Express (Parte 5 - Consumiendo la API de twitter desde el backend)

Hola nuevamente!!! a modo de hacer algo nuevo, aprenderemos a consumir la API de twitter desde nodejs, y para hacerlo mas interesante en el siguiente post pondremos un pequeño "widget" en el chat donde veremos los últimos mensajes de twitter.

Puedes seguir el proceso de construcción del proyecto en posts anteriores, o simplemente descargar la aplicación hasta este punto con:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/send-receive-messages

Primero, generaremos nuestras credenciales en twitter, ingresar a https://apps.twitter.com/ (ingresar con tu usuario y contraseña), luego hacer click en Create New App:

Agregar Aplicación
Ahora es necesario ingresar algunos datos de nuestra aplicación, el nombre y la descripción, el url del sitio web y por último el url al cual nos dirige la autenticación de twitter cuando el usuario ingresa sus datos exitosamente (en este momento realizaremos consultas generales, no específicamente del usuario por lo cuál este campo puede ser cualquiera)

Datos de la aplicación

Ahora configuraremos los datos para que sean solamente de lectura (siempre dar el permiso mínimo requerido es una buena práctica) y presionamos Update Settings
Permiso de solo lectura


Además, necesitamos los datos consumer key y consumer secret (anotarlos en un lugar seguro).

Consumer key y secret

Por último, presionar el botón Create my access token para generar los tokens. Necesitamos el access token y el access token secret (anotarlos en un lugar seguro)

Access toke, y access token secret
Habiendo generado las credenciales necesarias, es hora de consumir la api de twitter, primero instalaremos el cliente en la aplicación:

$ npm install twitter-node-client --save

Ahora, en el proyecto backend, crearemos la siguiente estructura de carpetas y archivos:

Estructura de carpetas y archivos

Comenzaremos agregando el archivo de configuración para conectar a twitter, para ello modificar el archivo configurations/twitter-config.js con las credenciales que obtuvimos al crear nuestra aplicación. El archivo debe quedar con la siguiente estructura (pero con las credenciales que obtuvimos):

module.exports = {
  consumerKey: 'CHANGE',
  consumerSecret: 'CHANGE',
  accessToken: 'CHANGE',
  accessTokenSecret: 'CHANGE',
  callBackUrl: 'CHANGE',
};


Con las credenciales en su lugar, crearemos el servicio que permita obtener una cantidad de mensajes basados en un hashtag. Para esto modificaremos el archivo services/twitter.js con el siguiente código, que básicamente obtiene las credenciales de conexión y realiza un query básico (q) limitado por defecto a diez registros (count) en caso de éxito resolvemos la promesa con los datos en formato JSON, y en caso de error rechazamos la promesa con el error obtenido:

import debugModule from 'debug';
import TwitterNodeClient from 'twitter-node-client';
import twitterConfigs from '../configurations/twitter-config';

const Twitter = TwitterNodeClient.Twitter;
const twitter = new Twitter(twitterConfigs);
const debug = debugModule('twitter-service');

module.exports.searchByTag = (tag, quantity = 10) => {
  return new Promise((resolve, reject) => {
    twitter.getSearch({ q: `#${tag}`, count: quantity },
      (err, response, body) => {
        reject(err);
      }, (data) => {
        resolve(JSON.parse(data));
      });
  });
}

Luego crearemos la ruta para nuestra API Rest (verbo GET), modificaremos el archivo routes/twitter.js en donde llamamos el servicio recién creado, y en caso de éxito lo devolvemos al usuario (con código 200 que indica éxito en la operación); en caso de error devolvemos la causa del mismo (con código 500 que indica la existencia de un error y que no podemos ser más específicos en la causa del mismo).

import express from 'express';
import twitterService from '../services/twitter';

const router = express.Router();

router.get('/', (req, res) => {
  twitterService.searchByTag('node')
    .then((data) => {
      res.json(data);
    })
    .catch((err) => {
      res.status(500).json(err);
    });
});

module.exports = router;


Además, modificaremos el archivo routes/index.js que exportará las rutas que necesitamos agregar a la aplicación.

import twitterRoutes from './twitter';

module.exports.twitterRoutes = twitterRoutes;

Por último, modificaremos el archivo index.js que se encuentra en la raíz del proyecto para cargar la ruta recien creada en el path /twitter.

import express from 'express';
import http from 'http';
import debugModule from 'debug';
import { twitterRoutes } from './routes';

const app = express();
const server = http.Server(app);

const debug = debugModule('chat-backend');

app.use('/twitter', twitterRoutes);


server.listen(8081, () => {
  debug('Servidor escuchando en el puerto 8081');
});

Y eso es todo, ahora podemos probar el servicio directamente en el navegador o con postman.

Prueba del servicio de twitter

Para descargar el proyecto hasta este lugar:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/twitter-api

Ahora ya se puede consumir cualquier otra api de twitter para prácticar modificando el archivo services/twitter.js, Hasta la próxima, keep coding >_<

domingo, 19 de febrero de 2017

Chat con Websockets - Aurelia y Express (Parte 4 - Enviando y recibiendo mensajes)

Hola de nuevo, ya que el backend y el frontend están conectados, es hora de enviar y recibir mensajes de las personas con las que nos encontramos chateando. Para esto descargaremos el frontend y el backend con los siguientes comandos (puedes seguir todo el proceso para llegar hasta acá en posts anteriores):
Para el backend:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/connect-be-fe

Para iniciar ingresamos a la carpeta chat-backend y ejecutamos:

$ npm start

Para el frontend:

$ git clone https://github.com/feardarkness/chat-frontend.git
$ git checkout tags/connect-be-fe

Para iniciar ingresamos a la carpeta chat-frontend y ejecutamos:

$ NODE_ENV=dev au run --watch

Ahora modifiquemos el código para mandar mensajes, es necesario modificar tanto el backend como el frontend, así que dividiremos los cambios en dos.

FRONTEND

Es necesario cambiar (src/resources/elements/chat-windows.html) el textarea por un div para ir agregando los mensajes que se nos envien, además se agrega un input text para anotar nuestro alias en el chat y se agrega un tag form para que cuando presionemos la tecla enter el formulario se envie (cuando presionamos enter, el primer botón submit que está en el formulario se presiona).

<template>
  <div class="row" id="chat">
    <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
        <div id="chat-content" ref="messages"></div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
      <div class="form-group">
        <div class="col-sm-2">
          <input type="text" class="form-control"
                 placeholder="Usuario"
                 value.bind="username">
        </div>
        <form submit.delegate="sendMessage()">
          <div class="col-sm-5">
            <input type="text" class="form-control"
               id="mensaje" placeholder="Escriba el mensaje"
               value.bind="message" autofocus>
          </div>
          <div class="col-sm-2">
            <button type="submit" class="btn btn-primary">Enviar Mensaje</button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>

Modificaremos los estilos (src/assets/main.scss) para que el div de los mensajes tenga un alto de 200 píxeles y lo marcaremos con un borde sólido de 2 píxeles con las esquinas redondeadas.

@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";

#chat{
  #chat-content {
    border: 2px solid #000000;
    border-radius: 5px;
    height:200px;
    overflow: auto;
    padding: 4px;
    margin: 5px 0px;

    .chat-message {

      .username {
        font-style: italic;
      }
    }
  }
}

Por último para el frontend, modificaremos el viewModel (src/resources/elements/chat-window.js) para que en el momento en que nos llegue un mensaje del backend lo agreguemos al div donde mostraremos los mensajes del chat.

this.socket.on('be-message', (data) => {
      this.logger.debug('Event with data', data);
      $(this.messages).append(
        this.createMessageDiv(
          data.message,
          data.username
        )
      );

    });

Además, agregaremos al viewModel (src/resources/elements/chat-window.js) la opción de generar una cadena aleatoria para establecer el nombre de usuario y agregar el mensaje con jquery al div, el archivo completo quedaría así:

import io from 'socket.io';
import {LogManager} from 'aurelia-framework';
import $ from 'bootstrap';

export class ChatWindow {
  message = '';
  username = '';

  constructor() {
    this.logger = LogManager.getLogger('chat-window-element');
    this.username = this.randomString();
  }

  attached() {
    this.logger.debug('Attached...');
    this.socket = io('http://127.0.0.1:8081');

    this.socket.on('connect', () =>{
      this.logger.debug('Socket connected...');
    });

    this.socket.on('be-message', (data) => {
      this.logger.debug('Event with data', data);
      $(this.messages).append(
        this.createMessageDiv(
          data.message,
          data.username
        )
      );

    });

    this.socket.on('disconnect', () =>{
      this.logger.debug('Socket disconnected...');
    });
  }

  createMessageDiv(message, username) {
    return $('<div class="chat-message"><span class="username">'
                    + username + ':</span> ' + message + '</div>');
  }

  sendMessage() {
    this.socket.emit('fe-message', {
      message: this.message,
      username: this.username
    });
  }

  randomString() {
    const letrasDisponibles = `ABCDEFGHIJKLMNOPQRSTUVWXYZ
                      abcdefghijklmnopqrstuvwxyzÑñ0123456789`;
    let cadena = '';
    '1234567'.split('').forEach(() => {
      const pos = Math.floor(Math.random() * letrasDisponibles.length);
      cadena += letrasDisponibles.charAt(pos);
    });
    return cadena;
  }
}

BACKEND

En el backend se implementará el envió de mensajes a todos los clientes conectados, en caso de recibir un mensaje del frontend (fe-message), emitimos ese mensaje a todos los clientes (be-message). Por ahora no realizamos ninguna validación, eso se implementara en un post posterior pero es necesario saber que las validaciones son MUY IMPORTANTES, y en ningún caso se debe confiar en los datos enviados por el cliente.

import express from 'express';
import http from 'http';
import debugModule from 'debug';
import socketIO from 'socket.io';

const app = express();
const server = http.Server(app);
const io = socketIO(server);
const debug = debugModule('chat-backend');

server.listen(8081, () => {
  debug('Servidor escuchando en el puerto 8081');
});

io.on('connection', (socket) => {
  debug('Alguien esta conectado!!!');

  socket.emit('be-message', {
    message: 'Bienvenido usuario!!!',
    username: 'Servidor',
  });

  socket.on('fe-message', (data) => {
    debug('Usuario conectado', data);
    io.emit('be-message', data);
  });
});

Ahora veamos un ejemplo de como debería quedarnos el chat:

Ejemplo de comunicación entre usuarios
Antes de realizar las pruebas en nuestros equipos es necesario deshabilitar browsersync que esta habilitado por defecto en Aurelia. Ingresar a 127.0.0.1:3001, sync options -> disable all -> habilitar code sync. Esto hará que los cambios que realicemos en una ventana no se repitan en otra (si no deshabilitamos estas opciones, las acciones que realicemos se repetirán en todos los navegadores donde tengamos abierta la aplicación).

Y eso es todo por ahora, para descargar la aplicación es necesario realizar los siguientes pasos:

BACKEND
$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/send-receive-messages

FRONTEND
$ git clone https://github.com/feardarkness/chat-frontend.git
$ git checkout tags/send-receive-messages

Keep coding >_<


domingo, 12 de febrero de 2017

Chat con Websockets - Aurelia y Express (Parte 3 - conectando frontend y backend)

Hola nuevamente, ahora que ya tenemos la estructura del proyecto preparada, es hora de ver si podemos conectarnos al socket, para esto crearemos la estructura del proyecto con aurelia y utilizaremos bootstrap-sass en el proyecto. Como ya se realizo estos pasos en otros posts, no los repetiré acá, pero si pondré los comandos necesarios para bajar el proyecto de github y darle el nombre de chat-frontend (se tendrá dos proyectos, uno con nodejs, express y socket.io para el backend, y otro con aurelia para el frontend).

$ git clone https://github.com/feardarkness/aurelia_bootstrap_sass_starter
$ mv aurelia_bootstrap_sass_starter/ chat-frontend
$ cd chat-frontend
$ rm -rf .git
$ git init
$ npm install
$ NODE_ENV=dev au run watch

Antes de continuar, revisaremos algunos conceptos necesarios; el protocolo HTTP no almacena ningún estado, abrimos el navegador, enviamos un mensaje al servidor, se procesa, se devuelve la respuesta y se cierra la conexión; no se guarda ningún estado, usualmente para que el navegador nos recuerde es necesario guardar la identidad en una cookie. Además, toda conexión es iniciada por el cliente y cada interacción entre el cliente y el servidor es aislada una de la otra.

Los websockets por otro lado son full-duplex (conexiones bidirecionales) y abren conexiones persistentes del navegador al servidor. Una vez que se ha realizado la conexión, esta se mantiene abierta hasta que uno de los dos extremos (cliente o servidor) decida cerrarla. Ya que la conexión se ha establecido se puede enviar mensajes en cualquier momento (ideal para un chat :D).

Ahora, continuando con la aplicación, crearemos un textarea y un input, nada muy elaborado pero lo suficiente como para ver el funcionamiento de nuestros sockets.
Primero agregamos la librería socket.io con

$ npm install socket.io-client --save

Ahora, agregamos la libreria al archivo aurelia_project/aurelia.json, dentro de dependencies:

{
  "name": "socket.io",
  "path": "../node_modules/socket.io-client/dist",
  "main": "socket.io"
},

Además, crearemos un elemento nuevo y le agregaremos los elementos básicos para hacer el chat:

$ au generate element
      chat-window

Modificaremos el elemento recién creado, le agregaremos un textarea donde aparecerán los mensajes, un input cuyo valor estará enlazado con el atributo message del viewmodel y un botón para enviar el mensaje con la acción sendMessage (src/resources/elements/chat-window.html).

<template>
  <div class="row" id="chat">
    <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
        <textarea id="chat-content" rows="10"></textarea>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
      <div class="form-group">
        <label for="mensaje"
           class="col-sm-1 control-label text-right">Mensaje:</label>
        <div class="col-sm-5">
          <input type="text" class="form-control"
             id="mensaje" placeholder="Escriba el mensaje que desea enviar"
             value.bind="message">
        </div>
        <div class="col-sm-2">
          <button type="submit" class="btn btn-primary"
                  click.delegate="sendMessage()">Enviar Mensaje</button>
        </div>
      </div>
    </div>
  </div>
</template>

También es necesario modificar el viewmodel del elemento recien creado (src/resources/elements/chat-window.js), agregaremos el logger, y algunos mensajes con el websocket.

import io from 'socket.io';
import {LogManager} from 'aurelia-framework';

export class ChatWindow {
  message = '';

  constructor() {
    this.logger = LogManager.getLogger('chat-window-element');
  }

  attached() {
    this.logger.debug('Attached...');
    this.socket = io('http://127.0.0.1:8081');

    this.socket.on('connect', () =>{
      this.logger.debug('Socket connected...');
    });

    this.socket.on('be-message', (data) => {
      this.logger.debug(`Event with data: ${data}`);
    });

    this.socket.on('disconnect', () =>{
      this.logger.debug('Socket disconnected...');
    });
  }

  sendMessage() {
    this.socket.emit('fe-message', {
      message: this.message
    });
  }
}

Importamos socket.io, y cuando el elemento está enlazado con el DOM (attached) creamos la conección con el backend (127.0.0.1 puerto 8081), además, agregamos un mensaje cuando el socket logré conectarse (on connect), un mensaje cuando el servidor nos hable con el evento be-message (on be-message) y un mensaje cuando el elemento se desconecté del socket del backend (on disconnect). Además, cuando el botón del frontend se presione emitiremos un mensaje (emit) con el nombre fe-message.

Agregamos unos estilos muy básicos en el archivo source/assets/main.scss:

#chat{
  #chat-content {
    width: 100%;
  }
}

Y eso es todo para el frontend. Ahora es necesario levantar el backend (chat-backend), puedes clonarlo con:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/express-debug

Modificaremos el backend para recibir el mensaje enviado por el frontend (index.js):

import express from 'express';
import http from 'http';
import debugModule from 'debug';
import socketIO from 'socket.io';

const app = express();
const server = http.Server(app);
const io = socketIO(server);
const debug = debugModule('chat-backend');

server.listen(8081, () => {
  debug('Servidor escuchando en el puerto 8081');
});

io.on('connection', (socket) => {
  debug('Alguien esta conectado!!!');

  socket.emit('be-message', {
    message: 'Bienvenido usuario!!!',
  });

  socket.on('fe-message', (data) => {
    debug('Usuario conectado', data);
  });
});

Se agrega un listener para cuando un ciente se conecte (on connection), y se responde un mensaje de bienvenida al usuario (socket emit be-message); además le indicamos al socket que muestre el mensaje "Usuario conectado" y los datos enviados en caso de recibir un mensaje tipo "fe-message" (on fe-message).

Ahora iniciamos el backend con:

$ npm start

Y podemos observar la interacción entre el backend y el frontend a través de sockets.

Backend y Frontend comunicándose

Y eso es todo por ahora, para obtener el backend del proyecto:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/connect-be-fe

Para obtener el frontend del proyecto:

$ git clone https://github.com/feardarkness/chat-frontend.git
$ git checkout tags/connect-be-fe

Y eso es todo por ahora, keep coding >_<

lunes, 6 de febrero de 2017

Chat con Websockets - Express y Debug (Parte 2 - Creando la estructura del proyecto)

Hallo!!! Esta es la segunda parte del proyecto que crearemos, puedes ver la primera donde preparamos las librerias necesarias para transpilar el proyecto con babel. Bueno, es hora de crear la estructura del proyecto, lo único que se necesita es duplicar la estructura que se presenta a continuación:
Estructura de carpetas apra el proyecto

Ahora, ageguemos algunos archivos interesantes, primero veamos como mostrar mensajes en la consola de la manera correcta. Instalaremos debug:

$ npm install --save debug

Ahora modificaremos el comando start para iniciar la aplicación con la opción de debug, hay que editar el archivo package.json:

"scripts": {
  "start": "DEBUG=chat-backend babel-watch -L index.js",
  "eslint": "eslint .",
  "test": "echo \"Error: no test specified\" && exit 1"
},

Agregamos la variable de entorno debug con el valor chat-backend (el nombre que le pusimos al debugModule), también modificaremos el archivo index.js para verificar que todo esta funcionando correctamente.

import express from 'express';
import http from 'http';
import debugModule from 'debug';
import socketIO from 'socket.io';

const app = express();
const server = http.Server(app);
const io = socketIO(server);
const debug = debugModule('chat-backend');

server.listen(8081, () => {
  debug('Servidor escuchando en el puerto 8081');
});

io.on('connection', (socket) => {
  debug('Socket conectado', socket);
});

Ahora iniciaremos la aplicación (con npm start), veamos como se comporta:



Como se puede observar, el mensaje enviado con debug se muestra, y esto se puede controlar con una variable de entorno tanto en un ambiente de desarrollo como en producción. Es ideal tener debug habilitado para evitar utilizar console.log, esto para no tener los mensajes que agregamos en nuestros logs de producción.

Para poder jugar con el código de esta parte de la aplicación, bajar la aplicación desde github:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/express-debug

Y eso es todo por ahora, keep coding >_<.

domingo, 5 de febrero de 2017

Chat con Websockets - NodeJS, npm, eslint y babel (Parte 1 - Preparando el proyecto)

Hola, ahora crearemos un pequeño chat para aprender un poco más sobre el funcionamiento de Express, Socket.io y Aurelia.

Primero crearemos el backend, con los siguientes comandos (dentro de la carpeta del proyecto que llamaremos chat-backend):

Primero creamos la estructura básica con npm init,

$ npm init

Ahora instalamos las dependencias que necesitaremos para iniciar (recordar que con --save las dependencias se guardan el en archivo package.json para ser instaladas posteriormente):

$ npm install express --save
$ npm install socket.io --save

Primero, habilitaremos algunas características nuevas propuestas de ecma6, para ello instalaremos tres dependencias más:

$ npm install --save-dev babel-cli
$ npm install --save-dev babel-preset-es2015
$ npm install --save-dev babel-preset-es2017
$ npm install --save-dev babel-watch

La primera es para habilitar el transpilador que básicamente modificará el código que escribamos con las características de ecma6 a javascript que nodejs pueda entender, babel-preset-es2017 es para obtener esas caracteristicas y que babel pueda entenderlas y por ultimo utilizaremos babel-watch para recargar la aplicación automaticamente en caso de modificar algún archivo javascript.

Ahora, necesitamos crear el archivo .babelrc con el siguiente contenido:

{
  "presets": [
    "es2015",
    "es2017"
  ]
}


Además, modificaremos el archivo package.json, y agregamos las siguientes lineas en caso de que no existan:

"scripts": {
  "start": "babel-watch -L index.js",
  "test": "echo \"Error: no test specified\" && exit 1"
},

Babel-watch es como nodemon, se utiliza para reiniciar automaticamente la aplicación cuando se realice alguna modificación en los archivos del proyecto (similar a nodemon).

Por último utilizaremos eslint para que nos reporte de malas prácticas que nos encontremos utilizando en el código, para ello es necesario ejecutar el siguiente comando (copiar el comando entero en la consola):

$ (
  export PKG=eslint-config-airbnb;
  npm info "$PKG@latest" peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs npm install --save-dev "$PKG@latest"
)

Ahora, agregaremos el archivo de configuración el la raiz del proyecto con el nombre .eslintrc donde extenderemos las reglas de airbnb que son bastante buenas:

{
  "extends": "airbnb",
  "env": {
    "es6": true,
    "node": true
  },
  "globals": {
  }
}

Tambien, ignoraremos la carpeta node_modules que es el lugar donde estan nuestras librerias, para ello crear el archivo .eslintignore con el siguiente contenido:

node_modules/

Agregaremos también la regla para ejecutar el linter de manera sencilla, esto en el archivo package.json:

"scripts": {
  "start": "babel-watch -L index.js",
  "eslint": "eslint .",
  "test": "echo \"Error: no test specified\" && exit 1"
},

Por último creamos el archivo index.js con arrow functions y constantes (uhhhh :D):

const mostrar = () => {
    console.log("funciona");
};

mostrar();

Ahora, primero ejecutaremos el linter con npm:

$ npm run eslint

Nos dara una advertencia sobre el console.log, que no es permitido, se debe utilizar un logger u otra opción, es buena práctica ejecutar el linter siempre que se pueda, al principio es bastante engorroso pero luego ya casi no se necesita porque uno se acostumbra a utilizar las buenas prácticas sugeridas.

Además, podemos probar la aplicación, para iniciarla usaremos start, justo como se configuró en el archivo package.json:

$ npm start

Ahora, al realizar cualquier cambio en el archivo  se puede ver como se reinicia automaticamente la aplicación.

Reinicio automático de la aplicación
Para obtener el proyecto desde git se debe obtener:

$ git clone https://github.com/feardarkness/chat-backend.git
$ git checkout tags/inicio

Puedes continuar con la segunda parte del tutorial, Express y Debug, creando la estructura del proyecto.

Eso es todo por ahora, keep coding >_<

sábado, 4 de febrero de 2017

Binding y Templating en Aurelia

Hello again!!! en este post se mostrará algunas de las principales características del motor de templates de Aurelia que harán nuestro trabajo con la interfaz de usuario mucho más simple de realizar. Todos los templates en Aurelia deben encontrarse entre los tags template, ejm:

<template>
  <require from="./assets/main.css"></require>
  <require from="./resources/elements/bootstrap-input"></require>

  <div>
    <bootstrap-input></bootstrap-input>
  </div>
</template>

Primero, ¿cómo obtenemos una hoja de estilos (css) o un plugin javascript? Para esto es necesario utilizar el tag require de la siguiente manera:

<!-- Para obtener un css -->
<require from="./assets/main.css"></require>

<!-- Para obtener un elemento no es necesario agregar la extensión -->
<require from="../resources/elements/bootstrap-input"></require>

En el primer caso, utilizamos ./ ó ../ para indicar que la ruta es relativa a la vista donde nos encontramos. En caso de no tener ./ ó ../, la ruta es relativa a la raíz de la aplicación. Solo se debe colocar una extensión en caso de que se este importando un elemento que es únicamente HTML o una hoja de estilos.

En caso de tener más de un elemento con el mismo nombre o si se quiere utilizar otro nombre es necesario especificar el atributo as, ejemplo:

<template>
  <require from="./assets/main.css"></require>
  <require from="./resources/elements/bootstrap-input"
          as="otro"></require>

  <div>
    <otro></otro>
  </div>
</template>

Ahora, ¿cómo agregamos texto dinámico en nuestra templeta?, este proceso se conoce como interpolación y es sumamente simple en Aurelia, se utiliza ${algo}.

<div class="row">
  <!-- Imprime la suma de 10 y 15, o sea 25 -->
  <p>${ 10 + 15 }</p>

  <!-- Imprime 50-->
  <p>${ 5 * 10 }</p>

  <!-- Imprime la cadena 'Soy una cadena' -->
  <p>${ 'Soy una cadena' }</p>

  <!-- Imprime el valor de la variable message del ViewModel -->
  <p>${ message }</p>
</div>

Además, es posible hacer referencia a un elemento en el DOM, para esto solamente se necesita agregar el atributo ref al elemento y luego se lo puede referenciar tanto en el ViewModel como en la misma templeta.

Por ejemplo, en el siguiente fragmento de código le damos el nombre campoNombre al input, para luego obtener su valor en el tag <p>.

<div class="col-sm-10">
  <input type="text" id="nombre" name="nombre" ref="campoNombre">
  <p>${campoNombre.value}</p>
</div>

Otra característica importante es que podemos "enlazarnos" (binding) a propiedades de los tags html, por ejemplo, podemos enlazarnos a la propiedad style de la siguiente manera:

    <div class="col-sm-10" style.bind="campoNombre.value">
      <input type="text" id="nombre" name="nombre" ref="campoNombre">
      <p>${campoNombre.value}</p>
    </div>

En este caso, se cambia el estilo del div de acuerdo a lo que se tiene anotado en el input, como se puede ver, se hizo una referencia al input con el nombre campoNombre, y luego se utilizo el valor (value) de este elemento en el div con style.bind. En caso de querer trabajar con Explorer o Edge, es necesario hacer el bind a la propiedad css que es un alias de style.

También se puede hacer un bind al value de un elemento como se ve a continuación, además, como los bindings son two-way por defecto, cambiando el valor de un input se reflejan los cambios en el otro.

    <div class="col-sm-10" style.bind="campoNombre.value">
      <input type="text" id="nombre" name="nombre" ref="campoNombre">
      <p>${campoNombre.value}</p>
      <input type="text" id="nombre1" name="nombre1" value.bind="campoNombre.value">
    </div>

Ejemplo de bind!!!
Además de bind, que es two-way por defecto para los controles de formularios, se puede utilizar one-way (fluye del view-model a la vista), one-time (fluye solamente una vez del view-model a la vista) y two-way.

Tambien es posible enlazarnos a eventos, para esto se utiliza trigger o delegate. La regla de oro es utilizar delegate siempre que sea posible hacerlo. Como ejemplo, se agregará unos eventos al input recientemente creado:

      <input type="text" id="nombre1" name="nombre1"
              focus.trigger="handleEvent($event)"
              keypress.delegate="handleEvent($event)"
              blur.trigger="handleEvent($event)">
      <p>${evento}</p>

Y el manejador simplemente será:

  evento = 'Aún no han ocurrido eventos';

  handleEvent(event) {
    this.evento = event;
    return true;
  }

Bind a eventos


Para los eventos focus y blur es necesario utilizar trigger (ninguno de estos elementos burbujea; en general, como los elementos en html se encuentran uno dentro del otro el burbujeo implica que el evento se va replicando a los padres del elemento en que ocurrió el evento), mientras que para el evento keypress se utilizará delegate (keypress si burbujea), además se esta llamando una función con el parámetro $event que Aurelia establece para enviarnos el evento que se ha realizado. Como comentario extra, en la función handleEvent se debe hacer un return true para que el evento se propague (En general, el término burbujear y propagar se utilizan con el mismo significado)

Al realizar templetas en Aurelia, contamos con algunos atributos de control como:

  • if
  • show
  • repeat.for
Cambiaremos el mismo ejemplo para mostrar el tag p cuando el valor del primer input sea 'mostrar', para esto utilizaremos if.bind, con la verificación de que el valor del input sea igual a mostrar:

<input type="text" id="nombre" name="nombre" ref="campoNombre">
      <p if.bind="campoNombre.value === 'mostrar'">${campoNombre.value}</p>

if-bind en vivo!!!!

Además, vamos a utilizar repeat para mostrar una lista. Existen algunas variables extras en el contexto del repeat que nos ayudan a identificar varias propiedades del elemento que se esta repitiendo, las cuales son:
  • $index: con el índice del iterador
  • $first: primer elemento
  • $last: último elemento
  • $even: elementos pares
  • $odd: elementos impares
En el ejemplo, creamos una lista en el view model y luego la mostramos en pantalla:

export class App {

  listaDeFrameworks = ['Aurelia', 'EmberJS', 'Angular', 'React', 'Vue', 'Backbone'];
}

<div>
  <ul>
    <li repeat.for="framework of listaDeFrameworks"
        style="${$odd? 'background-color:yellow' : 'background-color: cyan'}">
      ${$index}. ${framework}
      <span if.bind="$first"> - Primer elemento</span>
      <span if.bind="$last"> - Último elemento</span>
    </li>
  </ul>
</div>
Ejemplo de la lista

Y eso es todo por ahora, para obtener el ejemplo desde github:

$ git clone https://github.com/feardarkness/HotelReservationAppFE.git
$ git checkout tags/templating

And thats it, keep coding >_<