Nesta postagem do blog, apresentaremos um novo recurso interessante do próximo Versão do Couchbase Server 4.5
(codinome Watson), agora em Beta.
Estamos falando sobre o API de subdocumento (abreviado para subdoc).
Editar: Esta postagem do blog foi editada com atualizações do 4.5 Beta e do Java SDK 2.2.6.
Para conhecer outros recursos do Couchbase 4.5, consulte as postagens do blog de Don Pinto sobre o Visualização do desenvolvedor e o Beta.
Do que se trata?
O subdocumento é um recurso do lado do servidor que adiciona ao Memcached
protocolo que alimenta o Couchbase Operações de chave-valor. Ele adiciona algumas operações que atuam em um único JSON mas, em vez de forçá-lo a recuperar o conteúdo completo do documento, ele permite que você especifique o caminho
dentro do JSON que você está interessado em recuperar ou alterar.
Vou lhe mostrar por que isso é interessante com o seguinte cenário: digamos que você tenha um documento JSON grande (e eu quero dizer laaaaarge) armazenado sob o ID K
no Couchbase. Dentro do documento, no dicionário submarino
há um alguns
que você deseja atualizar. Como você faria isso com o Couchbase 4.1?
É isso mesmo, você precisa executar um get(k)
de todo o documento, modificar seu conteúdo localmente e, em seguida, executar um upsert
(ou um substituir
). Você não apenas transmite todo o documento grande pela rede quando está interessado apenas em uma pequena parte dele, mas também transmiti-lo duas vezes!
O subdoc A API permite operações mais simples nesses casos. Escolha a operação que deseja executar em seu documento e forneça a chave, o caminho dentro do JSON e, opcionalmente, o valor que deseja usar para essa operação, e voilà!
Em nosso exemplo anterior, o caminho
seria "sub.value
". Isso é bastante natural e consistente com a sintaxe de caminho usada em nossa linguagem de consulta, N1QL
. Aqui está a aparência, não desenhada em escala :)
Na verdade, a mensagem tem apenas alguns bytes, portanto, quanto maior for o documento JSON original, maior será o benefício.
A API do cliente
Agora, aposto que tenho sua atenção. Mas quais são as operações oferecidas e como é a API?
Vamos usar o exemplo da API Java, conforme oferecido pelo 2.2.6
Java SDK.
Para cada exemplo, consideraremos que um "subdoc
" O documento JSON existe no banco de dados e tem o seguinte conteúdo:
1 2 3 4 5 6 7 8 9 |
{ "frutas": [ "apple", "banana", "ananas" ], "sub": { "valor": "someString", "bool": falso }, "contador": 1, "lixo": [ ... ] //um conjunto muito, muito longo } |
Também consideramos que a Balde
instância, balde
está disponível.
Operações de pesquisa
Sem falar em mutações dentro de um JSON, às vezes você só quer ler um único valor escondido no fundo do documento. Ou apenas deseja verificar se uma entrada está lá. O Subdoc oferece duas operações (obter
e existe
) para fazer exatamente isso, que são disponibilizados por meio do bucket.lookupIn(String key)
método.
O lookupIn
na verdade, fornece a você um construtor direcionado a um único documento JSON ("chave
"), que, por sua vez, você pode usar para descrever com fluência as operações que deseja executar. Depois de ter todo o conjunto de especificações de pesquisa pronto, você pode executar a operação chamando a função executar()
método. É claro que ele também pode ser usado para uma única pesquisa.
Esse método retorna um Fragmento de documento
em Java, representando o resultado. Observe que as pesquisas múltiplas sempre retornam esse resultado, desde que não ocorra nenhum erro no nível do documento (ou seja, o documento não existe ou não é JSON).
Você pode obter o resultado individual de cada operação chamando result.content(String path)
ou result.content(int operationIndex)
(se você tiver várias operações direcionadas ao mesmo caminho, nesse caso, a primeira sempre retornará o resultado da primeira). Se houver um erro, você obterá a classe filha apropriada de SubDocumentException
. Caso contrário, você obtém o valor (ou "true" no caso de existe
).
Há também um existe
semelhante ao content, mas que retorna true somente se o objeto result contiver um resultado para esse caminho/índice e a operação correspondente tiver sido bem-sucedida.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Fragmento de documento resultado = balde .lookupIn("subdoc") .obter("sub.value") .existe("frutas") .existe("sub.foo") .executar(); Cordas subValor = resultado.conteúdo("sub.value", Cordas.classe); booleano frutasExistir = resultado.conteúdo("frutas", Booleano.classe); booleano fooExist = balde.existe("sub.foo"); Sistema.fora.println(subValor + ", " + frutasExistir + ", " + foExist); |
O código acima imprime:
1 |
someString, verdadeiro, falso |
Operações de mutação
Há mais variedades de operações de mutação. Algumas são feitas sob medida para lidar com matrizes, enquanto outras são mais adaptadas para lidar com dicionários.
Mais uma vez, fornecemos um construtor para direcionar mutações em um documento JSON específico por meio de bucket.mutateIn(String key)
. Você encadeia as operações que deseja executar e, por fim, chama executar()
para realizar o conjunto de mutações.
As mutações podem levar em conta o CAS do documento anexo, verificando o CAS fornecido em relação ao atual antes de aplicar todo o conjunto de mutações. Para fazer isso, forneça o CAS a ser verificado, chamando .withCas(long cas)
uma vez em sua cadeia de construtores.
Outros com
Os métodos (que só precisam ser chamados uma vez) são:
withDurability(PersistTo p, ReplicateTo r)
permite observar uma restrição de durabilidade em todo o conjunto de mutações.withExpiry(int ttl)
Permite dar ao documento um prazo de validade quando a mutação for bem-sucedida.
Além disso, algumas mutações permitem criar objetos mais profundos (por exemplo, criar a entrada no caminho newDict.sub.entry
, apesar de newDict
e submarino
não existente). Isso é representado pelo símbolo createParents
nos métodos do construtor.
Tomando o exemplo acima e usando algumas das operações de mutação, podemos fazer o seguinte com eficiência:
- Substituir a fruta
ananas
(que é incorretamente uma palavra francesa) comabacaxi
. - Adicionar uma nova fruta,
pera
no frente da matriz de frutas. - Adicionar uma nova entrada a
submarino
chamadonovoValor
. - Incrementar a
contador
por um delta de100
. - Livre-se do
lixo
grande matriz. - Aguarde até que o cbserver confirme que os dados foram gravados no disco do mestre.
- Abortar tudo se o CAS no servidor não for
1234
.
Veja como fazer isso usando o Java SDK:
1 2 3 4 5 6 7 8 9 10 11 12 |
booleano createParents = falso; Fragmento de documento resultado = balde .mutateIn("subdoc") .substituir("fruits[2]", "abacaxi") .arrayPrepender("frutas", "pera", createParents) .inserir("sub.newValue", "theNewValue", createParents) .contador("contador", 100L, createParents) .remover("lixo") .comDurabilidade(PersistTo.MESTRE, ReplicarPara.NENHUM) .comCas(1234L) .executar(); |
Outras operações de mutação disponíveis são:
upsert
arrayInsert
(especializado na inserção de um valor em uma matriz, em um índice específico)arrayAppend
(especializado na inserção de um valor no final de uma matriz)- arrayInsertAll (o mesmo que arrayInsert, mas inserindo cada elemento de uma coleção na matriz)
- arrayPrependAll (o mesmo que arrayPrepend, mas adicionando cada elemento de uma coleção à frente de uma matriz)
- arrayAppendAll (o mesmo que arrayAppend, mas adicionando cada elemento de uma coleção à parte de trás de uma matriz)
arrayAddUnique
(especializado em inserir um valor em uma matriz se o valor ainda não estiver presente na matriz)
Depois de aplicar essas 5 mutações, eis a aparência do documento no banco de dados:
1 2 3 4 5 6 7 8 9 |
{ "frutas": [ "pera", "apple", "banana", "abacaxi" ], "sub": { "valor": "someString", "bool": falso, "newValue": "theNewValue" }, "contador": 101 } |
Ao contrário do que acontece quando você faz várias pesquisas, se alguma das mutações falhar, todo o conjunto de operações será ignorado e nenhuma mutação será executada. Nesse caso, você receberá uma mensagem MultiMutationException
no qual você pode verificar o índice da primeira operação com falha (bem como o status de erro correspondente).
Tratamento de erros
Cada operação de subdocumento pode levar a erros relacionados ao subdocumento: e se o documento que corresponde à chave fornecida não for JSON? E se você fornecer um caminho que não existe? Ou se o caminho contiver um índice de matriz em um elemento que não é uma matriz (por exemplo. sub[1]
)?
Todos os erros específicos de subdoc têm um Status da resposta
no Java SDK e uma subclasse dedicada de SubDocumentException
. Essas exceções são consistentes nos SDKs que oferecem uma API para subdocumentos:
A lista completa no Java SDK é:
- PathNotFoundException
- PathExistsException
- NumberTooBigException
- PathTooDeepException
- PathMismatchException
- ValueTooDeepException
- DeltaTooBigException
- CannotInsertValueException
- PathInvalidException
- DocumentNotJsonException*
- DocumentTooDeepException*
- MultiMutationException*
Os três últimos são de nível de documento (o que significa que se aplicam a todo o documento, sem considerar os caminhos individuais), de modo que sempre serão lançados diretamente pelo executar()
métodos.
Os outros também podem ser lançados ao chamar Fragmento de documento
's conteúdo
nos casos em que você especificou várias operações de pesquisa.
Como dito na seção anterior, várias operações de mutação em que pelo menos uma falha acionam um MultiMutationException
sendo lançada. Essa exceção tem um firstFailureIndex()
e firstFailureStatus()
para obter informações sobre qual especificação causou a falha de toda a operação.
Conclusão
Esperamos que você encontre ótimos casos de uso para esse novo recurso e que o adore! Então, pegue o Couchbase 4.5 Betabrinque com a API e não hesite em nos informar feedback!
Nesse meio tempo, Codificação feliz! – A equipe do Java SDK
Obrigado pela postagem. No entanto, tenho uma dúvida com relação ao manuseio da matriz: Existe a possibilidade de endereçar os elementos da matriz além de sua posição na matriz, de modo que os IDs em subdocumentos não possam ser usados para acessar os elementos?
Estou usando este código
DocumentFragment result = couchbaseBucket.async().lookupIn(docId).get(subDocId).execute().
toBlocking().singleOrDefault(null);
Não sei bem por que, mas result.rawContent(subDocId) retorna nulo, enquanto result.content(subDocId) retorna o valor correto.
Você não poderia indicar a área que pode estar causando esse problema?