As coleções são um novo recurso do Couchbase 6.5. Elas permitem agrupar documentos semelhantes dentro de cada bucket, assim como as tabelas em bancos de dados relacionais coletam registros semelhantes dentro de cada banco de dados. As coleções terão suporte total no Couchbase 7.0, mas você pode experimentá-las agora mesmo na versão 6.5 do Couchbase como um recurso Developer Preview. O aplicativo de demonstração já as utiliza.
O que são coleções?
Se você vem do mundo dos bancos de dados relacionais, pode pensar nas coleções como tabelas. Todos os documentos de uma coleção do couchbase devem ser do mesmo tipo, assim como todos os registros de uma tabela relacional são do mesmo tipo. Pode haver uma tabela "cliente" ou uma tabela "produto" em um esquema relacional; da mesma forma, pode haver uma coleção "cliente" em um bucket do Couchbase.
Nas versões mais antigas do Couchbase, os dados eram organizados da seguinte forma:
- Aglomerado
- Balde
- Documento
- Balde
No Couchbase 6.5, há mais duas camadas, como esta:
- Aglomerado
- Balde
- Escopo
- Coleção
- Documento
- Coleção
- Escopo
- Balde
Como as coleções são úteis?
As coleções são o nível mais baixo de organização de documentos e contêm diretamente os documentos. Elas são úteis porque permitem que você agrupe documentos com mais precisão do que era possível antes. Em vez de despejar todos os diferentes tipos de documentos (produtos, pedidos, clientes) em um único compartimento e distingui-los por um campo de tipo, você pode criar uma coleção para cada tipo. E, quando fizer uma consulta, você poderá consultar a coleção, e não apenas o bucket inteiro. Eventualmente, você também poderá controlar o acesso no nível da coleção.
Os escopos são o nível de organização acima das coleções. Os escopos contêm coleções e as coleções contêm documentos. Há diferentes maneiras de usar escopos, dependendo da finalidade para a qual o cluster do Couchbase está sendo usado. Se ele estiver oferecendo suporte a muitos aplicativos internos diferentes de uma empresa, cada aplicativo deverá ter um escopo próprio. Se o cluster estiver sendo usado para atender a muitas organizações clientes, cada uma executando sua própria cópia de um aplicativo, cada cópia deverá ter um escopo próprio. Da mesma forma, se um cluster estiver sendo usado por grupos de desenvolvimento, talvez para testes, a unidade de alocação deverá ser um escopo. Em cada caso, o proprietário pode criar as coleções que quiser no escopo que lhe foi atribuído.
Os escopos devem ser exclusivos dentro de seus compartimentos e as coleções devem ser exclusivas dentro de seus escopos. Dessa forma, o bucket "default" poderia conter dois escopos "dev" e "prod", cada um com suas próprias coleções "products" e "customers".
Uso de coleções
Você pode ver as coleções e os escopos sendo usados na versão mais recente do aplicativo de demonstração do Couchbase, aqui:
https://github.com/couchbaselabs/try-cb-java/tree/6.5.0-branch
Esse aplicativo usa um bucket "travel-sample" existente para permitir que o usuário pesquise voos e hotéis, mas armazena seus próprios dados de usuário e reservas em coleções. A estrutura que está sendo usada é a seguinte:
- Balde: padrão
- Escopo: larson-travel
- Coleção: usuários
- Coleção: voos
- Escopo: larson-travel
Quando o usuário cria uma conta, um documento é criado na coleção "users". Quando eles reservam voos, os documentos são criados na coleção "flights" e referenciados no documento do usuário na coleção "users".
Esse design permite que vários aplicativos compartilhem o mesmo bucket. Se tivéssemos uma segunda instância do aplicativo de demonstração, usada por outra agência de viagens, poderíamos simplesmente criar outro escopo (com suas próprias coleções "users" e "flights") e apontar a segunda instância para esse escopo, atualizando seu arquivo application.properties. As duas instâncias operariam lado a lado, sem interferir uma na outra.
Exemplo de código
Para começar, o bucket e o escopo que contêm informações de usuário e reservas são nomeados no arquivo application.properties:
1 2 |
armazenamento.clienteorg.balde=padrão armazenamento.clienteorg.escopo=larson-viagens |
Esses valores de configuração são coletados no arquivo Database.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Configuração público classe Banco de dados { ... @Valor("${storage.clientorg.bucket}") privado Cordas clientOrgBucket; @Valor("${storage.clientorg.scope}") privado Cordas escopo do cliente; ... público Balde clientOrgBucket() { retorno loginCluster().balde(clientOrgBucket); } público @Bean Escopo escopo do cliente() { retorno clientOrgBucket().escopo(escopo do cliente); } } |
Em User.java, vemos como um novo voo é registrado para o usuário. O bean de escopo, criado acima, é passado. O nome de usuário é o id, o nome com o qual o usuário fez login.
1 2 |
público Resultado<Mapa<Cordas, Objeto>> registerFlightForUser(final Escopo escopo, final Cordas nome de usuário, final JsonArray novosVôos) { |
O id do documento do usuário é o nome de usuário do usuário. O aplicativo sabe como obtê-lo na coleção "users", na coleção usada pelo aplicativo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Cordas userId = nome de usuário; Coleção usersCollection = escopo.coleção(NOME_DA_COLEÇÃO_DE_USUÁRIOS); Coleção flightsCollection = escopo.coleção(FLIGHTS_COLLECTION_NAME); Opcional<GetResult> userDataFetch = usersCollection.obter(userId); se (!userDataFetch.isPresent()) { lançar novo IllegalStateException(); } JsonObject userData = userDataFetch.obter().contentAsObject(); se (novosVôos == nulo) { lançar novo IllegalArgumentException("Nenhum voo na carga útil"); } JsonArray adicionado = JsonArray.vazio(); |
Os voos que o usuário reservou são armazenados no documento do usuário, em uma matriz denominada "flights".
1 2 3 4 |
JsonArray todos os vôos reservados = userData.getArray("voos"); se(todos os vôos reservados == nulo) { todos os vôos reservados = JsonArray.criar(); } |
Adicionamos os novos voos aos voos existentes.
1 2 3 4 5 6 7 8 9 10 11 |
para (Objeto novoVôo : novosVôos) { checkFlight(novoVôo); JsonObject t = ((JsonObject) novoVôo); t.colocar("bookedon", "try-cb-java"); Cordas flightId = UUID.UUUID aleatório().toString(); flightsCollection.inserir(flightId, t); todos os vôos reservados.adicionar(flightId); adicionado.adicionar(t); } userData.colocar("voos", todos os vôos reservados); |
Em seguida, armazenamos a nova versão do documento do usuário.
1 2 3 4 5 6 7 8 |
usersCollection.upsert(userId, userData); JsonObject responseData = JsonObject.criar() .colocar("adicionado", adicionado); retorno Resultado.de(responseData.toMap(), "Voo reservado no documento do Couchbase" + userId); } |
Logo abaixo, vemos como os voos de um usuário são recuperados.
1 2 |
público Lista<Mapa<Cordas, Objeto>> getFlightsForUser(final Escopo escopo, final Cordas nome de usuário) { |
Obtenha o documento do usuário da coleção "users".
1 2 3 4 5 6 |
Coleção usuários = escopo.coleção(NOME_DA_COLEÇÃO_DE_USUÁRIOS); Opcional<GetResult> doc = usuários.obter(nome de usuário); se (!doc.isPresent()) { retorno Coleções.emptyList(); } JsonObject dados = doc.obter().contentAsObject(); |
Obtenha a matriz "flights" do documento do usuário. Ela contém uma lista de IDs de voos.
1 2 3 4 |
JsonArray voos = dados.getArray("voos"); se (voos == nulo) { retorno Coleções.emptyList(); } |
Recupere cada documento de voo da coleção "flights" por ID.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// O array "flights" contém IDs de voos. Converta-os em objetos reais. Coleção flightsCollection = escopo.coleção(FLIGHTS_COLLECTION_NAME); Lista<Mapa<Cordas, Objeto>> resultados = novo ArrayList<Mapa<Cordas, Objeto>>(); para (int i = 0; i < voos.tamanho(); i++) { Cordas flightId = voos.getString(i); Opcional<GetResult> res = flightsCollection.obter(flightId); se (!res.isPresent()) { lançar novo Exceção de tempo de execução("Não foi possível recuperar o ID do voo" + flightId); } Mapa<Cordas, Objeto> voo = res.obter().contentAsObject().toMap(); resultados.adicionar(voo); } retorno resultados; } |
Alterações em relação ao código anterior
O código para trabalhar com usuários e seus voos era bem diferente na versão anterior, que não usava coleções. Lá, os voos reservados eram armazenados diretamente no documento do usuário. O documento do usuário era armazenado diretamente na tabela "travel-sample". Aqui está o código original da função registerFlightForUser().
1 2 |
público Resultado<Mapa<Cordas, Objeto>> registerFlightForUser(final Balde balde, final Cordas nome de usuário, final JsonArray novosVôos) { |
Observe o uso de um prefixo para marcar o tipo de documento. Isso não é necessário quando as coleções estão disponíveis.
1 2 3 4 5 6 7 8 9 10 |
JsonDocument userData = balde.obter("user::" + nome de usuário); se (userData == nulo) { lançar novo IllegalStateException(); } se (novosVôos == nulo) { lançar novo IllegalArgumentException("Nenhum voo na carga útil"); } JsonArray adicionado = JsonArray.vazio(); |
Recuperamos a matriz de voos, que já está no documento.
1 2 3 4 5 6 7 8 9 |
JsonArray todos os vôos reservados = userData.conteúdo().getArray("voos"); se(todos os vôos reservados == nulo) { todos os vôos reservados = JsonArray.criar(); } para (Objeto novoVôo : novosVôos) { checkFlight(novoVôo); JsonObject t = ((JsonObject) novoVôo); t.colocar("bookedon", "try-cb-java"); |
Adicionamos os voos ao conjunto de voos reservados.
1 2 3 4 5 |
todos os vôos reservados.adicionar(t); adicionado.adicionar(t); } userData.conteúdo().colocar("voos", todos os vôos reservados); |
E armazenamos o documento do usuário.
1 2 3 4 5 6 7 |
JsonDocument resposta = balde.upsert(userData); JsonObject responseData = JsonObject.criar() .colocar("adicionado", adicionado); retorno Resultado.de(responseData.toMap(), "Voo reservado no documento do Couchbase" + resposta.id()); } |
Obviamente, separar os voos reservados do usuário não é muito atraente em um aplicativo de brinquedo. Mas em um aplicativo de produção, em que armazenamos muitos tipos de informações sobre cada usuário, faria sentido armazenar alguns registros fora do documento do usuário, especialmente aqueles que são grandes, numerosos ou propensos a mudar com frequência.
Documentação de escopos e coleções
Para saber mais sobre como trabalhar com escopos e coleções diretamente, consulte esta documentaçãoque explica a API RESTful para trabalhar com ambos, os comandos relevantes da CLI e informações sobre coleções disponíveis em cbstats.
Resumo
As coleções e os escopos permitem que você organize os documentos em um bucket do Couchbase, assim como as tabelas e os esquemas permitem que você organize as linhas em bancos de dados relacionais. A versão atual do Couchbase 6.5 GA fornece suporte antecipado e limitado para coleções e escopos como um recurso Developer Preview. Para começar a usar as coleções e os escopos, você pode começar a trabalhar com o aplicativo de demonstração em Java agora mesmo.
Recursos
Baixar
Faça o download do Couchbase Server 6.5
Documentação
Documentação do Couchbase Collections 6.5
Notas de versão do Couchbase Server 6.5
Couchbase Server 6.5 O que há de novo
Blogs
Apresentando as coleções - Developer Preview no Couchbase Server 6.5
Anunciando o Couchbase Server 6.5 - Novidades e aprimoramentos
Johan, esse é um recurso muito bom, especialmente para os aplicativos que estão implementando vários inquilinos com base na propriedade discriminadora de dados. Então, eu queria saber se é possível executar uma consulta em vários escopos... por exemplo, para algumas consultas, queremos obter dados de vários escopos e, para outras consultas, os dados devem voltar de um único escopo. Então, isso é possível na versão 6.5 do couchbase? Obrigado pela atenção
Olá, Malik. Não é possível escrever consultas N1QL em coleções na versão 6.5. No momento, as consultas N1QL não funcionam com coleções, apenas com compartimentos, como acontecia no passado. O N1QL para coleções será lançado na versão 7.0, provavelmente em algum momento de 2020.
Quando o N1QL funcionar para coleções, será possível escrever consultas que abranjam vários escopos, assim como agora é possível escrever consultas N1QL que abranjam vários buckets.
Como "Todos os documentos de uma coleção do couchbase devem ser do mesmo tipo, assim como todos os registros de uma tabela relacional são do mesmo tipo", isso implica que todos os documentos de uma coleção devem ter os mesmos campos?
Agradecimentos