Do you want to switch to Platzi in English?
Tu lugar en PlatziLive
Tu lugar en PlatziLive
estamos en vivo
2

Crea microservicios en Node.js con micro.js

20657Puntos

hace 2 años

Un microservicio es una pequeña aplicación que se encarga de una parte de un software más complejo de manera aislada, y se comunica con el resto del software mediante diferentes métodos: peticiones HTTP, o algún sistema de colas. micro.js es una librería muy pequeña (alrededor de 100 líneas de código) de JavaScript, que nos permite usar Node.js para crear fácilmente microservicios que funcionen sobre el protocolo HTTP, y haciendo uso de Async/Await y todas las características que se incluyeron en ECMAScript 2015 para facilitarnos el programarlos.

tl;dr - En este enlace está el repositorio de GitHub para que explores el código.


Creando un proyecto con micro.js

Para verlo en práctica, vamos a crear un pequeño microservicio que reciba un nombre de usuario de Platzi y nos devuelva la lista de cursos aprobados (haciendo scrapping desde los nuevos perfiles públicos de usuario). Creamos entonces el package.json de nuestro proyecto, con el siguiente contenido:
{
  "name": "micro-platzi-profile",
  "main": "./index.js",
  "repository": "sergiodxa/micro-platzi-profile",
  "scripts": {
    "start": "micro -p 3000 ."
  },
  "dependencies": {
    "isomorphic-fetch": "2.2.1",
    "jsdom": "9.4.2",
    "micro": "6.0.1"
  }
}
Luego, instalas todas las dependencias usando npm install. Mientras lo hace, revisaremos qué definiste en el package.json: lo primero es el nombre del proyecto; luego, el archivo principal (entry point), su repositorio en GitHub, y a continuación los scripts, donde tenemos la única entrada: start. Éste iniciará micro en el puerto 3000, usando el archivo index.js, donde estará el código del servidor. En cuanto a las dependencias:
  • isomorphic-fetch: Da soporte a Fetch API en Node.js.
  • jsdom: Permite tener un DOM falso en el servidor, con todos los métodos normales, basado en el string HTML que le indiques.
  • micro: La librería para crear microservicios que estamos aprendiendo a utilizar.
AVISO: La versión de micro que utilizaremos soporta desde Node.js v6 hacia arriba, ya que no transpila nada del código en ES2015 que usemos (internamente transpila Async/Await, pero no tenemos que hacer nada). Para usar versiones viejas de Node.js, es necesario usar versiones anteriores de micro. La base de este proyecto, como te contaba, es hacer un scrapper de los perfiles de usuarios de Platzi. ¡Y vamos a empezar por eso! Primero, importas las dependencias que usarás:
const { env } = require('jsdom');
const fetch = require('isomorphic-fetch');

> Debido a que no vamos usar Babel, y micro no transpila nada de ES2015, tenemos que usar require de CommonJS. Igualmente nada impide que se use Babel para soportar módulos de ES2015.<

Ya que vamos a usar Async/Await, vamos a envolver jsdom en una Promesa para facilitarnos su uso y no tener que usar callbacks:
/**
 * Promise wrapper for jsdom
 * @param  {string} html The HTML string to use
 * @return {Object}      The `window` object
 */
function jsdomPromised(html) {
  return new Promise((resolve, reject) => {
    env(html, [], (error, window) => {
      if (error) return reject(error);
      return resolve(window);
    });
  });
}
Ahora, programamos el scrapper: una función asíncrona que va a recibir un nombre de usuario y obtendrá el HTML del perfil de éste, para luego obtener el nombre, la descripción y la información de los cursos aprobados, y devolver un JSON con estos datos.
/**
 * Get user info from the user Profile
 * @param  {string} username The user to scrap
 * @return {Object}          The scrapped data
 */
async function scrapper(username) {
  // la url a donde vamos a hacer el request
  const url = `https://platzi.com/@${username}`;
  // hacemos el request con Fetch y obtenmos la respuesta
  const response = await fetch(url);
  // leemos la respuesta como text (esto nos da el HTML como un string)
  const html = await response.text();

  // creamos nuestro DOM falso con `jsdom`, esto nos da un objeto `window`
  const window = await jsdomPromised(html);
  // y de este objeto obtenemos el objeto `document`
  const { document } = window;

  // obtenemos el nombre del usuario
  const name = document.querySelector('.ProfileHeader-name').innerHTML;
  // y su descripcion
  const description = document.querySelector('.ProfileHeader-description').innerHTML;
  // y por última su lista de cursos con el nombre, la carrera, el porcentaje y el badge
  const courses = Array
    .from(document.querySelectorAll('.Course'))
    .map($course => {
      // obtenemos el título del curso
      const title = $course.querySelector('.Course-title').childNodes[1].data;
      // la carrera a la que pertenece
      const career = $course.querySelector('.Course-career').innerHTML;
      // el porcentaje completado del curso
      const percentage = $course.querySelector('.Course-percentage').childNodes[1].data;
      // la url del badge
      const badgeUrl = $course.querySelector('.Course-badge > img').getAttribute('src');

      return { title, career, percentage, badgeUrl };
    });

  // devolvemos un objeto con todos los datos que obtuvimos del usuario
  return {
    url,
    user: {
      username,
      name,
      description,
    },
    courses,
  };
}

module.exports = scrapper;

>> En el repositorio de Github tienes una versión más compleja, que devuelve aún más datos del usuario. <

Programando el servidor

Por último, nos falta el servidor que se encargará de recibir peticiones HTTP. Primero, al igual que antes, vamos a importar nuestras dependencias usando require:
const { parse } = require('url');
const scrapper = require('./lib/scrapper.js');
Del módulo nativo de Node.js url,nos vamos a traer el método parse e importarás la función del scrapper ya creado. Ahora, crearás la función encargada de manejar las peticiones que lleguen.
const EXAMPLE_URL = 'https://micro-platzi-profile.now.sh?username=some-user-name';

module.exports = async request => {
  // parseamos la url para obtener la query (los parámetros que llegan como `?algo=valor`)
  const { query } = parse(request.url, true);

  // si no se envió un `username` vamos a devolver un error 400
  if (!query.username) {
    // creamos el objeto de erro y definimos una propiedad `statusCode`
    const error = new ReferenceError(
      `You must query for a specific username using a URL like ${EXAMPLE_URL}.`
    );
    error.statusCode = 400;
    throw error;
  }

  // en caso contrario, vamos a ejecutar el scrapper con el username recibido
  // y devolver la respuesta
  return await scrapper(query.username);
};
Esta simple y pequeña función será tu servidor HTTP. Como aparece en los comentarios del código, recibirá el request; obtendrá los parámetros de la query (validando que lleguen), y ejecutará el scrapper para obtener los datos y enviarlos en la respuesta. Nota como estamos exportando la función directamente, y en ningún lado importamos micro. Esto es porque este nos permite simplemente exportar una función asíncrona que haga return de la respuesta, o un throw de un error. Al iniciar micro desde la terminal, este se encarga de importar nuestra función y crear el servidor en el puerto indicado. De todas formas, micro posee métodos propios que podemos importar, y que te explicaré rápidamente:
  • micro(fn, { onError = null }): Recibe la función del servidor (la que exportamos) y devuelve un servidor HTTP que puedes usar donde quieras. El onError es una función que se ejecutará cuando recibe un error, y te permite manejar todos los errores.
  • json(req, { limit = '1mb' }): Este método recibe el objeto request de la petición y te devuelve una promesa que, al completarse, te dará el body de la petición HTTP. El limit indica el límite de datos posibles a recibir en el body; por defecto es 1 megabyte.
  • send(res, statusCode, data = null): Envía una respuesta, recibe el objeto response (segundo parámetro de la función que exportaste), el código de status HTTP a usar y los datos a enviar. Si envías un objeto de JS, automáticamente lo convierte a un string JSON. De forma similar, se encarga solo de manejar los Stream y Buffer.
  • sendError(req, res, error): Recibe los objetos requestresponse y un objeto Error, y envía el mensaje de error en la respuesta con el código de status HTTP definido en error.statusCode (500, por defecto).
  • createError(code, msg, orig): Crea un objeto error con el statusCode que le indiques, el mensaje y un orig o error original si lo estás creando en base a otro error. Esta función abstrae lo que hiciste para definir el statusCode en el servidor.
¡Eso es todo el API que trae micro! Como vimos, no es necesario usar ni un solo método del API para crear un microservicio fácilmente. Aunque si necesitas mejor manejo de errores, o más control de qué enviar en diferentes casos, deberás usarlos obligatoriamente.

Despliegue

Ya estás listo para desplegar el microservicio a producción usando, por ejemplo, now.sh. Para esto, sólo debes instalar el paquete now de forma global usando npm. Luego, simplemente desde la terminal ejecutas el comando now;te pedirá un correo electrónico donde recibirás un enlace de confirmación. En cuanto entres, tendrás disponible un token de autenticación para no volver a ingresar tus credenciales, y comenzará el proceso de despliegue. Podrás seguir todo el proceso en la terminal o ingresando a una URL que now copia al portapapeles automáticamente. Si tomas esta última opción, al finalizar el sitio se recargará y verás el proyecto funcionando — o un mensaje de error, si algo salió mal. Puedes entrar al ejemplo que trabajamos, que ya está funcionando en https://micro-platzi-profile.now.sh/?username=sergiodxa; si ingresas a https://micro-platzi-profile.now.sh/_src, puedes ver el código final (con scrapping de más datos). Este también está en el repositorio en GitHub. Por último, si usamos Docker, podrías crear un Dockerfile  para meter en un container nuestro microservicio, y poder usar herramientas como Kubernetes para controlarlo.

Conclusión

Trabajar con microservicios se vuelve muy útil cuando tenemos aplicaciones grandes, ya que nos ayudan a descomponerla en partes más pequeñas, más fáciles de probar y escalar, y que en caso de una caída, no afecte a todo el sistema.
Sergio Daniel
Sergio Daniel
@sergiodxa

20657Puntos

hace 2 años

Todas sus entradas
Escribe tu comentario
+ 2
0
10793Puntos

Debido a que no vamos usar Babel, y micro no transpila nada de ES2015, tenemos que usar require de CommonJS. Igualmente nada impide que se use Babel para soportar módulos de ES2015.<

Sergio como sería este código utilizando las características de ES6?

0

hay algun curso sobre microservicios con node?