Práticas recomendadas para manter o estado do aplicativo com o IndexedDB

Conheça as práticas recomendadas para sincronizar o estado do aplicativo entre o IndexedDB e as bibliotecas de gerenciamento de estado mais conhecidas.

Quando um usuário carrega um site ou aplicativo pela primeira vez, geralmente há muito trabalho envolvido na construção do estado inicial do aplicativo que é usado para renderizar a interface. Por exemplo, às vezes o app precisa autenticar o usuário no lado do cliente e fazer várias solicitações de API antes de ter todos os dados necessários para exibir na página.

Armazenar o estado do aplicativo no IndexedDB pode ser uma ótima maneira de acelerar o tempo de carregamento para visitas repetidas. O app pode sincronizar com qualquer serviço de API em segundo plano e atualizar a interface com novos dados de forma lenta, empregando uma estratégia de revalidação de dados desatualizados.

No entanto, ao usar o IndexedDB, há muitos fatores importantes a serem considerados que podem não ser imediatamente óbvios para desenvolvedores iniciantes nas APIs. Este documento responde a perguntas comuns e discute alguns dos pontos mais importantes a serem considerados ao manter o estado do aplicativo no IndexedDB.

Mantenha seu app previsível

Muitas das complexidades em torno do IndexedDB decorrem do fato de que há muitos fatores sobre os quais você (o desenvolvedor) não tem controle. Esta seção explora muitos dos problemas que você precisa ter em mente ao trabalhar com o IndexedDB.

As gravações no armazenamento podem falhar

Os erros ao gravar no IndexedDB podem ocorrer por vários motivos e, em alguns casos, eles estão fora do seu controle como desenvolvedor. Por exemplo, alguns navegadores não permitem gravar no IndexedDB no modo de navegação anônima. Também existe a possibilidade de um usuário estar em um dispositivo que está quase sem espaço em disco, e o navegador vai impedir que você armazene qualquer coisa.

Por isso, é extremamente importante implementar sempre o tratamento de erros adequado no código do IndexedDB. Isso também significa que geralmente é uma boa ideia manter o estado do aplicativo na memória (além de armazená-lo). Assim, a interface não falha ao ser executada no modo de navegação privada ou quando o espaço de armazenamento não está disponível, mesmo que alguns dos outros recursos do app que exigem armazenamento não funcionam.

Os dados armazenados podem ter sido modificados ou excluídos pelo usuário

Ao contrário dos bancos de dados do lado do servidor, em que é possível restringir o acesso não autorizado, os bancos de dados do lado do cliente são acessíveis a extensões de navegador e ferramentas para desenvolvedores e podem ser limpos pelo usuário.

Embora seja incomum os usuários modificarem os dados armazenados localmente, é bastante comum limpá-los. É importante que o aplicativo possa processar esses dois casos sem erros.

Os dados armazenados podem estar desatualizados

Assim como na seção anterior, mesmo que o usuário não tenha modificado os dados, também é possível que os dados armazenados tenham sido gravados por uma versão anterior do código, possivelmente uma versão com bugs.

O IndexedDB tem suporte integrado para versões de esquema e upgrade usando o método IDBOpenDBRequest.onupgradeneeded(). No entanto, você ainda precisa escrever o código de upgrade de modo que ele possa lidar com o usuário de uma versão anterior (incluindo uma versão com um bug).

Os testes de unidade podem ser muito úteis aqui, já que geralmente não é viável testar manualmente todos os caminhos e casos de upgrade possíveis.

Manter o desempenho do app

Um dos principais recursos do IndexedDB é a API assíncrona, mas não se engane fazendo pensar que você não precisa se preocupar com o desempenho ao usá-lo. Há vários casos em que o uso inadequado ainda pode bloquear a linha de execução principal, o que pode levar à falta de resposta.

Como regra geral, as leituras e gravações no IndexedDB não podem ser maiores do que o necessário para os dados acessados.

Embora o IndexedDB permita armazenar objetos grandes e aninhados como um único registro (e isso seja bastante conveniente do ponto de vista do desenvolvedor), essa prática precisa ser evitada. Isso ocorre porque, quando o IndexedDB armazena um objeto, ele precisa primeiro criar um clone estruturado desse objeto, e o processo de clonagem estruturado acontece na linha de execução principal. Quanto maior o objeto, maior será o tempo de bloqueio.

Isso apresenta alguns desafios ao planejar como persistir o estado do aplicativo na IndexedDB, já que a maioria das bibliotecas de gerenciamento de estado conhecidas (como Redux) funcionam gerenciando toda a árvore de estado como um único objeto JavaScript.

Embora gerenciar o estado dessa maneira tenha muitos benefícios, e armazenar toda a árvore de estado como um único registro no IndexedDB possa ser tentador e conveniente, fazer isso após cada mudança (mesmo que limitada/debounce) resultará em bloqueio desnecessário da linha de execução principal, mas vai aumentar a probabilidade de erros de gravação e, em alguns casos, vai fazer com que a guia do navegador falhe ou pare de responder.

Em vez de armazenar toda a árvore de estados em um único registro, divida-a em registros individuais e atualize apenas os registros que realmente mudam.

Como na maioria das práticas recomendadas, essa não é uma regra "tudo ou nada". Nos casos em que não é viável dividir um objeto de estado e gravar apenas o conjunto de mudanças mínimo, dividir os dados em subárvores e gravar apenas elas ainda é preferível a gravar sempre a árvore de estado inteira. Pequenas melhorias são melhores do que nenhuma melhoria.

Por fim, sempre meça o impacto no desempenho do código que você escreve. Embora seja verdade que as gravações pequenas na IndexedDB têm um desempenho melhor do que as grandes, isso só é importante se as gravações na IndexedDB que seu aplicativo faz realmente levam a tarefas longas que bloqueiam a linha de execução principal e prejudicam a experiência do usuário. É importante medir para entender o que você está otimizando.

Conclusões

Os desenvolvedores podem usar mecanismos de armazenamento do cliente, como o IndexedDB, para melhorar a experiência do usuário do app, não apenas mantendo o estado em todas as sessões, mas também diminuindo o tempo necessário para carregar o estado inicial em visitas repetidas.

Embora o uso correto do IndexedDB possa melhorar muito a experiência do usuário, usá-lo incorretamente ou não processar casos de erro pode levar a apps corrompidos e usuários insatisfeitos.

Como o armazenamento do cliente envolve muitos fatores fora do seu controle, é essencial que seu código seja bem testado e processe corretamente os erros, mesmo aqueles que podem parecer improváveis de ocorrer.