lunes, 13 de enero de 2014

Google App Engine y su DataStore (NoSQL)

Hace mucho que tengo este blog abandonado, sobre todo debido a que he estado liado con temas de startup que tan de moda está por estos tiempos que corren.

No obstante, me he permitido un respiro para explicaros un poco como almacenar información (en este caso NoSQL por ser más simple) en el DataStore de Google App Engine.

Instalar Plugin en Eclipse


Para instalar el plugin de Eclipse simplemente lo abrimos y vamos al menú Help > Install New Software…

Una vez en el nuevo asistente, indicaremos la siguiente URL:

https://dl.google.com/eclipse/plugin/4.3

Voy a presuponer que tenemos la versión 4.3 de Eclipse, de no ser así, simplemente hay que cambiar la versión al final de la URL.


Seleccionamos todo menos NDK Plugins y seguimos el proceso hasta el final con los parámetros por defecto. EN la última pantalla tendremos que aceptar todas las licencias de cada uno de los paquetes de forma independiente.


Y al finalizar el programa se preparará para instalarlo todo.




Y cuando termine, tendremos que reiniciar Eclipse.



Es un proceso muy simple, pero para cualquier duda sobre la instalación, podéis consultar esta URL: https://developers.google.com/eclipse/docs/install-eclipse-4.3?hl=es

Creación del un proyecto GAE


Para crear un proyecto GAE, simplemente tendremos que ir a File > New > Other y seleccionar Google > Web Application Project.

En el asistente, indicaremos el nombre del proyecto, el paquete solo usaremos Google App Engine en la versión del SDK que tengamos por defecto.

Un detalle importante: hay que desmarcar Google Web Tool Kit y dejar marcado que nos genere código de ejemplo.



Al finalizar, nos generará un proyecto con una página web (war/index.html) que hace una invocación a un servlet de ejemplo (src/{paquete}/{nombre proyecto}servlet.java).



Si ejecutamos el proyecto (Run As > Web Application>, veremos en la URL localhost:8888 nuestra web.





Creación de Objetos persistentes (NoSQL)


Para crear objetos persistentes NoSQL usaremos las clases JAVA y extenderemos de java.io.Serializable. En nuestro ejemplo vamos a crear una ClaseA que tendrá un atributo String y otro del tipo ClaseB. ClaseB tendrá también un atributo String y una lista de objetos de ClaseC. Y ClaseC tendrá dos atributos string.

Algo así:

ClaseA
- nombre : String
- claseB : String
ClaseB
- nombre : String
- clasesC : List<ClaseC>
ClaseC
- nombre : String
- otraPropiedad : String

Comencemos a “picar código”:

package com.demogae.model;

public class ClaseA {
       private String nombre;
       private ClaseB claseB;
}

package com.demogae.model;
import java.util.List;

public class ClaseB {
       private String nombre;     
       private List<ClaseC> clasesC;
}

package com.demogae.model;

public class ClaseC {
       private String nombre;
       private String otraPropiedad;
}


Como había comentado antes, para que estas clases se almacenen en el DataStore de GAE, deben ser serializables, por lo que implementaremos java.io.Serializable. Al hacer esto, nos dará un warning y tendemos que añadir la propiedad serial version ID para que se quede tranquilo.



Nuestras clases irán quedando tal que así:

package com.demogae.model;
import java.io.Serializable;

public class ClaseC implements Serializable{
       private static final long serialVersionUID = 1L;
       private String nombre;
       private String otraPropiedad;
}

Como hemos comentado, usaremos NoSQL, por lo que solo tendremos una única entidad (ClaseA) y el resto ellas se almacenarán con BLOB en sus atributos.

Para ello, indicamos en la claseA la etiqueta @Entity y @XmlRootElement y a su propiedad claseB le decimos que es un @Lob.

package com.demogae.model;
import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.xml.bind.annotation.XmlRootElement;
import com.google.appengine.api.datastore.Key;

@Entity
@XmlRootElement
public class ClaseA implements Serializable{
       private static final long serialVersionUID = 1L;
       @Id
       private Key key;
       private String nombre;
       @Lob
       private ClaseB claseB;
}


Como veréis, he añadido una propiedad del tipo com.google.appengine.api.datastore.Key con una etiqueta @Id ya que esta será la clave de la “tabla”.

También indicaremos @Lob a la propiedad de clasesC de ClaseB, aunque NO indicaremos ni @Entity, ni @XmlRootElement.

package com.demogae.model;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Lob;

public class ClaseB implements Serializable{
       private static final long serialVersionUID = 1L;
       private String nombre;
       @Lob
       private List<ClaseC> clasesC;
}


La etiqueta @Entity nos indica que una clase es una entidad. En cambio, la etiqueta @Lob la usaremos para indicar al DataStore como queremos almacenar esa propiedad compleja. Y como en el caso de ClaseC todas las propiedades son simples, no hacemos uso de @Lob en ninguna de ellas.

Finalmente, crearemos el constructor sin parámetros y los getters y setters para todas las propiedades de cada clase (salvo serialVersionUID).

NOTA: Os recomiendo redefinir también el método toString para que al “jugar” nos sea más fácil ver los objetos.

Creación de los manejadores DAO


Crearemos una clase para manejar las entidades. Para ello, necesitaremos hacer uso de la clase EntityManager, haciendo uso del patrón singleton (http://es.wikipedia.org/wiki/Singleton).

package com.demogae.dao;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class DAOControl {
       private static EntityManagerFactory emf;
       //Singleton para devolver a EntityManager
       protected final EntityManager getEntityManager() {
              synchronized (DAOControl.class) {
                     if (emf == null) {
                            emf = Persistence.createEntityManagerFactory("transactions-optional");
                     }
              }
              return emf.createEntityManager();
       }
      
}

Pero esto es un simple C&P: lo realmente importante de nuestro manejador de entidades serán los métodos para acceder al DataStore. En concreto crearemos dos: uno para crear objetos (createClaseA) y otro para recuperarlos (getClaseA).

Comenzaremos por el de creación

Para ello, obtendremos el EntityManager con el método anterior y comenzaremos una transacción. Crearemos su key y persistiremos el objeto.

public ClaseA createClaseA(ClaseA claseA) {
       //obtenemos el entitiy manager del singleton
       EntityManager mgr = getEntityManager();
       try {
              //iniciamos la transacción
              mgr.getTransaction().begin();
              //creamos un código único (OID)
              String oid = System.currentTimeMillis()
                            + Long.toHexString((new SecureRandom()).nextInt());
              //creamos la clave a partir del OID
              Key key = KeyFactory.createKey(
                            ClaseA.class.getSimpleName(),oid);
              claseA.setKey(key);
              //persistimos el objeto
              mgr.persist(claseA);
              //y hacemos commit de la transaccion
              mgr.getTransaction().commit();
       } catch (Exception e) {
              e.printStackTrace();
       } finally {
              mgr.close();
       }
       return claseA;
}

En el caso de la consulta de entidades, obtenemos el EntityManager y creamos la consulta para que nos devuelva el objeto.


public ClaseA getClassA(String nombre) {
       EntityManager mgr = getEntityManager();
       ClaseA res = null;
       try {
              String strQuery = "SELECT c FROM ClaseA AS c"
                            + " WHERE c.nombre = :nombre";
              Query query = mgr.createQuery(strQuery);
              query.setParameter("nombre", nombre);
              ClaseA result = (ClaseA)query.getSingleResult();
              if (result != null) {
                     res = result;
              }
       } catch (javax.persistence.NoResultException ne) {
              return res;
       } catch (Exception e) {
              e.printStackTrace();
       } finally {
              mgr.close();
       }
       return res;
}

IMPORTANTE: En este caso estamos esperando un único resultado en la query (query.getSingleResult), pero si esperamos más, deberemos hacer uso del método getResultList.

Ejemplo de uso del controlador DAO


Para ver con un ejemplo como se almacenan los datos en el DataStore abriremos el servlet que se nos creó de ejemplo y crearemos dos métodos, uno para probar la escritura y otro para la lectura.

Para escritura


private void createObject(HttpServletResponse resp) throws IOException{
       resp.getWriter().println("Creando clases");
      
       ClaseC claseC1 = new ClaseC();
       claseC1.setNombre("c1");
       claseC1.setOtraPropiedad("cualquier valor c1");
      
       ClaseC claseC2 = new ClaseC();
       claseC2.setNombre("c2");
       claseC2.setOtraPropiedad("cualquier valor c2");
      
       ClaseB claseB = new ClaseB();
       claseB.setNombre("b");
       claseB.setClasesC(new ArrayList<ClaseC>());
       claseB.getClasesC().add(claseC1);
       claseB.getClasesC().add(claseC2);
      
       ClaseA claseA = new ClaseA();
       claseA.setNombre("a");
       claseA.setClaseB(claseB);
      
       resp.getWriter().println("almacenando claseA");
       DAOControl dao = new DAOControl();
       claseA = dao.createClaseA(claseA);
       if (claseA!=null){
              resp.getWriter().println("Objeto almacenado: " + claseA.toString());
       } else {
              resp.getWriter().println("Ha ocurrido un error y no se ha podido almacenar el objeto");
       }
}

Para lectura


private void loadObject(HttpServletResponse resp) throws IOException{
       DAOControl dao = new DAOControl();
       resp.getWriter().println("obteniendo objeto 'a' de la entidad ClaseA");
       ClaseA claseA = dao.getClassA("a");
       if (claseA!=null){
              resp.getWriter().println("Objeto extraido: " + claseA.toString());
       } else {
              resp.getWriter().println("Ha ocurrido un error obteniendo el objeto");
       }
}


Y finalmente, en nuestro servlet solo tendremos que invocar a uno u otro método a nuestro gusto.

public void doGet(HttpServletRequest req,
              HttpServletResponse resp) throws IOException {
       resp.setContentType("text/plain");
       resp.getWriter().println("Hello, world");
      
       //createObject(res);
       //loadObject(res);
}

Y para ejecutarlo, tan fácil como invocar al servlet en su URL http://localhost:8888

IMPORTANTE: Podremos consultar y gestionar los objetos que tenemos en la URL http://localhost:8888/_ah/admin




Ejemplo de ejecución del createObject


Hello, world Creando clases almacenando claseA Objeto almacenado: ClaseA [key=ClaseA("1389625781489ffffffffc2175f50"), nombre=a, claseB=ClaseB [nombre=b, clasesC=[ClaseC [nombre=c1, otraPropiedad=cualquier valor c1], ClaseC [nombre=c2, otraPropiedad=cualquier valor c2]]]]

Ejemplo de ejecución del loadObject


Hello, world obteniendo objeto 'a' de la entidad ClaseA Objeto extraido: ClaseA [key=ClaseA("1389625781489ffffffffc2175f50"), nombre=a, claseB=ClaseB [nombre=b, clasesC=[ClaseC [nombre=c1, otraPropiedad=cualquier valor c1], ClaseC [nombre=c2, otraPropiedad=cualquier valor c2]]]]


Cómo desplegar en GAE


Lo primero que tenemos que hacer es logarnos desde Eclipse en Google en la parte inferior derecha.



Una vez logados, sobre el proyecto pulsamos botón derecho y vamos a Google > Deploy to App Engine.




Se nos abriá un asistente que no nos hacer deploy ya que no tenemos configurado el app Id de GAE.  Para esto tendremos que crearnos una aplicación desde App Engine.

En este asistente pulsaremos sobre App Engine project settings… y nos abrirá las propiedades del proyecto para App Engine. En esta nueva pantalla indicaremos nuestra app Id si ya la tenemos, o si pulsamos sobre my applications navegaremos a nuestra cuenta App Engine para crearnos una.



Para crearla, tan sencillo como seguir el asistente





Asegurandonos que la app Id que usemos no est18esplegar. el asistente ya nos dejaropiedades de App Engine, e indicamos el Identificador de Aplicacia cuenta App Engine para cá en uso.



Una vez creado volvemos a nuestras propiedades de App Engine, e indicamos el Identificador de Aplicación que hayamos creado


Y tras esto el asistente ya nos dejará desplegar.




Y cuando termine ya tenedremos nuestra aplicación con la URL del tipo http://{app id}.appspot.com

El código lo podeis descargar de aquí.

Espero que os sea de utilidad.


Un saludo!
JC