Construya una aplicación altaente escalable con Node.js y Redis

Una de las razones más imperiosas para usar IBM Cloud para ejecutar su aplicación es la habilidad de escalar rápida y fácilmente su aplicación. Con las ofertas de Infraestructura como servicio (IaaS) tradicional, para escalar la aplicación sería necesario comprar imágenes virtuales adicionales, configurar esas imágenes, implementar su aplicación y configurar cierto tipo de equilibrador de carga para distribuir la carga entre las nuevas imágenes. Con IBM Cloud y su catálogo lleno de servicios, se puede lograr todo eso con solo hacer clic en un botón.

Webcast: Construcción de aplicaciones altamente escalables para IBM Cloud https://event.on24.com/eventRegistration/EventLobbyServlet?target=registration.jsp&eventid=818931&sessionid=1&key=8EF738F5818B4963811738611F934E48&partnerref=CL081914-IBM-05&sourcepage=register?cm_mmc=IBMDevProg-_-Quinstreetwebinarseries-_-Q3-_-Webcast02

Si bien la escalación es muy fácil de usar en IBM Cloud, no significa que todas las aplicaciones vayan a funcionar correctamente cuando se las escale. A menudo, las aplicaciones que se ejecutan en las instalaciones almacenarán el estado en la memoria o dentro del sistema de archivo local. Estos tipos de aplicaciones a menudo no pueden escalarse en la nube ya que las solicitudes del cliente se despacharán al azar a diferentes instancias de la aplicación que se ejecuta en la nube. El estado de la aplicación en una instancia no será el mismo que en ninguna otra instancia de la aplicación. Para abordar esto, las ofertas de Plataforma como Servicio (PaaS) como IBM Cloud proporcionan servicios que las aplicaciones pueden usar para compartir el estado entre múltiples instancias.

Mostraré cómo construir una aplicación de conversación que les permita a los usuarios enviar mensajes en tiempo real a otros usuarios, escalando la aplicación entre múltiples instancias para manejar la carga.

Ejecute la aplicaciónObtenga el código

Guía de inicio

Para construir esta aplicación, necesitará lo siguiente.

  1. Familiaridad básica con HTML, JavaScript, CSS y Node.js
  2. Node.js y NPM instalado. NPM se instalará con Node.js.

Creación del proyecto

Vaya a un directorio en su sistema de archivo local en donde le gusta guardar sus trabajos y cree una nueva carpeta denominada bluechatter.

 $ mkdir bluechatter

App.js

En el directorio bluechatter, cree un archivo denominado app.js. Dentro de app.js, pegue el siguiente código para crear un servidor web básico con la popular biblioteca Node.js, Express JS.

 var express = require("express"); var fs = require('fs'); var http = require('http'); var path = require('path'); var app = express(); app.set('port', 3000); app.use(express.static(path.join(__dirname, 'public'))); app.use(express.json()); // Serve up our static resources app.get('/', function(req, res) { fs.readFile('./public/index.html', function(err, data) { res.end(data); }); }); http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port'));});

Las aplicaciones Node.js utilizan un archivo denominado package.json para describir los metadatos básicos acerca de la aplicación, y también proporcionan una lista de dependencias. Dentro de la carpeta bluechatter, cree un archivo denominado package.json y pegue el siguiente código.

 { "name": "BlueChatter", "version": "0.0.1", "scripts": { "start": "node app.js" }, "dependencies": { "express": "3.4.8" } }

Esto le da a su aplicación un nombre, versión y script de inicio. También especifica la única dependencia que tiene hasta ahora la aplicación: Express. Luego, añadirá dependencias a la aplicación.

index.html

En el directorio bluechatter, cree una carpeta denominada public. Dentro del directorio public, cree un archivo denominado index.html y pegue el siguiente código.

    <meta http‑equiv="X‑UA‑Compatible" content="IE=edge">  BlueChatter <!‑‑ Bootstrap ‑‑>   <!‑‑ HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries ‑‑> <!‑‑ WARNING: Respond.js doesn't work if you view the page via file:// ‑‑> <!‑‑[if lt IE 9]>   <![endif]‑‑>    BlueChatter  Enter a username to get started  Go!        

Como puede ver a partir de este HTML, utilizamos Bootstrap y jQuery, ambos se recuperan desde una red de entrega de contenido (CDN). Además, hay referencias a style.css y client.js. Estos son archivos que utilizaremos para agregar estilos de cliente y lógica comercial a nuestra aplicación.

Primero, creemos style.css.

style.css

En la carpeta public, cree una carpeta denominada stylesheets. Dentro de la carpeta stylesheets, cree un archivo denominado style.css y pegue el siguiente código CSS en él.

 #main‑container { margin: 0 auto; } #main‑container h1 { text‑align: center; } .center { text‑align: center; max‑width: 430px; padding: 15px; margin: 0 auto; } .go‑user { margin‑top: 10px; } .chat‑box { max‑width: 930px; padding: 15px; margin: 0 auto; } .message‑area { max‑height: 500px; overflow: auto; } .footer { padding‑top: 19px; color: #777; border‑top: 1px solid #e5e5e5; }

Ahora, creemos client.js.

client.js

En la carpeta public, cree una carpeta llamada javascripts. Dentro de la carpeta javascripts, cree un archivo denominado client.js. Añada el código a continuación y guarde el archivo; completará el resto del código más tarde.

 $(document).ready(function() { });

Prueba de la aplicación

En este punto, tiene el código básico del lado del servidor y del cliente implementado para probar la aplicación. Antes de ejecutar la aplicación, debe ejecutar el comando npm install dentro del directorio bluechatter para instalar todas las dependencias necesarias. Abra una ventana terminal y ejecute los siguientes comandos.

 $ cd bluechatter $ npm install

Cuando ejecute npm install, debe tener una conexión a Internet activa ya que npm descargará las dependencias de código necesarias. Una vez que npm termina de instalar las dependencias, puede iniciar el servidor. Ejecute el siguiente comando desde dentro del directorio bluechatter.

 $ node app.js

Si todo funciona correctamente, verá «Express server listening on port 3000» en la ventana de su terminal. Abra su navegador favorito y desplácese hasta http://localhost:3000. En su navegador, verá algo similar a esto.

Image shows sample browser result

Código del lado del cliente

Hacer que el botón Go! funcione

En este punto, si hace clic en el botón Go! de la aplicación, no ocurrirá nada. Esto se debe a que aún no hemos definido su manejador de clics.

Abra public/javascripts/client.js desde el directorio bluechatter en su editor de textos favorito. Agreguemos una función dentro de document ready callback del siguiente modo:

 var name = ''; function go() { name = $('#user‑name').val(); $('#user‑name').val(''); $('.user‑form').hide(); $('.chat‑box').show(); };

Este fragmento de código es bastante directo. Lo que realmente estamos haciendo es manipular el DOM. Obtenemos el nombre que el usuario ingresó en el campo de nombre de usuario de la IU y lo guardamos en una variable ya que lo necesitaremos más adelante. A continuación, ocultamos el formulario y el botón, y mostramos el recuadro de conversación donde el usuario puede ver y enviar mensajes de conversación. (El recuadro de conversación aún no se definió en nuestro HTML; llegaremos a eso en un momento). Aún debemos llamar a esta función desde el receptor de clics del botón. Para esto, añada este código dentro de document ready callback.

 $('#user‑name').keydown(function(e) { if(e.keyCode == 13){ //Enter pressed go(); } }); $('.go‑user').on('click', function(e) { go(); });

Esta código no solo añade un receptor de clics al botón Go! sino que también añade un receptor de presión de teclas al campo de nombre de usuario, por lo tanto, si el usuario presiona la tecla Enter en el campo, hará lo mismo que hacer clic en Go!.

Por último, debemos agregar algunos códigos HTML para ver y enviar mensajes de conversación. Abra el archivo index.html dentro de la carpeta bluechatter/public. Busque dentro del div del contenedor principal y añada el siguiente fragmento de HTML dentro del contenedor principal div luego del contenido que ya está allí.

   It Is Quiet In Here! No one has said anything yet, type something insightful in the text box below and press enter!       

Si aún tiene el servidor de Node en ejecución, deténgalo presionando Ctrl+c y ejecute el siguiente comando para iniciar nuevamente el servidor.

 node app.js

Si vuelve a http://localhost:3000, el botón Go! debería funcionar. Ingrese un nombre de usuario y haga clic en Go!. Debería ver esto.

Figure 1. Envío de mensajes de conversación
Image shows sending chat messages

Si intentara «escribir algo ingenioso» en el área de texto y presionara Enter, como le indica la IU, notará que nuevamente no ocurre nada.Debemos definir lo que debe ocurrir cuando se presiona la tecla Enter . ¿Qué debería ocurrir? Debemos decirle al servidor dos cosas cuando se envía un mensaje: el usuario que envía el mensaje y el mensaje que se envía.

Para decirle esta información al servidor, llamaremos un API de REST. El API de REST aún no se definió en el código del servidor. Así que por ahora, asumiremos que el punto final del API de REST es /msg y acepta JSON. Añadamos en receptor de entradas por teclado en el área de texto que llamará a nuestra API de REST ficticia.

 $('.chat‑box textarea').keydown(function(e) { if(e.keyCode == 13){ $.ajax({ type: "POST", url: "/msg", data: JSON.stringify({"username" : name, "message" : $('#message‑input').val().trim()}), contentType: "application/json" }); $(this).val(''); $('.jumbotron').hide(); e.preventDefault() } });

Puede ver que este código añade un receptor de entradas por teclado en el área de texto. Cuando se presiona la tecla Enter (código de tecla 13), utilizaremos la API jQuery Ajax para hacer una solicitud de POST para /msg. Los datos que enviamos en la solicitud Ajax son un objeto JSON que contiene el nombre de usuario y el mensaje. También hacemos un poco de limpieza de la casa en el extremo del receptor. Borramos el área de texto, ocultamos la IU que dice «it is quiet in here» (porque ya no está tranquilo), y luego evitamos que el evento se propague llamando a preventDefault.

Probémoslo ejecutando el comando node app.js y yendo a http://localhost:3000. Ingrese un nombre de usuario, escriba un mensaje y presione Enter. Parece que no ocurrió nada, ¿verdad? Bueno, no exactamente. Abra las herramientas del desarrollador del navegador en el que se encuentra (como Firebug en Firefox) y vaya a la pestaña de la consola. Ahora, escriba otro mensaje y presione Enter. En la consola, debería ver que se está efectuando una solicitud al punto final /msg. Este es un ejemplo de cómo se ve Firebug dentro de Firefox.

Image shows Firefox Firebug example

Sin embargo, la solicitud falla porque nuestro servidor Node no define /msg. Nos ocuparemos de eso más tarde ya que aún tenemos algo más que hacer en el JavaScript del lado de nuestro cliente.

Hagamos un sondeo

La última pieza del rompecabezas del código del lado del cliente es el código que recibe mensajes de conversación de otros usuarios. Para crear este código, aprovechamos una técnica llamada sondeo largo. El sondeo largo hace exactamente lo que anuncia: realiza un sondeo del servidor, y en ocasiones, la respuesta del sondeo lleva mucho tiempo. En el caso de nuestra aplicación, el cliente realizará una solicitud al servidor, y el servidor esperará para responder a esa solicitud hasta que tenga algunos datos para enviar de regreso al cliente. En cuanto el servidor responde, el cliente inmediatamente envía otra solicitud de sondeo de regreso al servidor para que pueda recibir el siguiente mensaje que ingresa. Para esto, haremos una solicitud GET a una API de REST en el servidor. El servidor responderá con un objeto JSON que contenga el nombre de usuario y el mensaje de conversación enviado por ese usuario.

Añada el siguiente fragmento de código a client.js dentro del document ready callback.

 function poll() { $.getJSON('/poll/' + new Date().getTime(), function(response, statusText, jqXHR) { if(jqXHR.status == 200) { $('.jumbotron').hide(); msg = response; var html = '' + msg.username + \ '' + msg.message + ''; var d = $('.message‑area'); d.append(html); d.scrollTop(d.prop("scrollHeight")); } poll(); }); };

Nuevamente, estamos utilizando jQuery para ayudarnos a realizar la llamada de API de REST. La API getJSON hace una solicitud a /poll. Notará que también anexamos la hora actual. Hacemos esto porque los navegadores enviarán a la cola las solicitudes realizadas al mismo punto final; no deseamos esto, por lo que hacemos que el punto final aparezca como único anexando la hora actual. En el callback, primero verificamos para asegurarnos de que la respuesta sea 200, lo que indica una solicitud exitosa. Nuevamente, ocultamos el mensaje de que no sucede nada de «jumbotron» (ya que ya no es así) y anexamos algunos códigos HTML en el área de conversación con el nombre de usuario y el mensaje. Por último, nos desplazamos por el área de conversación hasta la parte inferior para que el nuevo mensaje esté siempre visible. También notará que la función es recursiva ya que se llama así misma. Este es el sondeo en el patrón de sondeo largo.

El desarrollador de observación notará que nada llama a nuestra función de sondeo para que se inicie el sondeo. ¿Cuándo deseamos comenzar el sondeo? Ni bien el usuario presione Go! . Ya contamos con una función que maneja el clic del botón Go! , así que llamemos a nuestra función de sondeo desde ese receptor. Añada una llamada de función de sondeo hasta el final de la función go .

 function go() { name = $('#user‑name').val(); $('#user‑name').val(''); $('.user‑form').hide(); $('.chat‑box').show(); poll(); };

Es tiempo de probar nuevamente nuestra aplicación. Ejecute node app.js en su terminal. En su navegador, asegúrese de tener abierta su consola de desarrollador y vaya a http://localhost:3000. Ingrese un nombre de usuario y haga clic en Go!. Debería ver nuestra solicitud de sondeo mientras se estaba haciendo — y mientras fallaba, tal como se muestra a continuación.

Image shows poll request failing

Nuevamente, no hemos implementado la API de REST en el servidor, por lo que esto es esperable.

Código del lado del servidor

Ahora que el código del lado de nuestro cliente está funcionando conforme a lo esperado, debemos comenzar a construir las API de REST en el servidor que utiliza el cliente.

Los resultados del sondeo se encuentran en

Primero implementemos nuestro punto final de sondeo. Como lo mencionamos, nuestra aplicación utiliza el sondeo largo, por lo que nuestro punto final de sondeo manejará las solicitudes de sondeo de los clientes y responderá con nuevos mensajes de conversación mientras se envían al servidor.

Abra app.js y añada el siguiente código.

 var clients = []; // Poll endpoint app.get('/poll/∗', function(req, res) { clients.push(res); });

No ocurre mucho aquí. Todo lo que hacemos es manejar la solicitud en el punto final /poll, tomar la respuesta y colocarla en un objeto de matriz. Aquí es donde la parte larga del sondeo largo entra en acción. Esperaremos para responder a la solicitud de sondeo hasta tener un mensaje con el cual responder.

Envíeme sus mensajes

Ahora solo debemos manejar los mensajes enviados al servidor y responderles a los clientes que esperan recibirlos. Añada el siguiente código pp.js para manejar el punto final /msg.

 // Msg endpoint app.post('/msg', function(req, res) { message = req.body; var msg = JSON.stringify(message); while(clients.length > 0) { var client = clients.pop(); client.end(msg); } res.end(); });

Nuestro punto final /msg recibe el mensaje contenido dentro del cuerpo de POST y luego va a través de todas las solicitudes de sondeo de la matriz del cliente, respondiéndoles con los mensajes del cliente. No hay magia aquí.

Prueba del código del lado del servidor

Ahora tenemos un código del lado del cliente y del servidor en funcionamiento, así que probémoslo. De nuevo en su terminal, ejecute node app.js para iniciar nuestro servidor y abra http://localhost:3000 en su navegador favorito. Realmente necesitamos dos navegadores para probar nuestra aplicación, por lo tanto, abra su segundo navegador favorito también y vaya a la misma dirección URL. Ingrese un nombre de usuario diferente en cada ventana del navegador y comience a escribir mensajes. Debería ver los mensajes que aparecen en ambas ventanas del navegador. ¡Bien hecho! Tenemos una aplicación de conversación que funciona.

Image shows chat app

Traslado a la nube

Nuestra aplicación parece funcionar bien de manera local, por lo tanto, implementémosla en la nube. Podemos hacer esto fácilmente utilizando IBM Cloud. Pero primero, debemos realizar un pequeño cambio en nuestro código del lado del servidor.

Uso del puerto correcto

Por ahora, iniciamos nuestro servidor Express en el puerto 3000. Es una codificación fija en app.js: app.set('port', 3000);.

Esto está bien cuando se ejecuta en forma local, pero cuando se ejecuta la aplicación en IBM Cloud, debemos usar el puerto abierto por la plataforma IBM Cloud, que posiblemente no sea 3000. IBM Cloud permite que la aplicación sepa qué puerto utilizar al establecer el puerto en una variable del entorno VCAP_APP_PORT. Sin embargo, hay una biblioteca Node que podemos usar y que nos dará el puerto: cf-env. Añadamos esta biblioteca a nuestro archivo package.json para poder usarla dentro de nuestro código del servidor.

Abra package.json, y en el objeto de las dependencias, añada una propiedad para cf-env.

 "dependencies": { "express": "3.4.8", "cf‑env": "∗" }

Ahora, abra app.js y después de las otras llamadas require , añada una require para cf-env y su archivo package.json.

 var cfEnv = require("cf‑env"); var pkg = require("./package.json");

Solo debemos crear una instancia de biblioteca cf-env pasando un nombre a su método getCode , con el uso del nombre en su archivo package.json.

 var cfCore = cfEnv.getCore({name: pkg.name});

Por último, debemos cambiar la línea de código que establece el puerto que está usando Express. Busque la línea de código en app.js como esta: app.set('port', 3000); y cámbiela para que sea app.set('port', cfCore.port || 3000);.

Tenga en cuenta que este código nos permite ejecutar la aplicación en forma local así como en la nube. Si cfCore.port no está definido, solo usaremos 3000 como lo hicimos anteriormente.

Inserción del código en IBM Cloud

Para utilizar IBM Cloud, debe tener una cuenta. Vaya a IBM Cloud para registrarse. Ahora que tiene una cuenta, podemos instalar la Interfaz de línea de comando Cloud Foundry (CLI), que usaremos para implementar nuestra aplicación. Para instalar la CLI, siga las instrucciones de la documentación de IBM Cloud.

¿Listo? Ahora viene lo divertido.

Primero, debemos señalar nuestra CLI en IBM Cloud e iniciar sesión. Ejecute el siguiente comando en su ventana terminal.

 cf login ‑a https://api.bluemix.net

Cuando se le pida ingresar su nombre de usuario y contraseña, ingrese el nombre de usuario y la contraseña de IBM. Ahora que se ha autenticado correctamente, insertaremos nuestra aplicación en la nube. Asegúrese de estar dentro de la raíz de la carpeta bluechatter que contiene todo el código de aplicación y ejecute el siguiente comando en su ventana de terminal, pero sustituya la porción de bluechatter del comando con su propio nombre de aplicación, como my-bluechatter.

 cf push bluechatter ‑m 128M

Consejo: si nota un error cuando inserta su aplicación que dice «IBM Cloud could not create a route for your application», el nombre que ha elegido ya se encuentra en uso. Elija un nombre diferente y ejecute el comando nuevamente.

Si la inserción se hizo correctamente, la CLI imprimirá la dirección URL en la que su aplicación se encuentra en ejecución. Abra este URL en sus dos navegadores favoritos y asegúrese de que la aplicación funcione como lo espera.

 App started Showing health and status for app bluechatter in org rjbaxter@us.ibm.com / space dev as rjbaxter@us.ibm.com... OK requested state: started instances: 5/5 usage: 128M x 5 instances urls: bluechatter.mybluemix.net

Opcional: Exclusión de archivos innecesarios de la implementación

Ha notado que cuando ejecuta su comando cf push la carga era bastante grande (4,2 MB en mi caso). Si bien 4,2 MB no es demasiado, es grande en comparación con la cantidad de código que hemos escrito. Para comprender por qué cargamos 4,2 MB de archivos, debemos comprender cómo funciona cf push . El comando cf push implementará cada archivo dentro del directorio en el que se ejecuta. Si recuerda cuando ejecutamos nuestra aplicación primero en forma local, ejecutamos npm install para instalar todas las dependencias necesarias para ejecutar nuestra aplicación. Las dependencias se instalaron en una carpeta denominada node_modules dentro la carpeta bluechatter. Dado que cf push implementa todo, también implementa las dependencias. Quizás usted diga, «Pero las necesitamos, ¿cierto?» Sí, las necesitamos, pero IBM Cloud se encargará de instalarlas por nosotros ya que ejecutará npm install como parte de la implementación.

La solución es simple: solo debemos crear un archivo en nuestro directorio bluechatter para decirle al comando cf push qué archivos y directorio no debe cargar. Cree un archivo denominado .cfignore en el directorio bluechatter y ábralo en su editor de texto favorito. En el archivo .cfignore, añada la siguiente línea y guarde el archivo.

 node_modules

Aquí le decimos al comando cf push que ignore todo lo que hay en el directorio node_modules. En otras palabras, no insertamos todas nuestras dependencias. Ahora ejecute el siguiente código.

 cf push bluechatter ‑m 128M

Debería notar una diferencia significativa en el tamaño del contenido implementado. En mi caso, el comando cf push solo cargó 11,4 K, una implementación mucho más pequeña — y lo que es más importante, mucho más rápida — .

Advertencia: ¡Posible problema!

Si intenta ejecutar node app.js de manera local ahora, es posible que le aparezca el error a continuación.

 $ node app.js module.js:340 throw err; ^ Error: Cannot find module 'cf‑env' at Function.Module._resolveFilename (module.js:338:15) at Function.Module._load (module.js:280:25) at Module.require (module.js:364:17) at require (module.js:380:17) at Object. (/Users/ryanjbaxter/temp/bluechatter/app.js:5:13) at Module._compile (module.js:456:26) at Object.Module._extensions..js (module.js:474:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12)

Esto es porque añadimos una dependencia a nuestro archivo package.json, pero aún no hemos instalado esta dependencia. Para solucionar esto, solo ejecute npm install desde dentro del directorio bluechatter, y luego ejecute node app.js. Deberá ejecutar npm install cada vez que haga un cambio en la sección de dependencias de package.json.

Falla de la escalación

Una ventaja de usar un PaaS como IBM Cloud es la habilidad de escalar fácilmente su aplicación de manera horizontal. ¿Qué queremos decir con escalar de manera horizontal? En esencia, esto significa crear múltiples servidores que ejecutan su aplicación y todos manejan solicitudes de usuarios entrantes. Puede leer más en Wikipedia.

Porque estamos seguros de que nuestra aplicación BlueChatter tendrá éxito entre los niños, asegurémonos de que nuestra aplicación se pueda escalar en la nube. Podemos usar la CLI confiable para escalar la aplicación desde nuestra ventana terminal; solo ejecute el comando para escalar la aplicación BlueChatter hasta cinco instancias.

 cf scale bluechatter ‑i 5

En cuestión de segundos, el comando debe regresar diciendo que todo salió bien. Verifiquemos el estado de nuestras instancias para asegurarnos de que se están ejecutando. Ejecute el siguiente comando en su ventana terminal.

 cf app bluechatter

Esto debe dar como resultado algo similar a lo siguiente.

 requested state: started instances: 5/5 usage: 128M x 5 instances urls: bluechatter.mybluemix.net state since cpu memory disk #0 running 2014‑05‑05 11:58:05 AM 0.0% 55.1M of 128M 25.5M of 1G #1 running 2014‑05‑05 11:58:33 AM 0.0% 55M of 128M 25.5M of 1G #2 running 2014‑05‑05 11:58:32 AM 0.0% 54.9M of 128M 25.5M of 1G #3 running 2014‑05‑05 11:58:33 AM 0.0% 54.8M of 128M 25.5M of 1G #4 running 2014‑05‑05 11:58:32 AM 0.0% 55.9M of 128M 25.5M of 1G

Si alguna de sus instancias aún dice que se está iniciando en la columna de estado, espere un momento y ejecute cf app bluechatter nuevamente.

Fácil, ¿verdad?

Identificación

Cada instancia de la aplicación que está ejecutando en IBM Cloud tiene una ID de instancia única.Por su propio bien, sería bueno saber a qué instancia nos estamos conectando al mostrar esa información en la IU de la aplicación BlueChatter. Añadamos algunos códigos HTML en index.html y creemos una API de REST que devolverá la ID de la instancia para poder mostrarle esa información al usuario.

index.html

Abra el archivo index.html en la carpeta public y busque el archivo div con el pie de página de clase. Añada el siguiente HTML al div.

 Instance ID: 

Aquí es donde pondremos la ID de la instancia de la IU.

client.js

Abra el archivo client.js en la carpeta public/javascripts. Dentro de la función document ready callback, añada el siguiente fragmento de JavaScript:

 $.getJSON('/instanceId', function(response, statusText, jqXHR) { if(jqXHR.status == 200) { $('#instance‑id').show(); $('#instance‑id‑value').html(response.id); } });

Similar a cómo funciona la función de sondeo, usamos jQuery para hacer una solicitud de Ajax a un punto final REST denominado /instanceId. Esto devolverá un objeto JSON simple con la ID de la instancia en él.

app.js

El paso final es añadir el código del servidor para nuestro nuevo punto final de REST. Abra app.js y añada el siguiente código al archivo.

 var instanceId = cfCore.app && cfCore.app != null ? cfCore.app.instance_id : undefined; app.get('/instanceId', function(req, res) { if(!instanceId) { res.writeHeader(204); res.end(); } else { res.end(JSON.stringify({ id : instanceId })); } });

Tenga en cuenta que utilizamos nuevamente la biblioteca cf-env para obtener la ID de la instancia. Somos muy cautos al realizar esto ya que podríamos estar ejecutando también la aplicación de manera local, por lo que verificamos para asegurarnos que se definan las propiedades adecuadas. Nuestro punto final REST retornará un 204 (sin contenido) si ejecutamos en forma local y no tenemos una ID de instancia. Si tenemos una ID de instancia, devuelve un objeto JSON simple que contiene la ID.

Prueba en IBM Cloud

Implementemos nuestro nuevo código y probemos la nueva funcionalidad.Desde dentro del directorio bluechatter, ejecute nuevamente:

 cf push bluechatter ‑m 128M

Después de implementar el nuevo código, abra su navegador favorito y vaya al URL de su aplicación. Ahora debería notar la ID de la instancia impresa en el pie de página.

Image shows instance ID

Abra su segundo navegador favorito y vaya nuevamente hasta el URL de su aplicación. Intente conectar una instancia diferente de su aplicación. (En otras palabras, debería haber diferentes ID de instancias en dos ventanas del navegador). Si termina conectándose a la misma instancia, simplemente actualice el navegador hasta ver una ID diferente en el navegador.

Image shows different browser instance ID

Ingrese dos nombres de usuario diferentes, como antes, e intente conversar. Debería notar un problema, nuestra aplicación ya no está funcionando. ¿Por qué?

Vuelva a la mesa de dibujo

¿Cuál es el problema? ¿Por qué nuestra aplicación falla cuando la escalamos? La clave se encuentra en los detalles sobre cómo funciona la escalación. Cuando se escala una aplicación en IBM Cloud, IBM Cloud creará múltiples instancias de su aplicación. Piense en cada instancia como su propio servidor independiente en su propia máquina independiente. Ninguna de las instancias comparte memoria ni siquiera el mismo sistema de archivos. Ahora, volvamos a ver cómo implementamos nuestro sondeo largo. ¿Cómo almacenamos las respuestas del punto final /poll? ¿No lo recuerda? Los almacenamos en una matriz de la memoria. Si cada instancia tiene su propia memoria, eso significa que cada instancia tiene su propia lista de clientes sondeando el servidor. Ninguno de sus servidores sabe acerca de los clientes de los demás.

No se altere. Hay una solución. En realidad, probablemente existen muchas soluciones para este problema, pero explicaré solo una. Podemos notificar las demás instancias del mensaje que se envió desde el cliente para que los servidores puedan a su vez notificar a sus clientes. Algo como una arquitectura pub-sub funcionaría. Afortunadamente, hay un servicio en el catálogo de IBM Cloud que nos proporcionará la funcionalidad pub-sub que necesitamos.

Redis al rescate

Redis es una tienda de valor clave extremadamente rápida. Bien, ese es su uso principal; puede hacer muchas otras cosas, incluido implementar un sistema pub-sub . En el catálogo de IBM Cloud, hay un servicio Redis, proporcionado por Redis Cloud, que debemos ser capaces de usar con nuestra aplicación. Además, hay una biblioteca Redis para Node.js, que se puede usar para comunicarse con nuestro servicio Redis Cloud.

¿Me da un servicio Redis, por favor?

Creemos una instancia de Redis Cloud que podamos usar para nuestra aplicación BlueChatter. IBM Cloud nos puede ayudar ahí. Solo necesitamos conocer algunos detalles adicionales sobre el servicio Redis Cloud. Podemos encontrarlos en nuestra herramienta CLI confiable. Ejecute el siguiente comando en su ventana terminal.

 cf marketplace

Este comando nos brinda cierta información — nombre del servicio, plan y descripción, información sobre cada servicio del catálogo de IBM Cloud, etc. Debería ver uno denominado rediscloud con la siguiente información.

 rediscloud 25mb Enterprise‑Class Redis for Developers

Esto es todo lo que necesitamos saber para crear una instancia de Redis desde Redis Cloud para nuestra aplicación BlueChatter.

Ejecute el siguiente comando para obtener una instancia de Redis.

 cf create‑service rediscloud 25mb redis‑chatter

Si ese comando se llevó a cabo con éxito, tiene una instancia de Redis lista para usar. Eso fue más fácil que implementar la aplicación. Puede ver que el comando create-service toma tres parámetros: el nombre del servicio, el plan, y un nombre para la instancia específica del servicio Redis Cloud que elija. Puede ser lo que usted desee; en este caso, elijo redis-chatter. Puede elegir el nombre que desee, pero anótelo ya que lo necesitará más tarde.

require('redis');

Ahora es momento de usar Redis en nuestro código de aplicación. Abra package.json dentro del directorio bluechatter. Debemos añadir la biblioteca Redis a nuestras dependencias. Sustituya la propiedad de las dependencias con el fragmento de código a continuación y guarde el archivo package.json.

 "dependencies": { "express": "3.4.8", "cf‑env": "∗", "redis": "∗" }

Ahora, abra app.js en el directorio bluechatter. Debemos solicitar la biblioteca Redis para poder usarla en nuestro código de servidor. Añada la siguiente línea en el extremo de los demás enunciados de solicitud en app.js.

 var redis = require('redis');

Detalles del servicio Redis

Antes de continuar, necesitamos detalles como el host, el puerto y la contraseña para conectarnos al servidor Redis que nos dio Redis Cloud. La instancia de Redis que creamos en la sección anterior nos brinda detalles como estos. Se almacenan dentro de una variable de entorno denominado VCAP_SERVICES como un objeto JSON. Un enfoque sería acceder a esa variable de entorno, analizar el JSON y extraerlo. Sin embargo, podemos usar la biblioteca cf-env para que haga todo el trabajo duro por nosotros. Añada las siguientes dos líneas de código a app.js.

 var redisService = cfEnv.getService('redis‑chatter'); var credentials = !redisService || redisService == null ? {"host":"127.0.0.1", "port":6379} : redisService.credentials;

Como se ve, estamos usando la API getService de la biblioteca cf-env para acceder a los detalles del servicio. Tenga en cuenta que pasamos el nombre del servicio (redis-chatter), por lo tanto, la biblioteca puede identificar la instancia específica de servicio del cual deseamos detalles. Dentro del objeto que nos regresó, se encuentra una propiedad de credenciales, que contiene el host, el puerto y la contraseña necesarios para conectarse al servicio Redis. Nuevamente, somos muy cuidadosos al obtener esa propiedad ya que si estamos ejecutando la aplicación de manera local no habrá variable de entorno VCAP_SERVICES , por lo que nuestra variable redisService será indefinida. Si es indefinida, suponemos que el usuario tiene un servidor Redis en ejecución localmente en el puerto predeterminado (6379). Para ejecutar la aplicación de manera local, puede descargar e instalar un servidor local Redis desde el sitio web Redis.

Creación de clientes Redis

En este punto, estamos listos para crear clientes que podamos usar para hablar al servidor Redis. Necesitamos dos clientes — uno para manejar las publicaciones de nuestros mensajes de conversación y el otro para escuchar los nuevos mensajes de conversación. Añada el siguiente fragmento del código en app.js.

 var subscriber = redis.createClient(credentials.port, credentials.hostname); subscriber.on("error", function(err) { console.error('There was an error with the redis client ' + err); }); var publisher = redis.createClient(credentials.port, credentials.hostname); publisher.on("error", function(err) { console.error('There was an error with the redis client ' + err); }); if (credentials.password != '') { subscriber.auth(credentials.password); publisher.auth(credentials.password); }

Como puede ver, estamos creando dos clientes Redis con el host y el puerto que obtuvimos utilizando la biblioteca cf-env y los asignamos a los nombres de variables suscriptor y publicador. Además, si tenemos una contraseña (nuevamente recordando que podemos no tener una si estamos ejecutando de manera local), autenticamos ambos clientes.

Notificación si hay alguien conversando

Usemos nuestro cliente suscriptor y escuchemos si hay alguna conversación publicada por otras instancias del servidor. Añada el siguiente código en cierto momento después de haber creado el cliente suscriptor en app.js.

 subscriber.on('message', function(channel, msg) { if(channel === 'chatter') { while(clients.length > 0) { var client = clients.pop(); client.end(msg); } } }); subscriber.subscribe('chatter');

Este código añade un receptor de eventos al cliente suscriptor para escuchar los eventos de mensajes. Se activará un evento de mensaje cuando se publique algo en pub-sub para la instancia redis-chatter. La función que maneja el evento toma un canal y el mensaje a publicar. El canal es como una ID para que los suscriptores escuchen. Identifica los tipos de mensaje que se publican. Los publicadores especificarán la ID del canal cuando publiquen un mensaje. Nuestra función de manejador de eventos solo maneja mensajes del canal «conversador», por lo tanto, lo primero que debemos hacer es asegurarnos de que las variables del canal igualen a los conversadores. De ser así, hacemos un bucle a través de la matriz de nuestro cliente (recuerde que estas son nuestras solicitudes de sondeo largo) y respondemos las solicitudes con el mensaje publicado en ellas. Por último, le decimos a nuestro cliente suscriptor que deseamos suscribirnos al canal de conversación. (Es posible que se pregunte, «si solo suscribimos y publicamos eventos en el canal de conversaciones, ¿por qué debemos verificar la variable del canal en nuestro manejador de eventos?». Técnicamente, no tenemos que hacerlo, pero somos buenos programadores. Debemos anticipar que en el futuro podríamos publicar eventos también en otros canales).

Difusión de conversaciones

En este punto, no se llamará nunca a nuestro suscriptor ya que nadie trasmite ninguna conversación. El lugar perfecto para difundir conversaciones es donde los clientes nos envían mensajes: el punto final /msg de REST. Sustituya la implementación del punto final actual /msg dentro de bluechatter/app.js con esta nueva implementación.

 app.post('/msg', function(req, res) { message = req.body; publisher.publish("chatter", JSON.stringify(message)); res.end(); });

En nuestra nueva implementación, ya no realizamos bucles a través de la matriz de los clientes ni enviamos el mensaje de conversación (esto ahora se hace en el manejador de eventos del suscriptor Redis). Simplemente obtenemos el mensaje de la conversación del cuerpo de POST y lo publicamos en el canal de conversaciones para nuestros otros servidores y para nosotros mismos, y se lo enviamos a los clientes.

Conversaciones escalables

En este punto, nuestra aplicación debe manejar ser escalada con facilidad, pero nuestra implementación se hizo más compleja al introducir el requisito de servicio Redis. (Nuestra aplicación no funcionará en IBM Cloud sin el servicio Redis vinculado a ella). Antes de implementar nuestra aplicación, usemos un manifiesto para simplificar nuestro proceso de implementación.

Nada como simplificar su inserción

Los manifiestos pueden tomar muchas implementaciones verbosas y propensas a errores de IBM Cloud y hacerlas simples y uniformes. En el directorio bluechatter, cree un nuevo archivo denominado manifest.yml y añada el siguiente contenido en él.

 applications: ‑ name: bluechatter memory: 128M command: node app.js services: ‑ redis‑chatter

Puede ver que la mayoría de los parámetros especificados en nuestro comando cf push se trasladaron a este archivo. Además, contamos con una propiedad de servicios con el nombre de nuestra instancia del servicio de Redis. Esto le dice a IBM Cloud que vincule la instancia de servicio redis-chatter a nuestra aplicación a la hora de implementar nuestra aplicación.

Una vuelta por el manifiesto

Probemos toda esta nueva y brillante funcionalidad. Desde dentro del directorio bluchatter, ejecute cf push. Si observa «App started», el comando cf push se completó con éxito y estamos listos para ver si reparamos nuestro problema. Aún debemos tener cinco instancias de su aplicación en ejecución, por lo tanto, abra sus dos navegadores favoritos y conéctelos a dos instancias diferentes. Cuando pruebe la aplicación, debería observar que todo funciona nuevamente.

Image shows testing success

Consejo: Pruebe la aplicación también en su dispositivo móvil favorito.

Image shows mobile test

Opcional: Manejo de los tiempos de espera

Debemos prestarles atención a los tiempos de espera de nuestras solicitudes de sondeo. Supongamos que el navegador le envía una solicitud al servidor, pero el servidor nunca le respondió al navegador porque no se enviaron mensajes de conversación durante un largo período de tiempo. El navegador agotará el tiempo de espera de la solicitud. Deseamos evitar que ocurra esto.

Una solución simple sería atravesar la matriz del cliente y responder con un 204 a todo lo que el servidor no haya respondido después de un minuto. Esto garantiza que el servidor nunca se cuelgue con una solicitud por más de un minuto y solucione el problema de tiempo de espera. A la vez, podría haber situaciones en las que el navegador envía una solicitud de sondeo a la que respondemos de inmediato con un 204 dado que ingresó justo antes de que finalizara el minuto. Podríamos recurrir a un algoritmo complejo para hacer esto más eficiente, pero para este ejemplo simple, no es necesario. Abra app.js y añada la llamada setInterval a continuación.

 // This interval will clean up all the clients every minute to avoid timeouts setInterval(function() { while(clients.length > 0) { var client = clients.pop(); client.writeHeader(204); client.end(); } }, 60000);

Conclusión

Finalmente, tenemos una aplicación que puede escalarse en la nube. Como puede ver, tuvimos que cambiar el diseño para asegurarnos de que se pudiera escalar, pero el código que debimos cambiar fue mínimo. Los patrones utilizados en nuestra aplicación para permitir la escalación se pueden aplicar a cualquier lenguaje de programación que desee — Java™ , Ruby, PHP, etc. La conclusión que nos llevamos de este artículo es tener en cuenta la escalación cuando escribimos un código. De ese modo, cuando implementa su aplicación en la nube y sea momento de escalar, estará seguro de que su aplicación seguirá funcionando y no se colgará.