Disons que nous avons un backend qui peut stocker certaines sortes d'entités. Et il a une API pour créer, lire, modifier et supprimer ces entités, abrégé en CRUD. Mais l'API est sur le serveur, et l'utilisateur est arrivé quelque part en profondeur et la moitié des demandes tombent à expiration. Je ne voudrais pas montrer un préchargeur sans fin et bloquer généralement les actions des utilisateurs. Hors ligne suppose d'abord le chargement de l'application à partir du cache, alors peut-être que les données devraient être extraites de là?
Il est suggéré de stocker toutes les données dans IndexedDB (disons qu'il n'y en a pas beaucoup) et, si possible, de les synchroniser avec le serveur. Plusieurs problèmes se posent:
Si l'Id de l'entité est généré sur le serveur, dans la base de données, alors comment vivre sans l'Id alors que le serveur est indisponible?
Lors de la synchronisation avec le serveur, comment distinguer les entités créées sur le client de celles supprimées sur le serveur par un autre utilisateur?
Comment résoudre les conflits?
Identification
L'identifiant est nécessaire, nous allons donc le créer nous-mêmes. Un GUID ou `+ new Date ()` convient pour cela, avec quelques mises en garde. Ce n'est que lorsqu'une réponse provient du serveur avec le véritable identifiant que vous devez la remplacer partout. Si cette entité nouvellement créée est déjà référencée par d'autres, ces liens doivent également être corrigés.
Synchronisation
Nous ne réinventerons pas la roue, regardons la réplication de base de données. Vous pouvez le regarder à l'infini, comme un feu, mais en bref, l'une des options ressemble à ceci: en plus de sauvegarder l'entité dans IndexedDB, nous écrirons un journal des modifications: [time, 'update', Id = 3 , Name = 'Ivan'], [time, 'create', Name = 'Ivan', Name = 'Petrov'], [time, 'delete', 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: []};
}
Il n'y aura pas d'implémentation, car dans chaque cas spécifique il y a un diable dans les détails, et il n'y a, en gros, rien à implémenter ici - la génération d'un identifiant et l'écriture dans indexedDB.
Bien sûr, CRDT ou OT seront meilleurs, mais si vous devez le faire rapidement, mais qu'ils ne sont pas autorisés sur le backend, alors ce travail fera l'affaire.