13 mayo, 2009

BrowserCouch

En Day Dreaming about Web Storage, Mark Finkle trata la naturaleza de la futura API de almacenamiento de datos en el navegador. Sea finalmente SQL, como abogan algunos, o un lenguaje más coherente con la idiosincrasia de Javascript y JSON, como abogan otros, la esperada API deberá ser más bien simple para que permita un ecosistema de librerías que, al igual que los actuales frameworks como MooTools o jQuery, ofrezca diversas opciones que se adapten a todas las necesidades y gustos.

Aunque SQL está mucho más extendido, proyectos como TrimQuery demuestran que no es imprescindible que el navegador incorpore un parseador para un lenguaje con tantas versiones incompatibles entre sí como fabricantes existen.

BrowserCouch es un intento de implementar la tecnología MapReduce en el navegador. Está escrito enteramente con JavaScript con la intención de que funcione con todos los navegadores, aprovechando de forma transparente otras capacidades disponibles.

No es casualidad que esta librería trate de imitar la funcionalidad de CouchDB en el cliente, e incluso podría soportar su integración en el futuro.

Esta librería es una respuesta al artículo de Vladimir Vukicevic HTML5 Web Storage and SQL. Una API al estilo de CouchDB parece una buena solución para el almacenamiento persistente en la Web, ya que mucha de su semántica es delegada al lenguaje JavaScript, lo que lo convierte en potencialmente sencillo de estandarizar. Además, el paradigma MapReduce también saca ventaja natural de los múltiples núcleos de los microprocesadores- algo cada vez más frecuente hoy día.

Tutorial


Ésta es una breve introducción al uso de la API de BrowserCouch y del mecanismo MapReduce. Hay que remarcar que no se trata de software "maduro" ya que aún faltan muchas características de CouchDB por portar y la API no es estable.

Respecto a los ejemplos de código de este tutorial: se ejecutan en el mismo navegador y los resultados se muestran en algunos casos en esta misma página. Esto permite asegurar que el software funciona como se pretende y permite un aprendizaje interactivamente. También conviene avisar que existe la posibilidad de que alguna de los ejemplos falle.

Primeros pasos


Supongamos que queremos añadir soporte sin conexión a un blog. Para obtener una base de datos llamada blog-posts en BrowserCouch se puede usar la siguiente función:

BrowserCouch.get('blog-posts', 
  function onRetrieveCb(db) { 
    blogDb = db; /* Guarda la base de datos para luego. */ 
  }, 
  new FakeStorage()
);
Está claro que el primer parámetro es el nombre de la base de datos; el segundo es la función callback que recibirá la base de datos tras obtenerse.

El tercer parámetro especifica el motor que será usado para almacenar la base de datos entre sesiones de navegación. En este caso se usa FakeStorage, el cual almacena los datos en memoria de forma no persistente, lo que vale como ejemplo. Podríamos igualmente no especificar el tercer parámetro para que BrowserCouch averigüe el mejor motor según las capacidades disponibles.

Si la base de datos no existe, se creará una nueva. Poner nuevos artículos en la base de datos se consigue con el método put():

blogDb.put( [
  {id: 0, author: 'Myk', title: 'Burritos', content: 'Burritos are yum.'}, 
  {id: 1, author: 'Thunder', title: 'Bacon', content: 'I like bacon.'}, 
  {id: 2, author: 'Thunder', title: 'Beer', content: 'Beer is good too.'}], 
  function onDone() { 
    /* Código función... */ 
  }
);
Cada elemento que guardemos en la base de datos necesita un atributo identificador, pero a parte de eso, el elemento puede contener cualquier dato codificable como JSON.

Vistas

Ahora que tenemos datos, podemos jugar con la generación de vistas sobre los datos usando el mecanismo MapReduce. Por ejemplo, a continuación se define una vista definiendo sólo la fase de mapeo que organiza todos los títulos de artículos por autor:

blogDb.view({ 
  map: function(doc, emit) { 
    emit(doc.author, doc.title); 
  }, 
  finished: function(result) { 
    displayInElement(result, 'author-keyed-view'); 
  } 
});
El método view() tiene muchos argumentos opcionales, y esa es la razón por la que estamos pasando un único objeto con las claves que se corresponden con los nombres de los argumentos. El argumento map es la función usada en la fase de mapeo, y el argumento finished es la función callback que recibirá los resultados cuando termine el proceso.

El resultado obtenido en el elemento author-keyed-view será:

{"rows":[
  {"id":0,"key":"Myk","value":"Burritos"},
  {"id":1,"key":"Thunder","value":"Bacon"},
  {"id":2,"key":"Thunder","value":"Beer"}
]}

Como se puede ver, BrowserCouch esencialmente itera sobre todos los objetos de artículo, pasándole cada uno a la función map() junto con una función arbitraria llamada emit(). La función map() entonces decide si el par clave-valor debe aceptarse pasándolo a la función emit(). map() puede hacer tantas llamadas a emit() como desee: cada llamada generará una nueva fila en la vista.

En este punto quizás resulte interesante saltar a la sección de pruebas para jugar definiendo una función map() personalizada. Edita el código y al salir del campo de texto se actualizará el resultado.

La fase de reducción de una vista es totalmente opcional y un poco confusa. Intentemos añadir una función reduce() a nuestra vista para agrupar juntos los títulos de artículos con los autores:

blogDb.view({ 
  map: function(doc, emit) { 
    emit(doc.author, doc.title); 
  }, 
  reduce: function(keys, values) { 
    return values;
  }, 
  finished: function(result) { 
    authors = result; /* Guarda los resultados para más tarde. */
    displayInElement(authors, 'author-titles-view');
  } 
});
lo que generará el siguiente resultado:

{"rows":[
  {"key":"Myk","value":["Burritos"]},
  {"key":"Thunder","value":["Bacon","Beer"]}
]}
BrowserCouch tomará todas las filas generadas por map() y genera una nueva lista de filas clave-valor, donde el valor de cada fila es la lista de todos los valores que coinciden con la clave de la fila. Esto explica el significado del argumento values pasado a reduce().

El argumento keys es una lista de tuplas de dos elementos, el primera de los cuales es la clave y el segundo es el identificador del documento que emitió la clave durante la fase de mapeo.

La función reduce() es invocada por cada clave única, y su valor de retoro es el valor de su clave en la vista final.

Una vez que se tiene la vista, se puede usar el método findRow() de la vista para encontrar la primera fila cuya clave coincida con (o sea la más parecida a) la provista. Por ejemplo:

var rowIndex = authors.findRow('Thunder'); displayInElement(authors.rows[rowIndex], 'author-find-row-view');
Y el resultado es:
{"key":"Thunder","value":["Bacon","Beer"]}

Prueba tú

Si tienes los ojos cruzados, no te preocupes - a mucha gente le lleva bastante tiempo comprender cómo funciona MapReduce. Dicho eso, la forma más rápida de comprender su funcionamiento es jugar creando tu propia vista.

En la página del proyecto tienes un campo de texto para hacerlo. Pulsa la tecla tabuladora cuando hayas terminado de hacer cambios para lanzar la consulta y ver los resultados.

4 comentarios:

Nando Quintana dijo...

Excelente el tutorial, es un tema un tanto complejo pero creo que lo has dejado muy claro. Gracias Alfons! :-)

Àl dijo...

Hola Nando! Cuánto tiempo.

El artículo no es más que una traducción. Pero me interesa que esta clase de tecnologías lleguen a más gente y creo que una traducción es una buena forma, aunque para estar al día en estas cosas hay que saber mucho inglés. Yo pongo mi granito de arena y ya veremos que sale.

Josepex dijo...

Hola digitta.com, hace poco que descubri este blog y la verdad te estoy muy agradecido por lo que haces, informacion como la que brindas es muy dificil encontrar (bueno al menos para mi)...sigue para adelante...

Àl dijo...

Gracias por tus palabras. Intentaré seguir en la misma línea.

Publicar un comentario en la entrada

Últimos links en indiza.com