Digamos que temos um back-end que pode armazenar alguns tipos de entidades. E possui uma api para criar, ler, modificar e excluir essas entidades, abreviado como CRUD. Mas a api está no servidor e o usuário chegou a algum lugar profundo e metade das solicitações caiu no tempo limite. Eu não gostaria de mostrar um pré-carregador sem fim e geralmente bloquear as ações do usuário. Off-line primeiro pressupõe o carregamento do aplicativo do cache, então talvez os dados devam ser retirados de lá?
Sugere-se armazenar todos os dados em IndexedDB (digamos que não haja muitos) e, se possível, sincronizar com o servidor. Vários problemas surgem:
Se o Id da entidade é gerado no servidor, no banco de dados, como viver sem o Id enquanto o servidor está indisponível?
Ao sincronizar com o servidor, como distinguir entidades criadas no cliente daquelas excluídas no servidor por outro usuário?
Como resolver conflitos?
Identificação
O identificador é necessário, portanto, nós o criaremos. Um GUID ou `+ new Date ()` é adequado para isso, com algumas ressalvas. Somente quando uma resposta vem do servidor com o ID real, você precisa substituí-lo em todos os lugares. Se essa entidade recém-criada já for referenciada por outras pessoas, esses links também precisam ser corrigidos.
Sincronização
Não vamos reinventar a roda, vamos dar uma olhada na replicação do banco de dados. Você pode olhar para ela indefinidamente, como um incêndio, mas em resumo, uma das opções fica assim: além de salvar a entidade no IndexedDB, vamos escrever um log de alterações: [tempo, 'atualizar', Id = 3 , Nome = 'Ivan'], [hora, 'criar', Nome = 'Ivan', Sobrenome = 'Petrov'], [hora, 'excluir', Id = 3] ...
, . , , IndexedDB. Id.
- , , . , - , . - , , . , : , , , . Eventual Consistency.
, , . Operational Transformations (OT) Conflict-free Replicated Data Types (CRDT) . , CRDT : UpdatedAt . , .
, Id . , . , , . . , , Id , . - . . , . Last write win. Eventual Consistency: , . .
function mergeLogs(left, right){
const ids = new Set([
...left.map(x => x.id),
...right.map(x => x.id)
]);
return [...ids].map(id => mergeIdLogs(
left.filter(x => x.id == id),
right.filter(x => x.id ==id)
)).reduce((a,b) => ({
left: [...a.left, ...b.left],
right: [...a.right, ...b.right]
}), {left: [], right: []});
}
function mergeIdLogs(left,right){
const isWin = log => log.some(x => ['create','delete'].includes(x.type));
const getMaxUpdate = log => Math.max(...log.map(x => +x.updatedAt));
if (isWin(left))
return {left: [], right: left};
if (isWin(right))
return {left: right, right: []};
if (getMaxUpdate(left) > getMaxUpdate(right))
return {left: [], right: left};
else
return {left: right, right: []};
}
Não haverá implementação, porque em cada caso específico há um demônio nos detalhes, e não há, de modo geral, nada a implementar aqui - a geração de um identificador e a gravação em indexedDB.
Claro, CRDT ou OT serão melhores, mas se você precisar fazer isso rapidamente, mas eles não são permitidos no back-end, então este trabalho servirá.