Como pasar de SQL a NoSQL sin sufrir (2)

Conceptos de MongoDB para Devs — Segunda parte

En mi post anterior cambiamos del modelo relacional al modelo de documentos haciendo una comparativa entre las diferentes sentencias que se pueden aplicar a nuestra definición de datos y su consulta; vimos los conceptos básicos de MongoDB y evaluamos ambos modelos.

En este nuevo post haremos uso de esos conceptos y los aplicaremos a un proyecto real, enfocándonos específicamente en el uso de la API de Java para MongoDB.

¡A codear!

Para poder hacer uso del driver de Java para MongoDB en un proyecto real he elegido el ejemplo de un buscador de libros que puede estar en una biblioteca, una librería o en internet ya dependerá de su imaginación :p

Haremos un modelo sencillo pero sólido que nos ayude a comprender el modelo de documentos y cómo implementar en un lenguaje de programación, un driver que haga la conexión a nuestra database para almacenar y consultar nuestros datos.

Pre-requisitos

Modelo de documentos

Ya que nuestro proyecto elegido es un buscador de libros lo primero que debemos identificar son los datos que vamos a usar. Un libro puede ser buscado por el título que posee, por el nombre del autor o autores que lo escribieron, por la editorial o por la categoría a la que pertenece, al menos estos son los principales, así que necesitamos representar estos datos en nuestro modelo. Primero lo haremos de forma relacional y después en documentos.

SQL model

Nuestro modelo relacional necesita 5 tablas de las cuáles al hacer un consulta, necesitará de al menos un join para mostrar la información completa en base a la búsqueda hecha. Ahora veamos el modelo de documentos:

NoSQL model using MongoDB

Ahora que ya hemos modelado nuestros datos, podemos comenzar a diseñar nuestra aplicación web.

Aplicación Web

Ya hemos dicho que lo importante de este post es aprender a usar el driver de Java para MongoDB por lo que haremos una arquitectura sencilla que consistirá de una simple REST API usando Spark Java que será consumida por un cliente web sencillo con Bootswatch y jQuery.

Model (Data Source)

Tomando en cuenta nuestro modelo de datos de documentos lo primero que debemos hacer es pasar ese modelo a clases de Java, crearemos entities que representen nuestros datos de la mejor forma. La primer idea que les puede venir a la mente tal vez sea que debemos crear una única entity la cual represente uno de nuestros documents en la collection de Books, sin embargo no hemos analizado que el modelo de nuestro document se compone de otro arreglo de documents lo cuál nos indica que debemos representar los documents involucrados en clases separadas. Quedando de la siguiente forma:

Lo siguiente que haremos será implementar un “Adapter” para cada entity el cuál nos permitirá adaptar un objeto Java a un Document de MongoDB y viceversa. En esta implementación será necesario agregar las librerías del driver de MongoDB a nuestro proyecto de Java, ya sea con Maven o Gradle según tu tipo de proyecto. La dependencia que debes agregar es la siguiente:

Maven
<dependencies>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
<dependencies> 
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-async</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
Gradle
dependencies {
compile 'org.mongodb:mongodb-driver-async:3.4.2',
complie 'org.mongodb:mongodb-driver-async:3.4.2'
}

Para nuestra implementación usaremos una combinación de MongoDB Async Driver para las operaciones write y MongoDB Sync Driver para las operaciones read. MongoDB Async Driver nos va a permitir hacer llamadas asíncronas a la database, esto nos traerá algunas ventajas que iremos analizando durante este post. Para nuestro adapter primero crearemos una Java Interface la cuál va a definir los métodos necesarios para hacer un adapter de nuestras entities a los Documents de MongoDB. Un Document de MongoDB es un objeto de Java que representa a un document en la database, cada que nos conectemos a la database para hacer alguna operación read o write tendremos que usar un objeto Document pues este type es lo que el driver de mongo entiende, quedando de la siguiente forma:

Ya que tenemos nuestros adapter ahora podemos continuar con un patrón de diseño más, que es DAO. Este patrón nos ayudará a darle estructura a nuestro código además de que nos permitirá abstraer y encapsular el acceso a los recursos de datos. La implementación que haremos será un Factory DAO sencillo, ya que no usaremos más de un tipo de persistencia sólo MongoDB crearemos un Factory DAO para nuestras entity Book. En el caso en el que deseen agregar más de un tipo de persistencia de datos, podrán agregarlo fácilmente incrementando una capa más en la estructura con otro Factory de Factory DAOs, ¿ suena bien no creen :p ?

Lo primero que debemos hacer es crear nuestros DAOs bases, los que nos permitirán hacer operaciones en nuestras entities o transfer objects (según la definición). Comencemos con la definición de nuestras interfaces DAO.

A continuación nuestras clase concreta MongoBookDAOImp. Observa que hemos implementado sólo lo básico de nuestro CRUD, podemos hacer búsquedas más avanzadas según sea el caso, pero lo dejaremos lo más simple posible por ahora.

¿Notaste que los métodos del drive de mongo para las operaciones CRUD ya hacen uso de las bondades de Java 8? Bondades como funciones anónimas para procesar los resultados de las operaciones write una vez que ya fue ejecutada la operación, o poder usar la función map y foreach sobre los datos obtenidos de la database, tales como:

//for a Void result and a possible Trhowable, executes the following
( final Void result, final Throwable t ) -> { }
map( new Function<Document, Book>() { 
// map from Document to Book
@Override
public Book apply( Document doc ) {
return bookAdapter.toPOJO( doc );
}
});
forEach( (Block<Document>) doc -> {   
//add each document received to the List<Book>
//doing a parse from document to book

books.add( bookAdapter.toPOJO( doc ));
});

Estas ventajas permiten reducir boilerplate, evitando que implementemos un callback, cuando simplemente debemos declararlo para cuando sea necesaria su ejecución.

Ahora, ¿Por qué hemos usado la librería async para las operaciones write y porqué sync para las operaciones read? El motivo es sencillo, como developers lo que debemos analizar siempre es cuando podemos usar llamadas asíncronas y cuando las síncronas (si el lenguaje lo permite) para recursos que bloquean la aplicación hasta que no se termine de ejecutar la tarea en cuestión. Analicemos, las operaciones write a una database son operaciones que siempre tardan más porque escribir en disco por lo general cuesta más procesamiento que leer del disco, y si dejamos que esta operación se ejecute en una llamada síncrona estaremos bloqueando la ejecución de la aplicación hasta que se haga la escritura a disco, cuando lo que podemos hacer es simplemente hacer la llamada y continuar la ejecución para después recibir el resultado de la operación en algún callback. En cambio las llamadas de lectura aunque pueden tardar en ser ejecutadas son operaciones de datos que necesitamos en ese momento y podemos esperar por ellas, por lo general cuando se hace una llamada de lectura es porque necesitas esos datos para procesarlos o inclusive para mandar llamar a otros datos, es por eso que en esta operación una llamada síncrona es la más común, sin embargo no descartemos las situaciones cuando una operación read pueda hacerse de forma asíncrona para después ser procesada.

Por último nuestra MongoDAOFactory, pon atención a la implementación de este factory que utiliza las nuevas bondades de Java 8.

Bien, hemos terminado toda la parte del Modelo ahora podemos pasar a la parte del servicio la cuál pasaremos de rápido sin tanto detalle pues no es el fuerte de éste tutorial.

Service (Simple REST API)

Lo primero que debemos hacer es agregar las librerías necesarias para crear nuestra REST API. En este proyecto usaremos Spark Java, un micro framework que nos permite crear un servicio web con el mínimo esfuerzo. Al arrancar el servicio de Spark estamos arrancando un web server embebido (jetty) el cuál escucha en el puerto default 4567. Para poder usar la API de Spark agrega la siguiente dependencia a tu proyecto:

Maven 
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.6.0</version>
</dependency>
Gradle
compile 'com.sparkjava:spark-core:2.6.0'

Como muchos saben, una REST API se compone de paths los cuáles representan nuestros recursos disponibles para ser llamados, es por esto que nuestro primer paso usando spark será definir los paths que vamos a exponer como servicios y hacer la implementación de estos recursos los cuáles obviamente harán uso de nuestro ya definido modelo de datos.

/booksearcher
/books
/create
/update
/delete
/find
/all
/isbn
/filter

Ya tenemos los paths, ahora vamos a crear una Java interface la cuál defina los métodos que van a ser expuestos como servicios a través de estos paths.

A continuación, implementaremos los métodos que definimos en nuestra interface. En esta clase vamos a instanciar el DAO para el cuál vamos a exponer los servicios, que en este caso es nuestro BookDAO, y vamos a usar los métodos que implementamos para hacer operaciones sobre el modelo de datos.

Por último haremos uso de los métodos de Spark para configurar nuestros paths y procesar los métodos HTTP que definamos para nuestros servicios. Para crear un nuevo document en nuestra database usaremos el método post de HTTP y el método createBook de nuestro servicio, para hacer un update a un document existente usaremos el método put de HTTP y updateBook de nuestro servicio, el método delete HTTP y deleteBook para remover un document existente (el isbn será usado como ID de nuestros books) y por último para hacer cualquier consulta usaremos el método get de HTTP y nuestros métodos findAllBooks, findBookByISBN y findBookByFilter.

Espero hayas notado que estamos haciendo uso de Jackson, una librería de Java que nos permite transformar nuestros objetos java a formato JSON y viceversa. JSON es un formato muy usado para hacer comunicación web ya que es ligero y fácil para representar nuestros datos. Para usar Jackson en tu proyecto web agrega la siguiente dependencia.

Maven
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.6</version>
</dependency>
Gradle
compile 'com.fasterxml.jackson.core:jackson-databind:2.8.6'

En este punto donde ya tenemos un servicio disponible es posible hacer pruebas a nuestro código para ver si se está ejecutando de forma correcta. Para hacer las pruebas basta con que ejecutes la clase ServerREST, una vez ejecutada puedes hacer una prueba llamando al path: http//localhost:4567/booksearcher/books/find/all, si el server te responde con el formato json de los documents que tienes guardados en la collection books entonces tu servicio está ejecutando correctamente esta operación. Para hacer pruebas de las otras operaciones apoyate de una tool como curl en command line o como ARC en chrome para consumir tus servicios.

Web Client

Es tiempo de crear nuestro Web Client. En un inicio les mencioné lo que usaríamos para hacerlo y espero lo recuerden, Bootswatch + jQuery.

Estos frameworks son más que suficientes para consumir nuestra REST API y hacer las operaciones CRUD sobre nuestro modelo de datos con MongoDB. No nos complicaremos en la implementación así que utilizaremos un template básico de Bootswatch. Bootswatch es una especie de wrapper de Bootstrap que ya tiene themes definidos y nos ahorran mucho, es por eso que debemos tener como base Bootstrap para después usar Bootswatch.

  1. Crea una carpeta que se llame public dentro de tu proyecto web en /src/main/resources.
  2. Agrega Bootstrap a tu proyecto web copiando las carpetas css, js y fonts dentro de public.
  3. Agrega Bootswatch a tu proyecto web creando una carpeta llamada bootswatch dentro de public y copiando ahí el archivo bootstrap.css, bootswatch.less y variables.less que descargues de la página de bootswatch una vez que hayas elegido el tema que te haya gustado. Ponerlo en una carpeta nueva permitirá que puedas cambiar a lo estilos de bootstrap sin problema.
  4. Crea un archivo index.html dentro de public y agrega el template que desees para tu web app BookSearcher.
  5. Crea un archivo booksearcher.js dentro de js y agrega tu código.

Tu Web Client debe lucir parecido a esto :)

BookSearcher (index.html)

Para ver el código del index.html aquí y el de booksearcher.js acá.

Ahora ejecutemos todo junto y hagamos las pruebas *\o/*

  1. Arranca tu servicio mongod en command line.
  2. Arranca tu servicio web ejecutando la clase ServerREST.java de tu proyecto.
  3. Abre tu navegador y ve a esta dirección: http://localhost:4567/booksearcher/index.html
  4. ¡PRUEBA!

¿Te gustó?, ¿Quieres colaborar con el código? Hazlo aquí.

NOTA: En este ejemplo del web client sólo hemos incluido las operaciones de búsqueda, en caso de querer agregar las operaciones restantes puedes hacer fork y agregarlas por tu cuenta y/o contribuir al código haciendo pull request.

Show your support

Clapping shows how much you appreciated Marcela Sena’s story.