Las colecciones son una nueva característica de Couchbase 6.5. Te permiten agrupar documentos similares dentro de cada bucket, igual que las tablas en las bases de datos relacionales agrupan registros similares dentro de cada base de datos. Las colecciones estarán totalmente soportadas en Couchbase 7.0, pero puedes probarlas ahora mismo en la versión 6.5 de Couchbase como una característica Developer Preview. La Aplicación Demo ya las utiliza.
¿Qué son las colecciones?
Si vienes del mundo de las bases de datos relacionales, puedes pensar en las colecciones como si fueran tablas. Todos los documentos de una colección couchbase deben ser del mismo tipo, del mismo modo que todos los registros de una tabla relacional son del mismo tipo. Puede haber una tabla "cliente" o una tabla "producto" en un esquema relacional; del mismo modo, puede haber una colección "cliente" en un bucket de Couchbase.
En versiones anteriores de Couchbase, los datos se organizaban así:
- Grupo
- Cubo
- Documento
- Cubo
En Couchbase 6.5, hay dos capas más, así:
- Grupo
- Cubo
- Alcance
- Colección
- Documento
- Colección
- Alcance
- Cubo
¿Qué utilidad tienen las colecciones?
Las colecciones son el nivel más bajo de organización documental y contienen directamente documentos. Son útiles porque permiten agrupar los documentos con mayor precisión que antes. En lugar de volcar todos los tipos de documentos (productos, pedidos, clientes) en un único bucket y distinguirlos por un campo de tipo, puede crear una colección para cada tipo. Y cuando se consulta, se puede consultar la colección, no sólo todo el bucket. También podrá controlar el acceso a nivel de colección.
Los ámbitos son el nivel de organización por encima de las colecciones. Los ámbitos contienen colecciones y las colecciones contienen documentos. Hay diferentes formas de usar los ámbitos, dependiendo de para qué se esté usando el cluster de Couchbase. Si está soportando muchas aplicaciones internas diferentes para una compañía, cada aplicación debería tener su propio ámbito. Si el cluster está siendo usado para servir a muchas organizaciones cliente, cada una ejecutando su propia copia de una aplicación, cada copia debería tener un scope propio. Del mismo modo, si un clúster está siendo utilizado por grupos de desarrollo, tal vez para pruebas, la unidad de asignación debe ser un ámbito. En cada caso, el propietario puede crear las colecciones que desee en el ámbito que se le asigne.
Los ámbitos deben ser únicos dentro de sus cubos, y las colecciones deben ser únicas dentro de sus ámbitos. Así, el bucket "default" podría contener dos ámbitos "dev" y "prod", cada uno con sus propias colecciones "products" y "customers".
Uso de las colecciones
Puedes ver colecciones y ámbitos siendo usados en la última versión de la Aplicación Demo de Couchbase, aquí:
https://github.com/couchbaselabs/try-cb-java/tree/6.5.0-branch
Esta aplicación utiliza un bucket "travel-sample" ya existente para permitir al usuario buscar vuelos y hoteles, pero almacena sus propios datos de usuario y reservas en colecciones. La estructura utilizada es la siguiente:
- Cubo: por defecto
- Alcance: larson-travel
- Colección: usuarios
- Colección: vuelos
- Alcance: larson-travel
Cuando el usuario crea una cuenta, se crea un documento en la colección "usuarios". Cuando reservan vuelos, se crean documentos en la colección "vuelos", y se hace referencia a ellos en el documento del usuario en la colección "usuarios".
Este diseño permite que varias aplicaciones compartan el mismo bucket. Si tuviéramos una segunda instancia de la aplicación de demostración, utilizada por otra agencia de viajes, podríamos crear otro ámbito (con sus propias colecciones "users" y "flights") y dirigir la segunda instancia a este ámbito actualizando su archivo application.properties. Las dos instancias funcionarían una al lado de la otra, sin interferir entre sí.
Código de ejemplo
Para empezar, en el archivo application.properties se nombran el bucket y el scope que contienen la información sobre usuarios y reservas:
1 2 |
almacenamiento.clientorg.cubo=por defecto almacenamiento.clientorg.alcance=larson-viaje |
Estos valores de configuración se recogen en el archivo Database.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Configuración público clase Base de datos { ... @Valor("${storage.clientorg.bucket}") privado Cadena clientOrgBucket; @Valor("${storage.clientorg.scope}") privado Cadena clientOrgScope; ... público Cubo clientOrgBucket() { devolver loginCluster().cubo(clientOrgBucket); } público @Bean Alcance clientOrgScope() { devolver clientOrgBucket().alcance(clientOrgScope); } } |
En User.java, vemos cómo se registra un nuevo vuelo para el usuario. Se pasa el bean scope, creado anteriormente. El nombre de usuario es el id el nombre con el que el usuario ha iniciado sesión.
1 2 |
público Resultado<Mapa<Cadena, Objeto>> registerFlightForUser(final Alcance alcance, final Cadena nombre de usuario, final JsonArray nuevosVuelos) { |
El id del documento de usuario es el nombre de usuario del usuario. La aplicación sabe que debe obtenerlo de la colección "users", en la colección utilizada por la aplicación.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Cadena userId = nombre de usuario; Colección usuariosColección = alcance.colección(NOMBRE_COLECCION_USUARIOS); Colección vuelosColección = alcance.colección(NOMBRE_COLECCIÓN_VUELOS); Opcional<GetResult> userDataFetch = usuariosColección.consiga(userId); si (!userDataFetch.isPresent()) { tirar nuevo IllegalStateException(); } JsonObject userData = userDataFetch.consiga().contentAsObject(); si (nuevosVuelos == null) { tirar nuevo IllegalArgumentException("No hay vuelos en carga útil"); } JsonArray añadido = JsonArray.vacío(); |
Los vuelos que el usuario ha reservado se almacenan en el documento de usuario, en un array llamado "vuelos".
1 2 3 4 |
JsonArray allBookedFlights = userData.getArray("vuelos"); si(allBookedFlights == null) { allBookedFlights = JsonArray.crear(); } |
Añadimos los nuevos vuelos a los ya existentes.
1 2 3 4 5 6 7 8 9 10 11 |
para (Objeto nuevoVuelo : nuevosVuelos) { checkFlight(nuevoVuelo); JsonObject t = ((JsonObject) nuevoVuelo); t.poner("bookedon", "try-cb-java"); Cadena flightId = UUID.randomUUID().toString(); vuelosColección.insertar(flightId, t); allBookedFlights.añada(flightId); añadido.añada(t); } userData.poner("vuelos", allBookedFlights); |
A continuación, almacenamos la nueva versión del documento de usuario.
1 2 3 4 5 6 7 8 |
usuariosColección.upsert(userId, userData); JsonObject responseData = JsonObject.crear() .poner("añadido", añadido); devolver Resultado.de(responseData.toMap(), "Vuelo reservado en documento Couchbase" + userId); } |
Justo debajo, vemos cómo se recuperan los vuelos de un usuario.
1 2 |
público Lista<Mapa<Cadena, Objeto>> getFlightsForUser(final Alcance alcance, final Cadena nombre de usuario) { |
Obtener el documento de usuario de la colección "usuarios".
1 2 3 4 5 6 |
Colección usuarios = alcance.colección(NOMBRE_COLECCION_USUARIOS); Opcional<GetResult> doc = usuarios.consiga(nombre de usuario); si (!doc.isPresent()) { devolver Colecciones.emptyList(); } JsonObject datos = doc.consiga().contentAsObject(); |
Obtiene la matriz "flights" del documento de usuario. Contiene una lista de identificadores de vuelo.
1 2 3 4 |
JsonArray vuelos = datos.getArray("vuelos"); si (vuelos == null) { devolver Colecciones.emptyList(); } |
Recuperar cada documento de vuelo de la colección "vuelos" por ID.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// El array "flights" contiene los ids de los vuelos. Conviértelos en objetos reales. Colección vuelosColección = alcance.colección(NOMBRE_COLECCIÓN_VUELOS); Lista<Mapa<Cadena, Objeto>> resultados = nuevo ArrayList<Mapa<Cadena, Objeto>>(); para (int i = 0; i < vuelos.talla(); i++) { Cadena flightId = vuelos.getString(i); Opcional<GetResult> res = vuelosColección.consiga(flightId); si (!res.isPresent()) { tirar nuevo RuntimeException("No se puede recuperar el identificador de vuelo" + flightId); } Mapa<Cadena, Objeto> vuelo = res.consiga().contentAsObject().toMap(); resultados.añada(vuelo); } devolver resultados; } |
Cambios respecto al código anterior
El código para trabajar con usuarios y sus vuelos era bastante diferente en la versión anterior, que no utilizaba colecciones. Allí, los vuelos reservados se almacenaban directamente en el documento de usuario. El documento de usuario se almacenaba directamente en la tabla "travel-sample". Este es el código original de la función registerFlightForUser().
1 2 |
público Resultado<Mapa<Cadena, Objeto>> registerFlightForUser(final Cubo cubo, final Cadena nombre de usuario, final JsonArray nuevosVuelos) { |
Fíjese en el uso de un prefijo para marcar el tipo de documento. Esto no es necesario una vez que las colecciones están disponibles.
1 2 3 4 5 6 7 8 9 10 |
JsonDocument userData = cubo.consiga("usuario::" + nombre de usuario); si (userData == null) { tirar nuevo IllegalStateException(); } si (nuevosVuelos == null) { tirar nuevo IllegalArgumentException("No hay vuelos en carga útil"); } JsonArray añadido = JsonArray.vacío(); |
Recuperamos la matriz de vuelos, que ya está en el documento.
1 2 3 4 5 6 7 8 9 |
JsonArray allBookedFlights = userData.contenido().getArray("vuelos"); si(allBookedFlights == null) { allBookedFlights = JsonArray.crear(); } para (Objeto nuevoVuelo : nuevosVuelos) { checkFlight(nuevoVuelo); JsonObject t = ((JsonObject) nuevoVuelo); t.poner("bookedon", "try-cb-java"); |
Añadimos los vuelos a la lista de vuelos reservados.
1 2 3 4 5 |
allBookedFlights.añada(t); añadido.añada(t); } userData.contenido().poner("vuelos", allBookedFlights); |
Y almacenamos el documento de usuario.
1 2 3 4 5 6 7 |
JsonDocument respuesta = cubo.upsert(userData); JsonObject responseData = JsonObject.crear() .poner("añadido", añadido); devolver Resultado.de(responseData.toMap(), "Vuelo reservado en documento Couchbase" + respuesta.id()); } |
Obviamente, separar los vuelos reservados del usuario no es terriblemente convincente en una aplicación de juguete. Pero en una aplicación de producción, en la que estamos almacenando muchos tipos de información sobre cada usuario, tendría sentido almacenar algunos registros fuera del documento de usuario, en particular los que fueran grandes o numerosos o propensos a cambiar con frecuencia.
Documentación sobre ámbitos y colecciones
Para saber más sobre cómo trabajar directamente con ámbitos y colecciones, consulte esta documentaciónque explica la API RESTful para trabajar con ambos, los comandos CLI pertinentes e información sobre las colecciones disponibles en cbstats.
Resumen
Las colecciones y los ámbitos te permiten organizar documentos dentro de un bucket de Couchbase, al igual que las tablas y los esquemas te permiten organizar filas dentro de bases de datos relacionales. La versión actual de Couchbase 6.5 GA proporciona soporte temprano y limitado para colecciones y ámbitos como una característica Developer Preview. Para empezar con colecciones y ámbitos, puedes empezar a trabajar con la Aplicación Demo Java ahora mismo.
Recursos
Descargar
Descargar Couchbase Server 6.5
Documentación
Documentación de Couchbase Collections 6.5
Notas de la versión de Couchbase Server 6.5
Novedades de Couchbase Server 6.5
Blogs
Presentación de Collections - Developer Preview en Couchbase Server 6.5
Hola Johan, esta es una característica muy buena para tener especialmente para las aplicaciones que están implementando multi tenencia sobre la base de la propiedad discriminador de datos. Así que quería saber una cosa que si es posible ejecutar una consulta en múltiples ámbitos? como para algunas consultas, queríamos obtener datos de múltiples ámbitos y para otras consultas, los datos deben volver de un solo ámbito. ¿Es posible en la versión 6.5 de couchbase? Gracias
Hola, Malik. No es posible escribir consultas N1QL contra colecciones en 6.5. En este momento, las consultas N1QL no pueden trabajar contra colecciones, sólo contra cubos, como lo han hecho en el pasado. N1QL para colecciones llegará en 7.0, probablemente en algún momento de 2020.
Una vez que N1QL funcione para colecciones, será posible escribir consultas que abarquen múltiples ámbitos, del mismo modo que ahora es posible escribir consultas N1QL que abarquen múltiples buckets.
Dado que "Todos los documentos de una colección couchbase deben ser del mismo tipo, al igual que todos los registros de una tabla relacional son del mismo tipo", ¿implica que todos los documentos de una colección deben tener los mismos campos?
Gracias