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.
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:
<pre class=“EnlighterJSRAW” data-enlighter-language=“json”>{
“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”
}
}</pre>
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:
<pre class=“EnlighterJSRAW” data-enlighter-language=“js”>const { env } = require(‘jsdom’);
const fetch = require(‘isomorphic-fetch’);</pre>
> 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:
<pre class=“EnlighterJSRAW” data-enlighter-language=“js”>/**
window
objectAhora, 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.
<pre class=“EnlighterJSRAW” data-enlighter-language=“js”>/**
https://platzi.com/@${username}
;// 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;</pre>
>> En el repositorio de Github tienes una versión más compleja, que devuelve aún más datos del usuario. <
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
:
<pre class=“EnlighterJSRAW” data-enlighter-language=“js”>const { parse } = require(‘url’);
const scrapper = require(’./lib/scrapper.js’);</pre>
Del módulo nativo de Node.js [url](https://nodejs.org/api/url.html)
,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.
<pre class=“EnlighterJSRAW” data-enlighter-language=“js”>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);
};</pre>
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 request, ``response
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.
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.
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.