Entidades, propiedades y claves

Los objetos de datos en Cloud Datastore se conocen como entidades. Una entidad tiene una o más propiedades, que pueden tener uno o más valores. No es necesario que las entidades similares tengan las mismas propiedades y los valores de una entidad para una propiedad determinada no necesitan pertenecer al mismo tipo de datos. (Si fuera necesario, una aplicación puede establecer y aplicar estas restricciones en su propio modelo de datos).

Cloud Datastore es compatible con una variedad de tipos de datos para valores de propiedad. Se incluyen, entre otros:

  • Enteros
  • Números de coma flotante
  • Strings
  • Fechas
  • Datos binarios

Para obtener una lista completa de los tipos, consulta Tipos de valores y propiedades.

Cada entidad en Cloud Datastore tiene una clave que la identifica de manera única. La clave consta de los siguientes componentes:

  • El espacio de nombres de la entidad, que permite la función multiusuario
  • El tipo de entidad, que la clasifica con el propósito de realizar consultas de Cloud Datastore
  • Un identificador de la entidad individual, que puede ser una de las opciones siguientes:
    • una string de nombre de clave
    • un ID numérico de número entero
  • Una ruta de acceso principal opcional, que ubica la entidad dentro de la jerarquía de Cloud Datastore

Una aplicación puede recuperar una entidad individual de Cloud Datastore con la clave de la entidad, o puede recuperar una o más entidades con una consulta basada en los valores de propiedad o las claves de las entidades.

El SDK de App Engine para Java incluye una API simple, proporcionada en el paquete com.google.appengine.api.datastore, que es compatible con las características de Cloud Datastore. Todos los ejemplos de este documento se basan en esta API de bajo nivel; puedes elegir usarla directamente en tu aplicación o como la base sobre la que crear tu propia capa de administración de datos.

Cloud Datastore no aplica ninguna restricción en las estructuras de las entidades por sí misma. Por ejemplo, si una propiedad posee un valor de un tipo en particular, esta tarea le corresponde a la aplicación.

Identificadores y tipos

Cada entidad de Cloud Datastore pertenece a un tipo en particular, que permite clasificar a la entidad con el propósito de realizar consultas. Por ejemplo, una aplicación de recursos humanos podría representar a cada empleado en una empresa con una entidad del tipo Employee. En la API de Datastore para Java, debes especificar un tipo de entidad cuando la creas, como un argumento en el constructor de Entity(). Todos los nombres de tipos que comienzan con dos guiones bajos (__) están reservados y no se pueden usar.

Con el ejemplo siguiente, se crea una entidad del tipo Employee, y se propagan sus valores de propiedad y se guardan en Datastore:

Entity employee = new Entity("Employee", "asalieri");
employee.setProperty("firstName", "Antonio");
employee.setProperty("lastName", "Salieri");
employee.setProperty("hireDate", new Date());
employee.setProperty("attendedHrTraining", true);

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
datastore.put(employee);

Además de un tipo, cada entidad también posee un identificador, que se le asigna cuando se crea la entidad. Debido a que es parte de la clave de la entidad, el identificador se encuentra asociado de forma permanente a la entidad y no se puede cambiar. Se puede asignar de dos maneras:

  • Tu aplicación puede especificar su propia string de nombre de clave para la entidad.
  • Puedes pedirle a Cloud Datastore que asigne de forma automática un ID numérico de número entero a la entidad.

Para asignarle un nombre de clave a la entidad, proporciona el nombre como el segundo argumento para el constructor cuando crees la entidad:

Entity employee = new Entity("Employee", "asalieri");

Para que Cloud Datastore asigne un ID numérico automáticamente, omite este argumento:

Entity employee = new Entity("Employee");

Asignar identificadores

Cloud Datastore se puede configurar para generar ID automáticamente mediante dos políticas de identificación automática diferentes:

  • La política default genera una secuencia aleatoria de ID sin usar con una distribución aproximadamente uniforme. Cada ID puede tener hasta 16 dígitos.
  • La política legacy crea una secuencia de ID no consecutivos más breves compuestos por números enteros.

Si deseas mostrar los ID de las entidades a los usuarios o depender de que los soliciten, lo más recomendable es que uses la asignación manual.

Cloud Datastore genera una secuencia aleatoria de ID sin usar con una distribución aproximadamente uniforme. Cada ID puede tener hasta 16 dígitos.

Se garantiza que los valores de ID asignados por el sistema son siempre únicos para el grupo de entidad. Si copias una entidad desde un grupo de entidad o un espacio de nombres a otro y quieres conservar la parte de la clave que contiene el ID, asegúrate de asignar primero el ID a fin de evitar que Cloud Datastore seleccione ese ID para una asignación futura.

Rutas principales

Las entidades de Cloud Datastore conforman un espacio con estructura jerárquica, similar a la estructura de directorios de un sistema de archivos. Cuando creas una entidad, tienes la opción de designar otra entidad como su superior, en cuyo caso la nueva entidad es secundaria de la entidad principal (ten en cuenta que, a diferencia de lo que ocurre en un sistema de archivos, no es necesario que la entidad principal exista realmente). Las entidades que no tienen un superior se denominan entidades raíz. La asociación entre una entidad y su superior es permanente y no puede cambiarse una vez creada la entidad. Cloud Datastore nunca asignará el mismo ID numérico a dos entidades con el mismo superior ni a dos entidades raíz (que no tienen un superior).

El superior de una entidad, el superior de su superior y así sucesivamente son sus principales. Sus secundarios, los secundarios de sus secundarios y así sucesivamente son sus descendientes. Una entidad raíz y todos sus descendientes pertenecen al mismo grupo de entidad. La secuencia de entidades que comienza con una entidad raíz y sigue de superior a secundario, hasta llegar a determinada entidad, constituye la ruta de principales de esa entidad. La clave completa que identifica a una entidad consta de una secuencia de pares de grupo de similares-identificador que especifican su ruta de principales y terminan con la entidad en sí:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

En el caso de una entidad raíz, la ruta principal está vacía, y la clave consta solo del grupo de similares y del identificador de la entidad:

[Person:GreatGrandpa]

Este concepto se ilustra en el diagrama siguiente:

Muestra la relación de la entidad raíz con las entidades secundarias del grupo de entidad

Para designar la entidad principal respecto de una secundaria, proporciona la clave de la entidad principal como argumento al constructor Entity() cuando creas la secundaria. Para obtener la clave, llama el método getKey() de la entidad principal:

Entity employee = new Entity("Employee");
datastore.put(employee);

Entity address = new Entity("Address", employee.getKey());
datastore.put(address);

Si la entidad nueva también tiene un nombre de clave, proporciona el nombre de clave como el segundo argumento al constructor Entity() y la clave de la entidad principal como tercer argumento:

Entity address = new Entity("Address", "addr1", employee.getKey());

Transacciones y grupos de entidad

Cada intento de crear, actualizar o borrar una entidad ocurre en el contexto de una transacción. Una transacción puede incluir una cantidad indeterminada de las operaciones mencionadas. Para mantener la coherencia de los datos, la transacción garantiza que todas las operaciones que contiene se apliquen a Cloud Datastore como una unidad o que, si alguna de las operaciones falla, no se aplique ninguna de ellas. Además, todas las lecturas de coherencia sólida (consultas principales o gets) que se realizan dentro de la misma transacción ven una sola instantánea coherente de los datos.

Como se mencionó antes, un grupo de entidad es un conjunto de entidades conectadas de manera ascendente a un elemento raíz común. La organización de los datos en grupos de entidad puede limitar las transacciones que se pueden realizar:

  • Todos los datos a los que accede una transacción deben estar contenidos en 25 grupos de entidad como máximo.
  • Si quieres usar consultas dentro de una transacción, los datos deben estar organizados en grupos de entidad de manera tal que puedas especificar los filtros principales que coincidan con los datos correctos.
  • Existe un límite de capacidad de procesamiento de alrededor de una transacción por segundo dentro de un solo grupo de entidad. Esta limitación existe debido a que Cloud Datastore realiza una replicación síncrona y sin instancia maestra de cada grupo de entidad en una zona geográfica amplia para brindar alta confiabilidad y tolerancia a errores.

En un gran número de aplicaciones, es aceptable usar coherencia eventual (en otras palabras, consultas no principales que abarcan varios grupos de entidad, que algunas veces pueden mostrar datos inactivos) cuando se obtiene una visualización amplia de datos no relacionados y, luego, usar coherencia sólida (una consulta de entidad principal, o un get de una sola entidad) cuando se visualiza o edita un solo conjunto de datos altamente relacionados. En tales aplicaciones, suele ser una buena estrategia usar un grupo de entidad separado para cada conjunto de datos altamente relacionados. Para obtener más información, consulta Estructuración para coherencia sólida.

Tipos de valores y propiedades

Los valores de datos asociados con una entidad constan de una o más propiedades. Cada propiedad tiene un nombre y uno o más valores. Una propiedad puede tener valores de más de un tipo, y dos entidades pueden tener valores de diferentes tipos para la misma propiedad. Las propiedades pueden estar indexadas o no (las consultas que ordenan o filtran en una propiedad P ignorarán las entidades en las que P no está indexada). Una entidad puede tener como máximo 20,000 propiedades indexadas.

Se admiten los tipos de valor siguientes:

Tipo de valor Tipo(s) de Java Ordenar por Notas
Número entero short
int
long
java.lang.Short
java.lang.Integer
java.lang.Long
Numérico Se almacena como un número entero largo, luego se convierte al tipo de campo

Exceso de valores fuera de rango
Número de coma flotante float
double
java.lang.Float
java.lang.Double
Numérico Doble precisión de 64 bits,
IEEE 754
Booleano boolean
java.lang.Boolean
false<true
String de texto (corta) java.lang.String Unicode Hasta 1,500 bytes

Valores superiores a 1,500 bytes arrojan IllegalArgumentException
String de texto (larga) com.google.appengine.api.datastore.Text Ninguno Hasta 1 megabyte

No indexada
String de bytes (corta) com.google.appengine.api.datastore.ShortBlob Orden de bytes Hasta 1,500 bytes

Valores más largos que 1,500 bytes arrojan IllegalArgumentException
String de bytes (larga) com.google.appengine.api.datastore.Blob Ninguno Hasta 1 megabyte

No indexada
Fecha y hora java.util.Date Cronológico
Punto geográfico com.google.appengine.api.datastore.GeoPt Por latitud,
luego longitud
Dirección postal com.google.appengine.api.datastore.PostalAddress Unicode
Número de teléfono com.google.appengine.api.datastore.PhoneNumber Unicode
Dirección de correo electrónico com.google.appengine.api.datastore.Email Unicode
Usuario de Cuentas de Google com.google.appengine.api.users.User Dirección de correo electrónico
en orden de Unicode
Controlador de mensajería instantánea com.google.appengine.api.datastore.IMHandle Unicode
Vínculo com.google.appengine.api.datastore.Link Unicode
Categoría com.google.appengine.api.datastore.Category Unicode
Calificación com.google.appengine.api.datastore.Rating Numérico
Clave de Cloud Datastore com.google.appengine.api.datastore.Key
o el objeto referenciado (como secundario)
Por elementos de ruta
(tipo, identificador,
tipo, identificador…)
Hasta 1,500 bytes

Valores más largos que 1,500 bytes arrojan IllegalArgumentException
Clave de Blobstore com.google.appengine.api.blobstore.BlobKey Orden de bytes
Entidad incorporada com.google.appengine.api.datastore.EmbeddedEntity Ninguno No indexada
Nulo null Ninguno

Importante: Recomendamos que evites almacenar users.User como un valor de propiedad, porque incluye la dirección de correo electrónico junto con el ID único. Si un usuario cambia su dirección de correo electrónico y comparas el user.User almacenado anterior con el valor nuevo de user.User, no coincidirán. En su lugar, usa el valor de ID de usuario User como el identificador único y estable del usuario.

Para strings de texto y datos binarios sin codificación (strings de bytes), Cloud Datastore es compatible con dos tipos de valores:

  • Se indexan strings cortas (hasta 1,500 bytes) y se pueden usar en condiciones de filtro de consulta y órdenes de clasificación.
  • Strings largas (de hasta 1 megabyte), que no se indexan y no pueden usarse en filtros de consulta ni órdenes de clasificación
Nota: El tipo de string de bytes larga se denomina Blob en la API de Cloud Datastore. Este tipo no está relacionado con los blobs que se usan en la API de Blobstore.

Cuando una consulta incluye una propiedad con valores de varios tipos, Cloud Datastore usa el siguiente orden determinista en función de las representaciones internas:

  1. Valores nulos
  2. Números de coma fija
    • Números enteros
    • fechas y horarios
    • calificaciones
  3. Valores booleanos
  4. Secuencias de bytes
    • String de bytes
    • String de Unicode
    • Claves de Blobstore
  5. Números de coma flotante
  6. Puntos geográficos
  7. Usuarios de Cuentas de Google
  8. Claves de Cloud Datastore

Debido a que las strings de texto largas, las strings de bytes largas y las entidades incorporadas no se indexan, no tienen orden definido.

Trabajar con entidades

Las aplicaciones pueden utilizar la API de Cloud Datastore para crear, recuperar, actualizar y borrar entidades. Si la aplicación conoce la clave completa para una entidad (o si puede obtenerla de su tipo, identificador o clave superior), puede usar la clave para operar directamente en la entidad. Una aplicación también puede obtener la clave de una entidad como resultado de una consulta de Cloud Datastore; consulta la página Consultas de Datastore para obtener más información.

La API de Datastore para Java usa métodos de la interfaz de DatastoreService con el objetivo de operar en entidades. A fin de obtener un objeto DatastoreService, llama al método estático DatastoreServiceFactory.getDatastoreService() de la siguiente manera:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Crea una entidad

Para crear una entidad nueva, crea una instancia de la clase Entity y proporciona el tipo de entidad como un argumento del constructor Entity().

Después de propagar las propiedades de la entidad, de ser necesario, debes pasarla como un argumento al método DatastoreService.put() para guardarla en el almacén de datos. A fin de especificar el nombre clave de la entidad, pásala como segundo argumento al constructor:

Entity employee = new Entity("Employee", "asalieri");
// Set the entity properties.
// ...
datastore.put(employee);

Si no proporcionas un nombre de clave, Cloud Datastore generará de forma automática un ID numérico para la clave de la entidad:

Entity employee = new Entity("Employee");
// Set the entity properties.
// ...
datastore.put(employee);

Recupera una entidad

Para recuperar una entidad identificada con una clave determinada, pasa el objeto Key al método DatastoreService.get() de la siguiente manera:

// Key employeeKey = ...;
Entity employee = datastore.get(employeeKey);

Actualiza una entidad

Para actualizar una entidad existente, modifica los atributos del objeto Entidad y, luego, pásalo al método DatastoreService.put(). Los datos del objeto reemplazan a la entidad existente. El objeto completo se envía a Cloud Datastore con cada llamada a put().

Cómo borrar una entidad

Si tienes la clave de una entidad, puedes borrar la entidad con el método DatastoreService.delete() de la siguiente manera:

// Key employeeKey = ...;
datastore.delete(employeeKey);

Propiedades repetidas

Puedes almacenar varios valores dentro de una sola propiedad.

Entity employee = new Entity("Employee");
ArrayList<String> favoriteFruit = new ArrayList<String>();
favoriteFruit.add("Pear");
favoriteFruit.add("Apple");
employee.setProperty("favoriteFruit", favoriteFruit);
datastore.put(employee);

// Sometime later
employee = datastore.get(employee.getKey());
@SuppressWarnings("unchecked") // Cast can't verify generic type.
    ArrayList<String> retrievedFruits = (ArrayList<String>) employee
    .getProperty("favoriteFruit");

Entidades incorporadas

A veces puede resultar práctico incorporar una entidad como una propiedad de otra entidad. Esto puede ser útil, por ejemplo, para crear una estructura jerárquica de valores de propiedad dentro de una entidad. La clase EmbeddedEntity de Java te permite realizar lo siguiente:

// Entity employee = ...;
EmbeddedEntity embeddedContactInfo = new EmbeddedEntity();

embeddedContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678");
embeddedContactInfo.setProperty("phoneNumber", "555-555-5555");
embeddedContactInfo.setProperty("emailAddress", "test@example.com");

employee.setProperty("contactInfo", embeddedContactInfo);

Cuando una entidad incorporada se incluye en los índices, puedes realizar consultas sobre subpropiedades. Si excluyes una entidad incorporada de la indexación, también se excluirán todas las subpropiedades. Opcionalmente, puedes asociar una clave con una entidad incorporada, pero (a diferencia de una entidad completa) no es necesaria la clave e, incluso si existe, no se puede usar para recuperar la entidad.

En lugar de propagar de forma manual las propiedades de la entidad incorporada, puedes usar el método setPropertiesFrom() para copiarlas desde una entidad existente de la siguiente manera:

// Entity employee = ...;
// Entity contactInfo = ...;
EmbeddedEntity embeddedContactInfo = new EmbeddedEntity();

embeddedContactInfo.setKey(contactInfo.getKey()); // Optional, used so we can recover original.
embeddedContactInfo.setPropertiesFrom(contactInfo);

employee.setProperty("contactInfo", embeddedContactInfo);

Puedes usar más adelante el mismo método para recuperar la entidad original de la entidad incorporada:

Entity employee = datastore.get(employeeKey);
EmbeddedEntity embeddedContactInfo = (EmbeddedEntity) employee.getProperty("contactInfo");

Key infoKey = embeddedContactInfo.getKey();
Entity contactInfo = new Entity(infoKey);
contactInfo.setPropertiesFrom(embeddedContactInfo);

Operaciones por lotes

Los métodos de DatastoreService put(), get() y delete() (y sus equivalentes de AsyncDatastoreService) tienen versiones de lote que aceptan un objeto iterable (de clase Entity en put(), Key en get() y delete()) y lo usan para operar en varias entidades en una sola llamada de Cloud Datastore de la siguiente manera:

Entity employee1 = new Entity("Employee");
Entity employee2 = new Entity("Employee");
Entity employee3 = new Entity("Employee");
// ...

List<Entity> employees = Arrays.asList(employee1, employee2, employee3);
datastore.put(employees);

Estas operaciones de lote agrupan todas las entidades o claves por grupo de entidad y luego realizan la operación solicitada en cada grupo de entidad en paralelo. Hacer llamadas por lotes es más rápido que hacer llamadas por separado para cada entidad individual porque estas generan sobrecarga para solo una llamada de servicio. Si se involucran varios grupos de entidad, el trabajo de todos ellos se realiza en paralelo del lado del servidor.

Genera claves

Las aplicaciones pueden usar la clase KeyFactory con el fin de crear un objeto Key para una entidad a partir de componentes conocidos, como el identificador y el tipo de entidad. Para una entidad sin superior, pasa el tipo y el identificador (ya sea un ID numérico o una string de nombre de clave) al método estático KeyFactory.createKey() a fin de crear la clave. Los ejemplos siguientes crean una clave para una entidad del tipo Person con nombre de clave "GreatGrandpa" o ID numérico 74219:

Key k1 = KeyFactory.createKey("Person", "GreatGrandpa");
Key k2 = KeyFactory.createKey("Person", 74219);

Si la clave incluye un componente de ruta de acceso, puedes usar la clase auxiliar KeyFactory.Builder para compilar la ruta. El método addChild de esta clase agrega una sola entidad a la ruta de acceso y muestra el compilador, para que puedas encadenar una serie de llamadas a partir de la entidad raíz, y así crear la ruta de acceso de una entidad a la vez. Después de compilar toda la ruta de acceso, llama a getKey para recuperar la clave resultante de la siguiente manera:

Key k =
    new KeyFactory.Builder("Person", "GreatGrandpa")
        .addChild("Person", "Grandpa")
        .addChild("Person", "Dad")
        .addChild("Person", "Me")
        .getKey();

La clase KeyFactory también incluye los métodos estáticos keyToString y stringToKey para la conversión entre claves y sus representaciones de strings de la siguiente manera:

String personKeyStr = KeyFactory.keyToString(k);

// Some time later (for example, after using personKeyStr in a link).
Key personKey = KeyFactory.stringToKey(personKeyStr);
Entity person = datastore.get(personKey);

La representación de string de una clave es "segura para la Web": no contiene caracteres que se consideren especiales en HTML o en URL.

Usar una lista vacía

Históricamente, Cloud Datastore no tenía una representación para una propiedad que represente una lista vacía. El SDK de Java solucionó esto almacenando colecciones vacías como valores nulos, por lo que no hay manera de distinguir entre valores nulos y listas vacías. Para mantener la compatibilidad inversa, este sigue siendo el comportamiento predeterminado, que se resume de la siguiente manera:

  • Las propiedades nulas se escriben como nulas en Cloud Datastore.
  • Las colecciones vacías se escriben como nulas en Cloud Datastore.
  • Un nulo se lee como nulo desde Cloud Datastore.
  • Una colección vacía se lee como nula.

Sin embargo, si modificas el comportamiento predeterminado, el SDK para Java será compatible con el almacenamiento de listas vacías. Recomendamos que consideres las consecuencias de modificar el comportamiento predeterminado de tu aplicación y luego actives la asistencia para listas vacías.

Para modificar el comportamiento predeterminado y así poder usar listas vacías, configura la propiedad DATASTORE_EMPTY_LIST_SUPPORT durante la inicialización de tu aplicación de la siguiente manera:

System.setProperty(DatastoreServiceConfig.DATASTORE_EMPTY_LIST_SUPPORT, Boolean.TRUE.toString());

Con esta propiedad configurada en true como se muestra más arriba:

  • Las propiedades nulas se escriben como nulas en Cloud Datastore.
  • Las colecciones vacías se escriben como lista vacía en Cloud Datastore.
  • Un nulo se lee como nulo desde Cloud Datastore.
  • Cuando se lee desde Cloud Datastore, se muestra una lista vacía como una Colección vacía.