Zwischenspeichern von CRUD in IndexedDB

Angenommen, wir haben ein Backend, in dem Entitäten gespeichert werden können. Und es hat eine API zum Erstellen, Lesen, Ändern und Löschen dieser Entitäten, abgekürzt als CRUD. Aber die API befindet sich auf dem Server, und der Benutzer hat einen tiefen Punkt erreicht, und die Hälfte der Anforderungen fällt auf eine Zeitüberschreitung. Ich möchte keinen endlosen Preloader anzeigen und generell Benutzeraktionen blockieren. Offline setzt zunächst voraus, dass die Anwendung aus dem Cache geladen wird. Vielleicht sollten die Daten von dort übernommen werden?





Es wird empfohlen, alle Daten in IndexedDB zu speichern (sagen wir, dass es nicht sehr viele davon gibt) und wenn möglich mit dem Server zu synchronisieren. Es treten mehrere Probleme auf:





  1. Wenn die ID der Entität auf dem Server in der Datenbank generiert wird, wie kann man dann ohne die ID leben, während der Server nicht verfügbar ist?





  2. Wie kann man bei der Synchronisierung mit dem Server auf dem Client erstellte Entitäten von Entitäten unterscheiden, die von einem anderen Benutzer auf dem Server gelöscht wurden?





  3. Wie löse ich Konflikte?





Identifizierung

Der Bezeichner wird benötigt, daher erstellen wir ihn selbst. Eine GUID oder `+ new Date ()` ist hierfür mit einigen Einschränkungen in Ordnung. Nur wenn eine Antwort vom Server mit der tatsächlichen ID kommt, müssen Sie sie überall ersetzen. Wenn diese neu erstellte Entität bereits von anderen referenziert wird, müssen diese Links ebenfalls korrigiert werden.





Synchronisation

Wir werden das Rad nicht neu erfinden, schauen wir uns die Datenbankreplikation an. Sie können es endlos wie ein Feuer betrachten, aber kurz gesagt, eine der Optionen sieht folgendermaßen aus: Zusätzlich zum Speichern der Entität in IndexedDB schreiben wir ein Protokoll der Änderungen: [Zeit, 'Aktualisierung', ID = 3 , Name = 'Ivan'], [Zeit, 'erstellen', Name = 'Ivan', Nachname = 'Petrov'], [Zeit, 'löschen', 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: []};
}
      
      



Es wird keine Implementierung geben, da in jedem speziellen Fall ein Teufel im Detail steckt und hier im Großen und Ganzen nichts zu implementieren ist - die Generierung eines Bezeichners und das Schreiben in die indizierte Datenbank.





Natürlich ist CRDT oder OT besser, aber wenn Sie es schnell erledigen müssen, diese aber im Backend nicht zulässig sind, reicht diese Arbeit aus.








All Articles