1

Manejo de datos en tiempo real con RethinkDB y ReactJS

2017Puntos

hace 6 años

Ya habíamos hablado antes de las bondades de _RethinkDB y ReactJS _trabajando en conjunto para facilitar el manejo de datos en tiempo real. Y ya que aprendimos la teoría, ¡es hora de ponerlo en practica! En este artículo te enseñaré, paso a paso, cómo** armar el full stack de una aplicación en tiempo real.** Se dice que una base de datos es tan buena como su sistema de querys; y RethinkDB posee uno tan robusto que nos permite hacer búsquedas avanzadas y conectar el resultado en tiempo real a nuestra app. Es decir, si algún cambio en los datos modifica el resultado de la búsqueda, estos se actualizan en los clientes en tiempo real. RethinkDB nos permite realizar querys avanzadas en el servidor y los clientes se enfocan principalmente en mostrar los cambios y enviar nuevos datos. Este es un escenario ideal para el uso de ReactJS y el flujo de datos de Flux; pues podemos crear un flujo unidireccional en el que ReactJS sólo se preocupa por mostrar los datos actuales, enviar los datos nuevos y realizar las actualizaciones necesarias en el DOM cuando se realicen cambios en el Modelo. Con esta arquitectura MVC, el backend se encarga del modelo y los controladores; y el front-end se encarga casi exclusivamente de la visualización. Esto se ve reflejado en un excelente performance que será consistente incluso en dispositivos de gama baja. Desde su versión 1.16, RethinkDB fue mejorada para optimizar el manejo de datos en tiempo real. Entre algunas de las mejoras, ahora puedes usar el comando .changes( ) para que la base de datos envíe una notificación a tu servidor indicando los cambios. Al mismo tiempo, este puede usar WebSockets para notificarle los cambios a los usuarios en un JSON como el siguiente: [code] { “old_val”:“old_version” , “new_val”:“new_version” } [/code] Gracias a que RethinkDB tiene muy presentes los detalles en escalabilidad y performance; te permite limitar el tiempo entre actualizaciones. Así es posible seleccionar qué tan seguido deseas que se actualicen los datos. Además, si hay muchos cambios en un mismo periodo, RethinkDB los une en un solo paquete. Este ajuste te permite afinar el performance de tus servidores y usuarios en un solo paso y de acuerdo a tus necesidades. Incluso puedes usar la propiedad squash para limitar las actualizaciones a ciclos de 150 milisegundos. Esto se vería así: [code] .changes(squash=0.15) [/code] Si estás buscando una base de datos para un videojuego, RethinkDB ofrece soluciones muy interesantes. Por ejemplo, si quieres integrar un tablero en tiempo real que muestre los 3 primeros lugares pero que no impacte el performance; puedes enviar los cambios cada tres segundos con un comando así: [code] order_by(index=r.desc(‘points’)).changes({squash:3}).limit(3) [/code] Es posible que deseemos calibrar el periodo de squash a no menos que el promedio de latencia entre el servidor y nuestros usuarios. Y RethinkDB también puede usarse para crear una base de datos distribuida en hasta 16 servidores, si nuestra aplicación lo requiere.

En el backend

Lo primero que necesitamos es nuestro servidor de RethinkDB. Te recomiendo usar Socket.io como tu backend_._ Si no estás seguro de cómo hacerlo, tenemos una serie de tutoriales que te enseñarán a montar tu API, a usar _WebSockets _y a montar un servidor de archivos estáticos con Express. Antes de escribir el código de nuestro servidor, debemos crear un archivo para guardar la configuración general. Le llamaremos config.json y lo vamos a colocar en el directorio raíz. Es importante definir la IP de nuestro servidor de RethinkDB en el campo host. Si estás usando Linux, puedes revisarla con el comando _ip addr show. _ También es importante que le asignes un nombre a la base de datos y la tabla que vas a usar. También es recomendable agregar un tableIndex para indexar tu base de dato. En nuestro ejemplo usaremos la fecha de creación de cada objeto. config.json [code] { “rethinkdb” : {     “host” : “127.0.0.1”,     “port” : 28015,     “authKey” : “”,     “db” : “test”     “table” : “list”,     “tableIndex” : “createdAt”   },   “express” : {     “port” : 80   } } [/code] En Node.js empecemos por invocar las dependencias e importar las configuraciones que usaremos en este proyecto:

<pre>server.js</pre>

[javascript] // Serve-Static  var serveStatic = require(‘serve-static’); // Body-Parser var bodyParser = require(‘body-parser’); // Multer var multer = require(‘multer’) // RethinkDB var r = require(‘rethinkdb’); // Express var express = require(‘express’); var app = express(); // Socket Server var server = require(‘http’).Server(app); // Socket.IO var io = require(‘socket.io’)(server); // Configuration File var config = require(’./config.json’) [/javascript] Ahora creemos un método para iniciar nuestro servidor de archivos estáticos:

<pre>server.js</pre>

[javascript] var startServer = function() {   server.listen(config.express.port) } [/javascript] Ahora creemos uno para iniciar RethinkDB: [javascript] var initializeRTDB = function(conn) { r.table(config.rethinkdb.table).indexWait(‘createdAt’).run(conn) .then(function(result) { startServer(); conn.close() .then(function(){ console.log(“RethinkDB connection closed”) }) }) .error(function(error){ console.log(“The table doesn’t exist.”); console.log("Initializing table: "+config.rethinkdb.table); r.tableCreate(config.rethinkdb.table).run(conn) .finally(function(){ console.log("Initializing index: "+config.rethinkdb.tableIndex); r.table(config.rethinkdb.table).indexCreate(config.rethinkdb.tableIndex).run(conn) .finally(function(){ console.log(“DB Initialized”); conn.close() .then(function(){ console.log(“RethinkDB connection closed”) }); startServer(); }) }) }) } [/javascript] Y ahora crearemos un método para conectarnos a nuestra base de datos:server.js [javascript] r.connect(config.rethinkdb) .then(function(conn) { r.dbList().run(conn) .then(function(dbList){ if (dbList.indexOf(config.rethinkdb.db) > -1){ initializeRTDB(conn); } else { console.log(“The DB doesn’t exist.”); console.log("Initializing DB "+config.rethinkdb.db); r.dbCreate(config.rethinkdb.db).run(conn) .then(initializeRTDB(conn)) } }) }) .error(function(error){ console.log("Could not open a connection to initialize the database "+config.rethinkdb.db); console.log(error.message); process.exit(1); }) [/javascript] Es momento de definir los métodos que vamos a llamar con nuestros end-points. Empecemos por uno para manejar los errores tipo 500: [javascript] var handleError = function(res) { return function(error){ res.send(500,{error: error.message}); conn.close(); } } [/javascript] Pasemos a algo más interesante. En esta ocasión, crearemos un método que baje todos los elementos de nuestra tabla y los ordene por la fecha en que fueron creados ¿Recuerdas el tableIndex en nuestro archivo config.json?

<pre>server.js</pre>

[javascript] var list = function(request, res, next) { r.connect(config.rethinkdb) .then(function(conn) { r.table(config.rethinkdb.table).orderBy({index: config.rethinkdb.tableIndex}).run(conn) .then( function(data) { if (data._responses[0]){ var query = data._responses[0].r; res.send(query); } conn.close() }) .error(handleError(res)) }) } [/javascript] Ahora que tenemos cómo mostrar el contenido de una tabla, necesitamos un método para insertar contenido en ella:

<pre>server.js</pre>

[javascript] var add = function(request, res, next) { var element = request.body; element.createdAt = r.now(); r.connect(config.rethinkdb) .then(function(conn) { r.table(config.rethinkdb.table).insert(element).run(conn) .then( function() { conn.close() }) .error(handleError(res)) }) } [/javascript] Hasta este punto tenemos lo básico. Ahora agreguemos un método para vaciar toda la tabla y así reiniciarla de una manera sencilla.

<pre>server.js</pre>

[javascript] var empty = function (request, res, next) { r.connect(config.rethinkdb) .then(function(conn) { r.table(config.rethinkdb.table).delete({returnChanges: true}).run(conn) .then( function(changes) { console.log(changes); conn.close() }) .error(handleError(res)) }) } [/javascript] Ya casi tenemos listo nuestro backend. Es momento de poner en uso una de las características más poderosas de RethinkDB:emitir notificaciones de los cambios en tiempo real a nuestros clientes. Para lograrlo usaremos Web Sockets. Con React.js lo mas efectivo es notificar al cliente que hubo un cambio para que este actualice la lista. Para hacerlo, vuelve a llamar a la API y reinicia el proceso de carga y parseo de datos. En otra situación este proceso no sería eficiente, pero gracias a la arquitectura Flux es la mejor opción. La arquitectura **_Flux _nos permite ahorrar código **y evitar errores gracias al VDOM de React.js. Es decir,sólo se actualizarán las partes del DOM que tengan diferencias con la copia actualizada en el VDOM. En este ejemplo usaremos Socket.io para notificar a nuestro cliente cuando ocurre un cambio en la tabla. Pon especial atención en el objeto **squash:1, **pues indica el periodo mínimo entre actualizaciones. En este caso es de 1 segundo. Este código también envía una notificación para verificar que la conexión fue establecida y lo transmite en checkConnection.

<pre>server.js</pre>

[javascript] io.on(‘connection’, function (socket) { this.socket = socket; var webSocket = this.socket; webSocket.emit(‘checkConnection’, { result: ‘Web Socket OK’ }); // Listen to test conection r.connect(config.rethinkdb.table) .then(function(conn) { r.table(config.rethinkdb.table).changes({squash:1}).run(conn, function(error,cursor){ cursor.on(“data”,function(change){ webSocket.emit(‘change’,{change:change}); }); cursor.on(“error”,function(error){ webSocket.emit(‘error’,{error:error}); cursor.close(); }) }) }) }) [/javascript] Ahora sólo queda iniciar el servidor de datos estáticos:

<pre>server.js</pre>

[javascript] // Data parsing app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(multer()); // Define main routes app.route(’/api/list’).get(list); app.route(’/api/add’).post(add); app.route(’/api/empty’).post(empty); // Static files server app.use(serveStatic(’./public’)); [/javascript] Te invito a que veas el código completo en el repositorio de GitHub. Incluye logs adicionales que te brindan mas información en la consola de tu servidor.

En el Front-end

Ahora que ya tenemos nuestro backend, es hora de armar nuestro frontend con ReactJS. Es importante siempre empezar planeando nuestros componentes y el flujo de datos. A la larga, esto nos ahorrará mucho tiempo y hará el proceso mas fácil. Veamos nuestro ejemplo y pensemos cómo dividirlo de forma eficiente: Componentes Empecemos por definir el componente más sencillo: SimpleListRow. Este se encarga de mostrar una fila de la lista por cada elemento.

reactApp.jsx

[javascript] var SimpleListRow = React.createClass({ render: function() { var rows = this.props.simpleList; return (

  1. {element.row}

) } }) [/javascript] Subimos un nivel en la jerarquía de nuestra aplicación y tenemos a **SimpleList;**que se encarga de recibir los elementos de la lista y los datos para filtrarlos y pasarlos a SimpleListRow en los props simpleList y userInput.

reactApp.jsx

[javascript] var SimpleList = React.createClass({ render: function() { return ( Pasos para dominar un nuevo lenguaje de programación: ) } })[/javascript] Es momento de regresar a y agregar un poco de código adicional. Este comparará el contenido de los props para decidir si esa línea debe aparecer o no.

reactApp.jsx

[javascript] var SimpleListRow = React.createClass({ render: function() { var rows = this.props.simpleList; var userInput = this.props.userInput; return (

  1. {element.row}

) } }) [/javascript] En el siguiente paso vamos a armar SimpleFilterableList, que será nuestro componente principal. Este se encargará de recibir los datos y pasarlos a los niveles inferiores. Aquí también vamos a colocar las entradas de texto. Empecemos creando un método para iniciar un Web Socket.

reactApp.jsx

[javascript] var socket = null; var startSocket = function() { socket = io(window.location.hostname) } [/javascript] **SimpleFilterableList **es el objeto más complejo, pues contiene la lógica de conexión al servidor. Empecemos a ver sus partes una por una; la primera es componentDidMount. Este es llamado una vez que el componente ya cargó. Pero primero mandamos llamar a startSocket() para iniciar una nueva conexión por la que vamos a recibir las notificaciones de los cambios en el _modelo. D_espués creamos un método para bajar la lista por AJAX a través de nuestra API. reactApp.jsx [javascript] var SimpleFilterableList = React.createClass({ componentDidMount: function() { startSocket(); var instance = this; var downloadData = function(){ $.ajax({ url: ‘/api/list’, dataType: ‘json’, success: function(data) { instance.setState({simpleList: data}); }.bind(instance), error: function(xhr, status, err) { console.log(‘Data error:’); console.error(instance.props.url, status, err.toString()) }.bind(instance) }); }; downloadData(); socket.on(‘change’, function (data) { downloadData(); }) } }); [/javascript] Ahora vamos a asignarle los valores por defecto a nuestros datos con getInitialState.

reactApp.jsx

[javascript] getInitialState : function() { return { userInput : “”, simpleList : [ { row : ‘cargando …’ } ] }; } })[/javascript] Lo siguiente que necesitamos es un método al que llamaremos updateUserInput. Este mantendrá actualizado su stateuserInput que usará SimpleListRow como filtro.

reactApp.jsx

[javascript] updateUserInput : function(input){ this.setState({ userInput : input.target.value }) } [/javascript] Continuaremos con el método sendNewElement, que se encargará de enviar los nuevos elementos a RethinkDB usando nuestra API.

reactApp.jsx

[javascript] sendNewElement: function(key){ if (key.key == “Enter”){ $.ajax({ url : “/api/add”, type : “post”, data : { “row” : document.getElementById(‘newElement’).value } }); document.getElementById(‘newElement’).value = ‘’; document.getElementById(“userInput”).focus(); } } [/javascript] Y finalmente tenemos render, que se encargará de crear la salida de HTML de nuestra aplicación. Ahí debemos colocar el objeto SimpleList y las entradas de texto. A cada una debemos asignarle id y un callBack con onChange.reactApp.jsx [javascript] render: function(){ return (

<div><input id=“userInput” type=“text” placeholder=“Filtrar…” onchange="{this.updateUserInput}"><simplelist simplelist="{this.state.simpleList}" userinput="{this.state.userInput}/"><input id=“newElement” type=“text” placeholder="+" onkeypress="{this.sendNewElement}" onclick="{this.favToInput}" classname=“fav”></simplelist></div>

) } [/javascript] Ahora lo único que queda es colocar todos estos elementos juntos en SimpleFilterableList y compilar nuestro JSX. Recuerda que esto se hace desde la carpeta raíz de tu archivo con el comando jsx -x jsx -w jsx/ js/. No olvides que debes levantar tu servidor de RethinkDB antes de correr tu aplicación en **NodeJS. **Si eres usuario de Windows, este tutorial te ayudará a lograrlo. ¡Felicidades! ya tienes todo lo necesario para armar el full stack de una aplicación en tiempo real que puede escalar de forma profesional de acuerdo a las necesidades de tus proyectos. Si quieres descubrir el verdadero potencial de React.js regístrate al Curso de ReactJS  en Platzi y empieza a aprender desde ahora para convertirte en un profesional de una de las principales tecnologías del futuro.

Cesar
Cesar
reicek

2017Puntos

hace 6 años

Todas sus entradas
Escribe tu comentario
+ 2
0
4192Puntos

Deberían colocar mas bonito este post, con toda ese código no se entiende mucho