Cloud
He desarrollado múltiples proyectos empleando la tecnología de Google Cloud Platform explotando muchas de las características que ofrece. Mi principal preocupación en cada proyecto siempre es la optimización de los recursos consumidos y los tiempos de respuesta de la aplicación.
Google App Engine
Comencé a trabajar con Google App Engine mientras me encontraba laborando en Orcius, donde después de evaluar las características de los distintos servicios de nube de Microsoft, Amazon y Google, me decanté por éste último debido al ecosistema que ofrecía.
Desde entonces me he actualizado en el uso y manejo de los servicios que ofrece, tales como la construcción de máquinas virtuales, integraciones con API's de mapas y geolocalización, endpoints para aplicaciones móviles, etc. Al tiempo que me he destacado en el diseño y construcción de Bases de Datos Orientadas a Objetos.
Para trabajar con Google Cloud Datastore, he creado mi propio framework a partir de Objectify 5.0, de manera que pueda construir y consumir sus servicios de una manera más rápida y efectiva dentro del código.
¿Objectify?
Una librería de Google para trabajar con Bases de Datos No Relacionales, que actualmente se encuentra en su versión 5. Sin embargo, al día de hoy, el trabajo que se debe realizar para integrarla dentro de un proyecto genera grandes cantidades de código 'ruidoso' dentro de la lógica de trabajo. Motivo por el cual he creado mi propio framework
Generalizando Objectify
Primero y antes que nada, Objectify no maneja tipos durante la compilación, por lo que es fácil confundirse sin darse cuenta, motivo por el cual lo he generalizado la clase y he sobre-escrito los métodos más habituales para tenerlos todos a la mano con el tipo de dato correcto, los métodos más importantes de la clase son los más elementales: add, delete, get, list, listByProperty y se encuentran muchas veces sobre-cargados
public class ObjectifyDao<T> {
protected Class<T> clazz;
public ObjectifyDao(Class<T> clazz) {
this.clazz = clazz;
}
public Key<T> add(T entity) {
Result<Key<T>> result = ObjectifyService.ofy().save().entity(entity);
return result.now();
}
public Map<Key<T>, T> add(List<T> counts) {
Result<Map<Key<T>, T>> result = ObjectifyService.ofy().save().entities(counts);
return result.now();
}
public void delete(T entity) {
ObjectifyService.ofy().delete().entity(entity);
}
public void delete(Collection<Key<T>> entities) {
ObjectifyService.ofy().delete().entities(entities);
}
public void delete(Key<T> entityKey) {
ObjectifyService.ofy().delete().key(entityKey);
}
public T get(Key<T> key) throws EntityNotFoundException {
Result<T> result = ObjectifyService.ofy().load().key(key);
return result.now();
}
public Map<Key<T>, T> get(Collection<Key<T>> keys) {
return ObjectifyService.ofy().load().keys(keys);
}
public List<T> getAllList(Key<?> parentKey) {
Query<T> q = ObjectifyService.ofy().load().type(clazz).ancestor(parentKey);
return q.list();
}
public List<T> getAllList(String order, int offset, int limit) {
Query<T> q = ObjectifyService.ofy().load().type(clazz);
if (order != null && !order.trim().isEmpty()){
q = q.order(order);
}
q = q.offset(offset).limit(limit);
return q.list();
}
...
Por otro lado, he creado una interface que cualquier manejador de objetos puede implementar para trabajar con entidades del datastore
public interface EntityService<T>{
public void setObjectifyDao(ObjectifyDao<T> ofyDao);
public T load(Key<T> key);
public T load(String key);
public Map<Key<T>, T> load(Collection<Key<T>> keys);
public Key<T> save(T entity);
public Map<Key<T>, T> save(List<T> entity);
public void delete(Key<T> key);
public void deleteEntities(List<Keyable<T>> keys);
public void delete(List<Key<T>> keys);
public List<T> list(Key<?> parent);
public List<T> list(Key<?> parent, String order, int offset, int limit);
public List<T> list(Key<?> parent, int offset, int limit);
public List<T> list();
public List<T> list(int offset, int limit);
public List<T> list(String order, int offset, int limit);
public boolean isHardRequired();
public void setHardRequired(boolean hardRequired);
}
Siendo que muchas entidades deberían implementar ésta interface para procedimientos equivalentes, creé una clase de la que pueden heredar y que realiza muchos de los procesos comunes:
public abstract class EntityServiceImpl<T> implements EntityService<T> {
protected ObjectifyDao<T> dao;
protected boolean hardRequired = false;
public boolean isHardRequired() { return hardRequired; };
public void setHardRequired(boolean hardRequired) {
this.hardRequired = hardRequired;
}
public void setObjectifyDao(ObjectifyDao<T> objectifyDao) {
this.dao = objectifyDao;
}
@Override
public T load(Key<T> key) {
try {
return isHardRequired() ? dao.getHard(key) : dao.get(key);
} catch (Exception e) {
return null;
}
}
@Override
public T load(String keyString) {
if (keyString != null && !keyString.trim().isEmpty()){
Key<T> key = Key.create(keyString);
return load(key);
}
return null;
}
@Override
public Map<Key<T>, T> load(Collection<Key<T>> keys) {
return dao.get(keys);
}
@Override
public Key<T> save(T entity) {
return isHardRequired() ? dao.addHard(entity) : dao.add(entity);
}
@Override
public Map<Key<T>, T> save(List<T> entity) {
return dao.add(entity);
}
@Override
public void delete(Key<T> key) {
dao.delete(key);
}
public void deleteEntities(List<Keyable<T>> objects) {
List<Key<T>> keys = new ArrayList<>(objects.size());
for (Keyable<T> o : objects) {
keys.add(o.getKey());
}
dao.delete(keys);
}
public void delete(List<Key<T>> keys) {
dao.delete(keys);
}
@Override
public List<T> list(Key<?> parentKey) {
if (parentKey != null) {
return dao.getByParentId(parentKey);
} else {
return dao.getAllList();
}
}
@Override
public List<T> list(Key<?> parentKey, int offset, int limit) {
return list(parentKey, null, offset, limit);
}
@Override
public List<T> list(Key<?> parentKey, String order, int offset, int limit) {
if (parentKey != null) {
return dao.getList(parentKey, order, offset, limit);
} else {
return dao.getAllList(order, offset, limit);
}
}
@Override
public List<T> list() {
return dao.getAllList();
}
@Override
public List<T> list(int offset, int limit) {
return list("", offset, limit);
}
@Override
public List<T> list(String order, int offset, int limit) {
return dao.getAllList(order, offset, limit);
}
}
Éstas clases e interfaces se encuentran definidas dentro de mis propias librerías, por lo que en cualquier proyecto que desee implementarlas, sólo debe crear la clase de entidad y definir los servicios con un mínimo de líneas de código
EntityClass, EntityService y EntityServiceImpl
Partiendo de una clase EntidadEjemplo, se puede construir un proceso muy simple para acceder al Datastore
@Entity
public class EntidadEjemplo {
@Id private Long id;
private String dato;
@Index private Date fecha;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getDato() { return dato; }
public void setDato(String dato) { this.dato = dato; }
public Date getFecha() { return fecha; }
public void setFecha(Date fecha) { this.fecha = fecha; }
}
Creo una interfaz que me pueda permitir personalizar posteriormente los comportamientos, de momento, sólo heredo de EntityService
public interface EntidadEjemploService extends EntityService<EntidadEjemplo> {}
Creo una clase que implemente la interfaz que acabo de crear, heredando de EntityServiceImpl
public class EntidadEjemploServiceImpl implements EntidadEjemploService extends EntityServiceImpl<EntidadEjemplo> {}
Con ésto, sin más líneas de código, puedo acceder a los métodos básicos de Objectify que he sobrecargado, de manera tipificada en cualquier bloque que requiera
public class FuncionesEjemplo {
private EntidadEjemploService ees = ServiceFactory.getInstance().getEntidadEjemploService();
public String registrarEjemplo(Date hoy, String dato) {
EntidadEjemplo ee = ees.getByProperty("fecha", hoy);
if (ee == null) {
ee = new EntidadEjemplo();
}
ee.setDato(dato);
Key<EntidadEjemplo> key = ees.save(dato);
return key.getString();
}
}
Configuraciones de Spring
Como parte de las configuraciones adicionales que requiere Objectify, es que se inicialice con cada una de las entidades que deseemos leer / escribir, por lo que cree una clase OfyService que mantiene un singleton inicializado desde que se arranca la ejecución de la app con un objeto de Objectify y registro las clases con las que trabajaré
Finalmente, como en el ejemplo, normalmente mantengo singleton ServiceFactory que me permita acceder a los servicios donde quiera que esté en el código del proyecto
...
<bean id="OfyService" class="com.genexis.dao.OfyService" factory-method="getInstance">
<constructor-arg name="classes">
<array value-type="java.lang.Class">
<value>com.genexis.ccorona.entity.EntidadEjemplo</value>
...
</array>
</constructor-arg>
</bean>
...
<bean id="entidadEjemploDao" class="com.genexis.dao.ObjectifyDao">
<constructor-arg name="clazz" value="com.genexis.ccorona.entity.EntidadEjemplo" />
</bean>
<bean id="entidadEjemploService" class="com.genexis.ccorona.service.impl.EntidadEjemploServiceImpl">
<property name="objectifyDao" ref="entidadEjemploDao" />
</bean>
<bean id="ServiceFactory" class="com.genexis.ServiceFactory" factory-method="getInstance">
<property name="entidadEjemploService" ref="entidadEjemploService" />
...
</bean>