CouchDB

CouchDB como base de datos para AngularJS

Vamos a ampliar el ejemplo anterior hecho con AngularJS (https://mysticalpotato.wordpress.com/2014/01/31/creando-una-sencilla-aplicacion-con-angularjs-y-yeoman/) con una interesante funcionalidad que nos permitirá guardar el estado del dibujo que hayamos hecho en un momento determinado y claro está, volver a recuperarlo más adelante.

Como se vio en el post anterior, se utiliza una estructura de objetos en Java (por tanto convertible a formato JSON) para almacenar el número de veces que se había visitado cada una de las celdas del ejemplo. De este modo, si almacenamos este objeto, podremos reproducir fielmente la posición en un momento determinado.

Existen diferentes opciones para guardar los diferentes estados para nuestra aplicación. Por ejemplo podemos utilizar el localstorage de HTML5 o también podemos usar una base de datos. En este último caso lo mejor es tratar con una base de datos que pueda manejar la información en formato JSON. Existen múltiples alternativas, pero en este caso nos vamos a decantar por CouchDB.

Usar CouchDB para este ejemplo nos ofrece varias ventajas. La primera es que es una base de datos NoSQL que maneja documentos JSON de forma nativa. Otra ventaja es que para interaccionar con ella, monta un servidor RESTful a través de HTTP. Esto facilita enormemente las llamadas AJAX que tenemos que hacer en AngularJS.

Vamos pues en primer lugar a ver cómo se instala y se crea la base de datos para salvar el estado del dibujo en cada momento con CouchDB. Más adelante veremos lo fácil que es modificar utilizar AngularJS para soportar esta nueva funcionalidad.

Seguimos los siguientes pasos:

  1. Descargar la última versión de CouchDB (al momento de escribir este artículo era la 1.5.0). Accedemos a su sitio web y descargamos el fichero. Están en http://couchdb.apache.org/
  2. Instalamos CouchDB. Muy fácil, sólo hay que indicar el lugar del disco duro donde lo pondremos. A vuestro gusto. Una cosa que no he hecho es hacer que se arranque como servicio.
  3. Accedemos al directorio de instalación/bin y ejecutamos CouchDB.bat . Así se arranca el servidor, que responde en http://127.0.0.1:5984/

Si queremos podemos probar a llamarlo desde un navegador y hacer llamadas. También podemos usar cURL para invocar las acciones de la API. Tienes una información detallada en la documentación oficial de esta base de datos: http://docs.couchdb.org/en/latest/intro/api.html

Por simplificar el proceso, CouchDB incluye un gestor Web integrado en su servidor. Su acceso es muy fácil, http://127.0.0.1:5984/_utils/ . Este manager se llama “Futon”. Como dato de la versatilidad de CouchDB, esta base de datos permite almacenar documentos y servirlos como si de un servidor web se tratase. Futon es una aplicación Web que sirve la base de datos, e incluso nosotros podremos incluir nuestra aplicación (HTML+CSS+JS) y hacer que CouchDB la sirva a los navegadores. Son los llamados Design Documents (http://guide.couchdb.org/draft/design.html).

Veamos cómo crear la base de datos dentro de CouchDB.

En primer lugar creamos una base de datos para almacenar la información. Entramos en Futon (http://127.0.0.1:5984/_utils/) Pulsamos sobre “Create Database…” y le damos el nombre “dibujo”. Aquí se almacenarán los objetos Javascript que determinan el estado de cada celda del dibujo, esto es, el número de veces que se ha pasado el ratón por encima.

couch1

Si abrimos el navegador e introducimos la dirección http://127.0.0.1:5984/dibujo podremos ver información acerca de la base de datos recién creada. Después de varios usos podremos obenter algo así:

{"db_name":"dibujo","doc_count":28,"doc_del_count":0,"update_seq":30,"purge_seq":0,"compact_running":false,"disk_size":90216,"data_size":13301,"instance_start_time":"1391813178196821","disk_format_version":6,"committed_update_seq":30}

Sin entrar mucho en detalle, CouchDB almacena por cada uno de los registros un identificador (_id) y un número de revisión (_rev). El Id se asigna autmáticamente si no indicamos nada, lo mismo que el número de revisión (_rev). Sí, aquí se almacenan diferentes versiones de un mismo registro, de modo que hay un histórico de datos. Estos dos valores son básicos, y se les tienen que unir el resto de datos que vamos incluyendo. Al final podríamos obtener algo así:

{
 "_id": "9cd4cfb4841782207f7f3460f1002285",
 "_rev": "3-313a13f005e452222cbc1e4aca28121a",
 "e": {
 "a2": 2,
 "b2": 2,
 "c2": 2,
 "d6": 1,
 "c6": 1,
 "b6": 2,
 "b7": 3,
 "a7": 2,
 "c3": 1,
 "d3": 1,
 "d4": 1,
 "c4": 1,
 "c5": 1,
 "b5": 1,
 "a8": 1,
 "a9": 1,
 "c7": 1,
 "d7": 1,
 "d9": 1,
 "b9": 1
 },
 "t": 1391809957089
}

Donde:

  • “e” es el atributo que representa cada uno de las celdas sobre las que ha pasado el ratón.
  • “t” es la fecha en la que se ha insertado el registro (un simple new Date().getTime() en Javascript).

Cabe suponer por lo tanto, que usando la característica RESTful de CouchDB, podamos sacar un registro concreto de la base de datos mediante la llamda a esta URL: http://127.0.0.1:5984/dibujo/9181ffd10aa47c52e1cee65ce0006ecd . Esto es, usamos el campo _id para identificar el elemento, asique lo tenemos fácil a la hora de recuperar la información de la base de datos.

Por otra parte también parece interesante recuperar un listado de los elementos que hay en la base de datos, para poder cargar la información. En una base de datos tradicional NoSQL haríamos algo así como  SELECT * FROM dibujo. Pero esto es una base de datos NoSQL y se emplea otro paradigma: MapReduce . A grandes rasgos es una combinación de dos funciones, Map y Reduce, que se aplican de forma secuencial, una detrás de otra y que construyen a partir de un conjunto de datos un conjunto de resultados:

  • Map se encarga de recorrer todos los elementos de la base de datos y extraer como resultados los que cumplen el criterio indicado. Es decir, para un conjunto de datos almacenados en la base de datos extrae otro conjunto de resultados.
  • Reduce se aplica sobre los elementos resultados obtenidos de Map para obtener un valor reducido, como por ejemplo la suma de un determinado campo de los resultados. Gracias a la arquitectura interna de CouchDB basado en árboles-B (B-tree) se consiguen unos rendimientos espectaculares.

Vamos a crear una función Map que nos devuelva los elementos que en la base de datos usando Futon (http://127.0.0.1:5984/_utils/).

Acedemos a nuestra base de datos (dibujo) y a la derecha arriba seleccionamos en el desplegable “View” la opción “Temporary view…”. Aparecerá una tabla con dos grandes celdas: la de la izquierda para escribir la función de Map y la de la derecha para Reduce. Vamos a ir solamente con la de Map.

couch1

Si vemos la función por defecto es la siguiente:

function(doc) {
 emit(null, doc);
}

couch1

Esta función Javascript se aplica a todos y cada uno de los documentos de la base de datos. Por ello tiene un parámetro llamado “doc”, que representa cada documento. Sí, se llama una vez por cada documentos que exista. El siguiente elemento que destaca es la función “emit”, que se encarga de incluir un resultado. Este resultado se emite en forma de dupla: [key, value]. Los resultados se ordenan respecto a la key (recordad la estructura de árbol-B). Además debemos saber que esta función se aplica cuando hay un cambio en la base de datos, y no cada vez que se llama la vista. Esto es una gran ventaja de cara al rendimiento de la base de datos.

Nuestra función Map será la siguiente:

function(doc) {
if(doc.e && doc.t)
 emit(doc.t, doc.e);
}

Que no es otra cosa que emite un resultado cuando “e” (los elementos del grid y las veces que se ha pasado el ratón sobre ellos) y “t” (la fecha) no sean nulos. Además emite como resultado la dupla (t,e) , es decir, el tiempo o fecha en la que se ha creado el registro y el contenido. Pulsamos sobre el botón “Run” y vemos los resultados (es posible que no veas nada porque aún no tienes datos… en este caso crea varios documentos nuevos con New Document). Si los resultados son los esperados, salvamos pulsando el botón Save As. De nombre elegiremos “_design/dibujo” y de nombre de la vista será “todos”.

Si hemos hecho todo bien veremos cómo podemos acceder a esta vista mediante la URL: http://127.0.0.1:5984/dibujo/_design/dibujo/_view/todos

Es el turno ahora de modificar el ejemplo de AngularJS para poder escribir y leer los registros de la base de datos.

En primer lugar debemos saber que AngularJS dispone de una interfazz para hacer llamadas AJAX contra el servidor fácilmente, al estilo JQuery. Esta interfaz es $http (http://docs.angularjs.org/api/ng.$http) y está especialmente diseñada para soportar el protocolo REST.

Comenzaremos por modificar el controlador existente para añadir una nueva función que salve el estado actual del juego:

$scope.saveElementos = function()
{
    $http.post("http://127.0.0.1:5984/dibujo", {"e":$scope.elementos,"t":new Date().getTime()})
    .success(function(datos)
    {
       $scope.listadoElementos.push(datos);
       $scope.respuestaServidor = datos;
    });
}
 

Podemos ver cómo hacemos una petición de tipo POST a la URL de la base de datos, pasando por parámeotr un objeto que tiene dos atributos “e” para salvar los elementos y “t” para salvar la fecha y hora. Los elementos se pueden sacar de $scope.elementos, que como ya sabemos de artículos anteriores, está continuamente actualizado gracias al doble binding con la vista de angular. La fecha se saca simplemente los milisegundos con el new Date().

Como se trata de una función asíncrona, preparamos una función anónima con el resultado del servidor. Por una parte insertamos con push() el resultado (que es el elemento insertado) en un listado en el que tenemos todas las partidas guardadas. Por otra parte actualizamos una simple etiqueta que muestra por pantalla el resultado. No hemos implementado la función de error.

Así se fácil es. Se crea una función que usa $http y funciona correctamente. ¡Ah! Es buena idea incluir la inyección del elemento $http en el controlador que acabamos de modificar.

angular.module('angularJs2BbApp').controller("ctrlDibujo", ["$scope","$http",function($scope, $http, elementosProvider)

El último paso para salvar elementos es crear un botón o un enlace que llame a esta función. Esto se tiene que hacer en la vista:

<button ng-click="saveElementos()"class="btn btn-warning">Salva elementos...</button>

Cuando se haga click sobre este botón se llamará a la función y se creará un nuevo estado en la base de datos con los datos de las celdas del dibujo.

Problema CORS

Aún queda un problema por resolver… y es el CORS (Cross-Origin resource sharing). Que resuminéndolo en una frase sería una llamada AJAX hecha de un dominio a otro. Es decir, nuestra aplicación puede estar corriendo en 127.0.0.1:9000 si la ejecutamos con gruntJS, pero el servidor CouchDB y su interfaz RESTful responden en 127.0.0.1:5984. Aunque es la misma IP no nos debemos olvidar del puerto, que hace que sea otro dominio diferente, y por eso dé el error. ¿Cómo podemos resolver esto?

La solución pasa por hacer que el servidor CouchDB emita una cabecera HTTP en cada llamada, indicando que admite que le hagan peticiones desde determinados dominios. Esta cabecera es “Access-Control-Allow-Origin”. ¿Cómo configuramos CouchDB par que la emita?

Es muy sencillo, sólo dos pasos (asegurante de tener una versión que lo soporta, la 1.5.0 sí que lo soporta – desconozco a partir de qué versión funciona):

1. ir a /etc/local.ini
2. Introducir estos valores:

[httpd]
enable_cors = true
[cors]
origins = *

3. Reinicamos el servidor CouchDB

Con esto le indicamos al servidor de CouchDB que vamos a recibir peticiones AJAX desde otro dominio, y que las aceptamos. Nuestra aplicación Angular, al recibir la contestación, verá que en la cabecera se permite su dominio, esto es “Access-Control-Allow-Origin:http://127.0.0.1:9000&#8221;. Así debería funcionar todo correctamente.

Además de ir almacenando las partidas, parece interesante disponer de algún método para ver las partidas que ya hay almacenadas. Es en este momento cuando vamos a utilizar la vista creada unos párrafos atrás. Cada vez que se cargue la aplicación web mostraremos un listado con las partidas guardadas en la base de datos.

Para ello procedemos de modo parecido a antes, utilizando la interfaz que nos proporciona $http para hacer llamadas AJAX:

$scope.loadTodosElementos = function()
{
 $http.get("http://127.0.0.1:5984/dibujo/_design/dibujo/_view/todos").success(function(datos)
 {
 $scope.listadoElementos=datos.rows;
 });
}

La URL que invocamos es la de la vista creada, que se encuentra dentro del documento_design dibujo. Esta vista nos devuelve un objeto JSON con metainformación, y dentro de su campo “row” se encuentra el listado de resultados que necesitamos. Utilizamos la variable “listadoElementos” para guardar cada uno de los estados del juego.

Usamos AngularJS para mostrar todos los elementos con un simple ng-repeat

<ol>
    <li ng-repeat="lista in listadoElementos"><a ng-click="loadElementosById(lista.id)" >{{formateaFecha(lista.key)}}</a></li>
</ol>

Si te fijas, para mostrar la fecha hemos usando la función “fromateaFecha”, que también es una función que forma partel del controlador que hemos creado. Vamos a crearla:

$scope.formateaFecha = function(milis)
{
    console.log("fecha: " + milis);
    return moment(new Date(milis)).format('llll');
}

Para facilitar el trabajo se ha usado http://momentjs.com/ que es una biblioteca de Javascript para el manejor de fechas. La puedes bajar directamente e incluirla dentro del index.html o usar bower si has usado Yeoman:

bower install -S momentjs

En el listado de las partidas, si te fijas, he incluído un enlace con ng-click y la llamada a la carga de la función por Id. Puedes suponer que se trata de otra función que hace uso del API RESTful de CouchDB:

$scope.loadElementosById = function(id)
{
    $http.get("http://127.0.0.1:5984/dibujo/"+id).success(function(datos)
    {
        $scope.elementos = datos.e;
    });
}

Con la URL apuntando hacia el ID correspondientes descargaremos el estado del dibujo determinado y como estamos usando AngularJS, al cambiar el modelo (la variable $scope.elementos), se cambia rápidamente la vista (que es el grid de divs sobre los que se pasea el ratón). El resultado total sería algo como esto:

angular2

En resumen, hemos visto cómo usar $http para conectar con una base de datos CouchDB que pone a nuestra disposición una interfaz RESTful para el desarrollo de aplicaciones AngularJS de una forma rápida y escalable. 

PS: podéis acceder al GitHub de esta aplicación en: https://github.com/4lberto/AngularJS_CouchDB