Mais um dia e mais uma atualização do runtime que não para de nos surpreender! O Deno anunciou que, desde a versão 1.32, agora conta com um banco de dados chave-valor nativo!

Não é tão estranho assim

Antes que você pense: "Mas por que alguém iria colocar um banco de dados em um runtime? Não é melhor ter uma lib para isso?". De fato, ter um banco de dados chave-valor dentro do runtime de forma nativa parece algo muito elaborado para uma standard lib, mas e se a gente já estiver fazendo isso há muitos anos e nem percebia?

A realidade é que, sempre que criávamos pequenas aplicações usando Node, ou até mesmo servidores Web, uma das formas mais rápidas e mais eficientes de se armazenar um dado de forma persistente sem ter que usar uma solução completa de banco é usar o sistema de arquivos do computador (o famoso file system, ou FS). E a forma mais fácil de usar o FS é através de um modelo chave-valor.

Inicialmente, antes do advento do map, alguém poderia dizer que é bastante complicado implementar uma classe que atuasse como um banco, porque você iria precisar de algo mais completo como:

class Database {
	insert () {}
    search (query) {}
    delete (id) {}
    update (id, data) {}
}

Fora o gerenciamento do estado interno e também concorrência para abrir o mesmo arquivo ao mesmo tempo, o que pode levar você a implementar locks ou até mesmo um MUTEX.

Eu mesmo já fiz vários pequenos bancos de dados usando arrays e objetos para armazenar valores no FS quando queria algo simples e rápido.

Os maps mudaram a forma de pensar porque eles já tinham muitos dos métodos como o delete, nativos da API. Assim, criar um banco dessa forma era basicamente serializar e desserializar um array em um arquivo, que ainda continua sendo a maior complexidade.

Deno KV

O time do Deno anunciou recentemente que a versão 1.32 do runtime já vai contar com uma implementação nativa de um Key-Value store (KV), porém de uma forma mais evoluída.

Para usarmos, basta abrir o banco usando Deno.openKv(), que leva opcionalmente um caminho que será o local que o banco será salvo.

const kv = await Deno.openKv();

Agora temos os quatro métodos básicos de um CRUD:

  • Inserção pelo método kv.set
  • Remoção pelo método kv.delete
  • Leitura pelo método kv.get
  • E a atualização pode ser feita re-inserindo um registro com a mesma chave

A mudança é que, além de aceitar chaves simples, o Deno KV também aceita prefixos e chaves aninhadas, basta passar um parâmetro como array para a chave ao setar, então se quisermos criar uma "pasta" chamada users com todas as chaves relacionadas a usuários ali, podemos fazer assim:

await kv.set(['users', 'lucas'], { name: 'Lucas' })

Podemos também obter o valor usando a mesma notação:

const valor = await kv.get(['users', 'lucas'])
// valor.key -> ['users', 'lucas']
// valor.value -> { name: 'Lucas' }

Mas além disso, podemos tirar proveito de generators e de async iterators para ler todo um conjunto de chaves que começam com o mesmo prefixo com o kv.list:

for await (const entry of kv.list({ prefix: ["users"] })) {
  console.log(entry.key);
  console.log(entry.value);
}
O método list também aceita outros tipos de seletores, como um range alfanumérico e várias opções para alterar o funcionamento, como limit e cursor

Versões

Se uma mesma chave é sobrescrita usando kv.set na mesma chave, o Deno vai automaticamente manter um conjunto de versões chamado versionstamp, que nada mais é do que um número bem grande que é o valor daquela versão.

Versões podem ser muito úteis em sistemas de arquivos que são compartilhados por muitos computadores ou aplicações, de forma que múltiplas escritas podem ser realizadas ao mesmo tempo, para garantir que uma não sobrescreva a outra, podemos comparar os números de versões antes e depois da escrita para garantir que elas foram aplicadas na ordem que queremos:

const db = await Deno.openKv()
const results = await db.getMany([['chave1'], ['chave2']])
results[0].versionstamp // "00000000000000010000"
results[1].versionstamp // null

Conclusão

O Deno KV pode não parecer algo super interessante à primeira vista, mas a forma como ele é elaborado permite que tenhamos uma grande facilidade quando vamos construir aplicações de pequeno e médio porte, principalmente por ter tudo à mão.

Não só isso, mas também podemos tirar proveito desse sistema para construir, por exemplo, pequenos sistemas de caches não persistentes locais, que podem ser a diferença entre a performance de uma aplicação estar boa ou ruim.

No entanto, é importante salientar que a API do Deno KV ainda está experimental e pode sofrer mudanças a qualquer momento. Para mais informações, recomendo ler a documentação da API para entender um pouco mais sobre tudo que ela pode fazer!