Nota: todos los ejemplos se pueden encontrar aquí.
Hoy lanzamos una Developer Preview (DP) muy especial de una nueva funcionalidad de la próxima versión de Couchbase Server llamada Subdocumentojunto con nuestro Couchbase Server .NET SDK 2.2.6 normal. La API Sub-documento es una nueva característica de Couchbase Server que está disponible en la Developer Preview de Couchbase Server 4.5.
Si recuerdas, en Couchbase todas las mutaciones de documentos son atómicas e involucran a todo el documento. Si sólo quieres cambiar un único campo y luego haces una actualización, todo el documento en el servidor Couchbase es copiado por la nueva revisión. El problema es que si el documento es grande o la red lenta (o ambos), entonces se desperdician muchos recursos enviando datos que no han sido modificados. Una solución mejor y más eficaz sería enviar sólo la parte del documento o el valor que se ha modificado. Esencialmente, eso es lo que se consigue con la API de subdocumento; cuando se actualiza un elemento o se elimina un elemento de un documento, sólo se envía la porción del documento que ha sido modificada. ruta del fragmento que se va a mutar se envía por cable y sólo se modifica esa parte del documento.
La API admite varias operaciones diferentes, desde mutaciones en elementos anidados individuales o sub-a modificaciones de matrices y diccionarios. También se admiten operaciones de contador, así como operaciones de recuperación de fragmentos JSON incrustados.
La API se expone a través de una interfaz fluida que permite añadir múltiples operaciones y luego ejecutarlas contra el documento de forma atómica. Existen dos "constructores" diferentes: un constructor para operaciones de mutación y un constructor para lecturas o "búsquedas" y para comprobar si un elemento existe en una ruta determinada.
IMPORTANTE: ¡¡¡La API de subdocumentos es una versión preliminar para desarrolladores!!!
Tenga en cuenta que se trata de una primera versión de la API de subdocumentos y que no se ha sometido a las comprobaciones y balances habituales a los que se someten las API antes de su publicación. Además, en función de los comentarios de los usuarios, las interfaces públicas pueden cambiar en versiones posteriores, por lo que no se recomienda utilizar la API de subdocumentos en producción... todavía. No obstante, el resto de la versión 2.2.6 ha sido probado y está listo para su uso en producción.
Requisito previo: Couchbase Server 4.5.0 Developer Preview
Para poder seguir los ejemplos que se presentan a continuación, deberá descargar e instalar Vista previa para desarrolladores de Couchbase Server 4.5. ¡Vamos, hazlo ya!
Subdocumento API DP Descripción general
En los siguientes ejemplos se utilizará un documento con el id "cachorro" y tendrá el siguiente aspecto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "tipo": "perro", "raza": "Pitbull/Chihuahua", "nombre": "Cachorro", "juguetes": [ "chillón", "pelota", "zapato" ], "propietario": { "tipo": "sirviente", "nombre": "Don Knotts", "edad": 63 }, "atributos": { "pulgas": verdadero, "color": "blanco", "eyeColor": "marrón", "edad": 5, "sucio": verdadero, "sexo": "mujer" } } |
Todos los ejemplos están disponibles en Github para que puedas clonar el proyecto y jugar con la API.
MutateInBuilder y LookupInBuilder
Como se ha mencionado anteriormente, la API de subdocumentos ofrece dos nuevos tipos que utilizan un patrón constructor a través de una interfaz fluida para encadenar varias operaciones en un documento. Ambos objetos se crean llamando a MutateIn
o BúsquedaEn
en un CouchbaseBucket
e introduciendo la clave o id del documento con el que se está trabajando:
1 2 3 4 5 6 7 8 9 10 11 12 |
//Inicializar el cluster helper con la configuración por defecto - es decir, localhost ClusterHelper.Inicializar(); var cubo = ClusterHelper.GetBucket("por defecto"); //crear un constructor de mutaciones para el documento "thekey" var mutar = cubo.MutateIn("la llave"); //crear un constructor de búsqueda para el documento "thekey2" var buscar = cubo.BúsquedaEn("thekey2"); ClusterHelper.Cerrar(); |
Una vez que tenga el objeto constructor, puede encadenar una serie de operaciones para ejecutarlas contra el documento, por ejemplo:
1 2 3 4 5 6 |
var constructor = cubo.BúsquedaEn(id). Visite("tipo"). Visite("nombre"). Visite("propietario"). Existe("no encontrado"); |
A continuación, puede enviar todas las operaciones al servidor en un único lote:
1 2 |
var fragmento = constructor.Ejecute(); |
Y, a continuación, compruebe el resultado de una operación para la ruta tipo
:
1 2 3 4 5 6 |
si (fragmento.OpStatus("tipo") == ResponseStatus.Éxito) { cadena formato = "Ruta='{0}' Valor='{1}'"; Consola.WriteLine(formato, "tipo", fragmento.Contenido("tipo")); } |
El IDocumentFragment
Nombre | Descripción |
---|---|
Contenido(...) | Obtiene el contenido de una ruta o índice dados. |
Existe(...) | Devuelve true si hay un resultado para una ruta o índice dados. |
Contar() | El recuento de las operaciones en curso mantenidas por el constructor. |
OpStatus(...) | En ResponseStatus de una operación en un índice o ruta determinados. |
Estado | En ResponseStatus para toda la operación múltiple. |
Éxito | Verdadero si la operación múltiple completa tiene éxito. |
Además de estas propiedades o métodos, existen todas las demás propiedades heredadas de OperationResult
que es la respuesta estándar de una operación K/V: Upsert, Remove, Replace, etc.
Tratamiento de errores
Al enviar múltiples mutaciones, si una de ellas falla, falla toda la solicitud multioperación, lo que permite una semántica transaccional de todo o nada al realizar mutaciones dentro de un mismo documento. Cuando se envían múltiples búsquedas, algunas operaciones pueden tener éxito y otras pueden fallar, intentando el servidor devolver tantos elementos como se hayan solicitado. Si las operaciones fallan, la propiedad Status contendrá una respuesta de error de nivel superior, como por ejemplo SubDocMultiPathFailure,
que es una indicación para profundizar en los resultados de la operación para obtener el error específico. Puede hacerlo iterando llamando al método OpStatus y pasando el índice o la ruta:
1 2 3 4 5 6 7 8 9 10 11 12 |
var constructor = cubo.BúsquedaEn(id). Visite("tipo"). Visite("algún camino que no existe"). Visite("propietario"); var fragmento = constructor.Ejecute(); Consola.WriteLine("Error genérico: {0}{1}Error específico: {2}", fragmento.Estado, Medio ambiente.NewLine, fragmento.OpStatus(1)); Consola.WriteLine("Error genérico: {0}{1}Error específico: {2}", fragmento.Estado, Medio ambiente.NewLine, fragmento.OpStatus("algún camino que no existe")); |
En este caso, como la ruta no existía dentro del documento, el error específico devuelto fue SubDocPathNotFound
. Hay muchas combinaciones diferentes de errores dependiendo del tipo de constructor y la condición del error - esta es una breve introducción y debería ser adecuada para empezar con la API.
Ejemplos de LookupInBuilder
LookUpInBuilder admite dos operaciones: buscar un valor por ruta y comprobar la existencia de un valor en una ruta determinada.
Consíguelo:
Busquemos el propietario
fragmento por ruta
:
1 2 3 4 5 6 |
var constructor = cubo.BúsquedaEn(id). Visite("propietario"). Ejecute(); var propietario = constructor.Contenido("propietario"); |
La salida es:
1 2 3 4 5 6 |
{ "tipo": "sirviente", "nombre": "Don Knotts", "edad": 63 } |
Existe:
Comprobemos si el propietario
existe una ruta:
1 2 3 4 5 6 |
var constructor = cubo.BúsquedaEn(id). Existe("propietario"). Ejecute(); var encontrado = constructor.Contenido("propietario"); |
El resultado es verdadero
el camino propietario
existe efectivamente en el documento.
MutateInBuilder
MutateInBuilder ofrece una serie de métodos que permiten realizar mutaciones en valores escalares, diccionarios y matrices, así como operaciones de contador atómico.
Insertar:
Insertar añade un valor a un diccionario permitiendo opcionalmente que se añada el elemento que lo contiene (el propio diccionario):
1 2 3 4 |
var constructor = cubo.MutateIn(id). Inserte("attributes.hairLength", "corto"). Ejecute(); |
El diccionario de atributos del documento tendrá ahora este aspecto:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... "atributos": { "pulgas": verdadero, "color": "blanco", "eyeColor": "marrón", "edad": 5, "sucio": verdadero, "sexo": "mujer", "hairLength": "corto" } ... |
Ahora bien, si el elemento padre no existe, la función crearPadres
para crear el elemento padre. Esto es true por defecto, así que no tienes que hacer nada - pasa false si quieres fallar si el elemento padre no existe:
1 2 3 4 |
var constructor = cubo.MutateIn(id). Inserte("anewattribute.withakey", "algún valor"). Ejecute(); |
Esto creará el nuevo atributo llamado unnuevoatributo
en el documento y añadir una única clave llamada conakey
con un valor de algún valor
.
1 2 3 4 5 6 7 |
... "unnuevoatributo": { "conakey": "algún valor" } ... |
Ahora, si pasamos falso
para crearPadres
y el atributo padre no existiera, la mutación múltiple fallaría con un estado de respuesta de nivel superior de SubDocMultiPathFailure
y el error específico sería SubDocPathNotFound
.
Upsert
Upsert añadirá o sustituirá una entrada del diccionario existente. El uso es exactamente el mismo que Inserte
con la excepción de que el nombre del método es Upsert
.
Eliminar
Eliminar
eliminará un elemento en una ruta dada. Como ejemplo, eliminaremos el nombre del propietario del documento anterior:
1 2 3 4 |
var fragmento = cubo.MutateIn(id). Eliminar("nombre.propietario"). Ejecute(); |
Y el documento después de llamado Eliminar
:
1 2 3 4 5 6 7 8 |
... "propietario": { "tipo": "sirviente", "edad": 63 }, ... |
Sustituir
Reemplazar intercambiará el valor del elemento en una ruta dada, fallando si la ruta no existe:
1 2 3 4 |
var fragmento = cubo.MutateIn(id). Sustituir("propietario", nuevo { Amante de los gatos=verdadero, CatName="celia"}). Ejecute(); |
El documento tendrá ahora un valor diferente para "propietario":
1 2 3 4 5 6 7 8 |
... "propietario": { "catLover": verdadero, "catName": "celia" }, ... |
PushBack
Añade un valor a la parte posterior de un array añadiendo opcionalmente el elemento padre (el propio elemento del array) si no existe.
1 2 3 4 |
var fragmento = cubo.MutateIn(id). PushBack(ruta, valor, falso). Ejecute(); |
En juguetes
del documento tiene ahora el valor "zapatilla" en el último ordinal:
1 2 3 4 5 6 7 8 9 10 |
... "juguetes": [ "chillón", "pelota", "zapato", "zapatilla" ], ... |
PushFront
Añade un valor al principio de un array añadiendo opcionalmente el elemento padre (el propio array) si no existe:
1 2 3 4 |
var fragmento = cubo.MutateIn(id). PushFront(ruta, valor, falso). Ejecute(); |
En juguetes
tiene ahora el valor "zapatilla" en su primer ordinal:
1 2 3 4 5 6 7 8 9 10 |
... "juguetes": [ "zapatilla", "chillón", "pelota", "zapato" ], ... |
ArrayInsert
Inserta un valor en un array en un índice dado:
1 2 3 4 |
var fragmento = cubo.MutateIn(id). ArrayInsert("juguetes[2]", "zapatilla"). Ejecute(); |
En juguetes
tiene ahora el valor "zapatilla" en su ordinal 3 (índice 2):
1 2 3 4 5 6 7 8 |
"juguetes": [ "chillón", "pelota", "zapatilla", "zapato" ], |
AñadirUnico
Inserta un valor en un array, fallando si existe (el valor debe ser único dentro del array):
1 2 3 4 |
var fragmento = cubo.MutateIn(id). AñadirUnico("juguetes", "zapato"). Ejecute(); |
Dado que el valor "shoe" ya existe en el documento original de juguetes
esto fallará con el siguiente estado:
1 2 |
SubDocPathExists |
Tenga en cuenta que este método sólo permite insertar primitivas JSON: cadenas, números y valores especiales para true, false o null. La razón es que no hay forma de comparar la unicidad sin descender a cada objeto JSON y comparar los elementos elemento por elemento.
Contador
Añade el delta especificado a un valor existente, creando el elemento si no existe y poniendo por defecto el valor y el delta a 0. Si el delta es negativo, el valor del elemento se reducirá en el delta dado.
1 2 3 4 |
var fragmento = cubo.MutateIn(id). Contador("me gusta", 1). Ejecute(); |
Dado que el elemento no existe, se creará y se establecerá en uno (1). El documento tendrá ahora este aspecto:
1 2 3 4 5 |
... ], "me gusta": 1 } |
Si pasamos un uno negativo (-1), entonces el contador de le gusta
volverá a cero (0):
1 2 3 4 |
var fragmento = cubo.MutateIn(id). Contador("me gusta", -1). Ejecute(); |
Y el documento JSON tendrá ahora este aspecto:
1 2 3 4 5 |
... ], "me gusta": 0 } |
Notas de la versión 2.2.6
Error
- [NCBC-981] - Cuando se define FQDN para la instancia de Couchbase, SSL falla.
- [NCBC-1074] - La solicitud de vista se bloquea indefinidamente si se espera de forma sincrónica
- [NCBC-1083] - PoolConfiguration sigue utilizando la configuración predeterminada cuando se anula
- [NCBC-1084] - ConfigurationSection ignora UseSsl
- [NCBC-1086] - GetAndLock no devuelve el estado Bloqueado pero se agota el tiempo de espera cuando el documento está bloqueado
Mejora
- [NCBC-1070] - Hacer que QueryRequest no dependa de JSON.NET
- [NCBC-1082] - Añadir soporte para sortCount en QueryResult.Metrics
- [NCBC-1085] - Retrollamada de espera para que no se bloquee el subproceso de ejecución
- [NCBC-1090] - Fix " No se puede esperar en el cuerpo de una cláusula catch" en SSL IO
Nueva función
- [NCBC-998] - Incluir soporte para Subdocument API - Parte 1 Multi-comandos DP