Seleccionar página
Como comentamos en un artículo anterior, los sistemas RPC son extremadamente flexibles y permiten llamar a funciones remotas de forma prácticamente transparente, pudiendo centrar nuestros esfuerzos de desarrollo en la funcionalidad y no en los mecanismos de comunicación entre las distintas partes de nuestra aplicación.

Hemos creado una aplicación de ejemplo para poder comprender mejor cómo podemos utilizar la librería jsRPC para gestionar de forma muy sencilla las llamadas a procedimientos remotos de forma transparente. En esta aplicación hemos utilizado el modelo RPC para que el navegador invoque funciones del servidor y para que los diferentes microservicios invoquen de forma remota funciones entre ellos.

El código fuente completo de este ejemplo lo puedes obtener en:

https://github.com/todojs/jsrpc-example

Es un proyecto con un cliente HTML + JS Vanilla (se puede usar cualquier framework front como React, Angular o Vue, pero hemos querido no distraer de la implementación) y un servidor en NodeJS con NeDB como sustituto a la base de datos (se podría haber usado MongoDB o una base de datos SQL).

Core

En primer lugar, hemos creado tres objetos: courses, teachers y students. Como es fácil adivinar, es una micro aplicación de ejemplo para la gestión de cursos. Cada uno de estos objetos tiene los siguientes de métodos que realizan las operaciones que necesitamos en nuestra aplicación:

  • .list([query][, sort]) obtiene todos los registros en base a una query (por defecto devuelve todos) y un criterio de ordenación (por defecto se ordena por nombre).
  • .get(id) obtiene los datos detallados de un registro.
  • .add(data) crea un nuevo registro.
  • .edit(id, data) actualiza los valores de un registro.
  • .del(id) elimina un registro.

Todos estos métodos devuelven una promesa. Si tenéis curiosidad por cómo están implementados en detalle estos métodos, podéis ver su código fuente en:

microservicios/courses/coures.js
microservicios/teachers/teachers.js
microservicios/students/students.js

Estos servicios se han probado con una serie de tests que también están incluidos en el repo y que por medio de Mocha y Chai van llamando a cada uno de los métodos comprobando diferentes combinaciones y casos.

Sub y Skeleton

La idea es que estos métodos se utilicen como llamadas locales (dentro del mismo espacio de direcciones, es decir, dentro del mismo programa) o como llamadas remotas (RPC) sin que tengamos que cambiar nada. Es decir, que si quiero obtener una lista con todos los alumnos (tanto remotamente como localmente) sólo tendré que llamar a:

students.list().then(function(data) {
  // …
});

Para ello vamos a hacer uso de las funciones de jsRPC para crear el programa stub que utilizaremos en el cliente y el programa skeleton que usaremos en el servidor. Antes de esto tenemos que instalar la librería con:

npm install @todojs/jsrpc –save

En nuestro repo, al tenerlo ya incluido en las dependencias, basta con:

npm install

Para cada uno de los objetos se ha creado su programa squeleton, que se ejecutará en la parte remota, con la función skeletonify() de la siguiente forma:

const courses     = require ('./courses');
const skeletonify = require ('@todojs/jsrpc/skeletonify');
module.exports    = skeletonify ('courses', courses);

De igual forma, para cada uno de los objetos se ha creado su programa stub, que se ejecutará en la parte local, con la función stubify() de la siguiente forma:

(root => {
  const stubify = typeof window !== 'undefined' ? window.stubify : require ('@todojs/jsrpc/stubify');
  const courses = stubify ("http://localhost:9001", 'courses');

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = courses
  } else {
    root.courses = courses;
  }
}) (this);

El código de los stub está preparado para ser utilizado desde un navegador y desde un programa Node de forma indistinta, de esta forma podremos llamar remotamente a estas funciones desde cualquier sitio que las necesitemos.

Estructura de ficheros

Cada objeto de nuestra aplicación tiene ahora tres ficheros, uno con su código: courses.js, otro con su programa skeleton: courses-squeleton.js y otro con su programa stub: coures-stub.js, y podemos distribuirlos según nuestras necesidades.

En muchas ocasiones el fichero con el stub tiene el mismo nombre que el fichero con los métodos originales, lo cual simplifica la sustitución de uno por otro, ya que no cambian el nombre del módulo. En nuestro caso los hemos dejado con nombres diferentes sólo con fines didácticos.

Desde el navegador vamos a querer llamar de forma remota a los tres objetos courses, teachers y students, por lo que hemos incluido sus stub dentro de la carpeta front, que incluye todo lo que vamos a servir al cliente. Como estos programas hacen uso de stubify() también hemos movido aquí esta función desde la carpeta node_modules, aunque también podríamos haber hecho una referencia para evitar realizar esta copia del fichero.

En el servidor hemos organizado la ejecución en tres microservicios, uno por cada uno de los objetos. Para simplificar el funcionamiento lo hemos preparado para que cada uno arranque en un puerto diferente:

  • courses: puerto 9001
  • teachers: puerto 9002
  • teachers: puerto 9003

Cómo los métodos del objeto courses llaman a su vez a métodos de los objetos teachers y students, y como estos se van a ejecutar en otro microservicio, hemos incluido los stub de estos objetos dentro de la carpeta del microservicio courses. Cada uno de los microservicios tiene su propia carpeta con los datos que maneja, no accediendo directamente nunca unos a los datos de los otros.

En cada microservicio también hay un fichero index.js que simplemente configura el puerto y llama al programa skeleton de cada uno de ellos:

process.env.port = process.env.port || 9001;
require ('./courses-skeleton');

Ejecutar el ejemplo

Ya lo tenemos todo preparado, por lo que podemos empezar a arrancar cada uno de nuestros microservicios con:

cd microservices/courses
node index.js
cd microservices/teachers
node index.js
cd microservices/students
node index.js

Ahora arrancamos el servidor http para que nos sirva la página de ejemplo:

./node_modules/.bin/http-server ./front -o

Si todo ha funcionado correctamente, se abrirá un navegador y podremos ir consultando las diferentes pantallas:

Llamadas entre los elementos

Para comprender un poco mejor cómo funciona, vamos a analizar cómo consultamos un detalle de un curso en el navegador. Para obtener todos los cursos se está haciendo una llamada de este tipo:

courses.get (id)
  .then (function (result) {
    //...
  })
  .catch (function (err) {
    error (err.message);
  });

Este método courses.get() se ejecuta en el servidor a través del sistema jsRPC, y este a su vez llama a métodos de students y de teachers:

  const currrentTeachers = await teachers.list ({_id : {$in : data.teachers}});
  // ...
  const currrentStudents = await students.list ({_id : {$in : data.students}});

Tal y como lo hemos configurado, estas llamadas son enviadas a los microservicios correspondientes que ejecutan remotamente las funciones que les hemos solicitado.

Ejecutar estos métodos local o remotamente sólo cambia si estamos invocando al módulo directamente o a su sustituto el stub, que es quien ser encarga de gestionar la llamada remota, por lo que nuestro código no se ve afectado por este cambio.

Conclusión

De esta forma podemos gestionar todas las comunicaciones entre las diferentes piezas de nuestro sistema de forma absolutamente transparente. Esta es la gran ventaja de las soluciones RPC, que nos permiten centrarnos en la funcionalidad de nuestro código y gestionan por nosotros todas las características internas de la comunicación entre los diferentes procesos.

Os invitamos a explorar este ejemplo y descrubir por vosotros mismos las capacidades que tiene el sistema jsRPC en concreto y las soluciones RPC en general.

Novedades

Template a fondo

Hay dos formas estándar de crear contenido en un componente de forma flexible: la etiqueta template, que se considera como uno de los pilares de los Web Components y las template string de Javascript, que son una buena alternativa para generar el Shadow DOM con interpolación de datos.

Light DOM a fondo

El Light DOM es un espacio compartido entre nuestro componente web y el DOM general, que podemos utilizar para insertar contenido o configurar nuestro componente. Es una muy interesante característica que debemos conocer.

Shadow DOM a fondo

Para que los componentes web no colisionen unos con otros es muy útil utilizar el Shadow DOM para aislar el DOM y el CSS de cada componente. Esta característica se puede aplicar también a elementos HTML sin necesidad de utilizar Custom Elements, pero es con estos donde cobra todo su potencial. Demos un repaso profundo a las capacidades del Shadow DOM.

HTMLElement a fondo

HTMLElement es una pieza clave, ya que de él heredan todos los componentes web, pero en muchas ocasiones no conocemos bien sus características. Os invitamos a dar un repaso a fondo a sus capacidades y descubrir cómo sacarle todo el partido en nuestros componentes.