Stai provando a creare un sistema distribuito alla fine coerente. Quelli sono intrinsecamente complicati. È comprensibile che tu abbia difficoltà a trovare una buona soluzione perché non ce n'è una .
Per esempio, si consideri un utente con due dispositivi di “phone” e “tablet”, e la seguente sequenza di eventi:
- Il telefono e il tablet iniziano con lo stesso database e sono disconnessi da qualsiasi rete.
- alle 13:00, l'utente cancella la ricetta "Limone" sul proprio tablet.
- alle 14:00, l'utente modifica la ricetta "Lemon bar" sul proprio telefono.
- alle 15:00, il telefono si sincronizza con il backup del cloud. Il database di backup conterrà la modifica delle barre di limone.
- alle 16:00, il tablet si sincronizza con il backup del cloud. Ma ora, il database di backup contiene una modifica di un record che il tablet vuole eliminare. Cosa fare?
In un sistema di controllo della versione, questo sarebbe un "conflitto di unione" che richiederebbe una risoluzione manuale. Tuttavia, in genere non è auspicabile implementare l'interfaccia utente per la risoluzione dei conflitti, almeno nelle app consumer.
Un possibile approccio consiste nel separare i concetti dello stato del database interrogabile e la sequenza di eventi che hanno portato a tale stato ( Evento Sourcing ). Lo stato può essere ricalcolato dalla riproduzione di tutti gli eventi. Qui, la domanda difficile rimane su come fondere più stream di eventi, in particolare su come ordinare gli eventi. In molti casi, è bene ordinare per data e ora dell'evento e si fondono da una strategia “Last One vince” in caso di conflitto. Tuttavia, le esatte regole di fusione dipenderanno dal tuo dominio problematico. Non esiste un approccio universale che funzioni sempre.
In particolare, il risultato è che mentre tutti i dispositivi alla fine mostreranno lo stesso stato (i dispositivi saranno coerenti), ci sono buone probabilità che l'unione abbia portato a perdita di dati o perdita di qualità dei dati. Per le app di consumo questo è probabilmente un po 'accettabile, soprattutto perché i conflitti saranno rari. Ma questo dipende dai tuoi precisi requisiti.
Con un approccio orientato agli eventi, memorizzeresti i registri degli eventi nel backup del cloud e possibilmente le istantanee del database, anche se l'eliminazione di vecchie istantanee senza impedire a un altro dispositivo di sincronizzare correttamente è così difficile che potrebbe essere meglio evitare gli snapshot completamente . Puoi assegnare a ciascun log un nome univoco, ad es. in base al dispositivo + timestamp o basato su un hash dei contenuti. Se i file sono immutabili, questo semplifica la sincronizzazione: carica tutti i file che hai ma il cloud non lo fa e scarica tutti i nuovi registri del cloud (confronta anche l'idea di storage adressable di contenuto ). Mentre la sincronizzazione coinvolgerà lo stato del database locale in fase di ricostruzione dai log degli eventi, questo non dovrebbe rappresentare un problema per database ragionevolmente piccoli.
Tutto ciò diventa molto più semplice se si esegue il proprio server che può fungere da fonte di verità per lo stato dell'applicazione. Normalmente gli eventi verrebbero inviati al server immediatamente e produrranno un nuovo stato lì. Se il dispositivo è disconnesso, potrebbe offrire una modalità offline che memorizza gli eventi in un secondo momento, con la consapevolezza che questi eventi potrebbero essere scartati in caso di conflitto.
Una nota sulla definizione degli eventi: un evento non dovrebbe sostituire un'entità con una nuova versione, ma descrivere una piccola modifica autonoma che si traduce in un modello di dati logicamente coerente. Ciò renderà gli eventi più facili da unire. Ad esempio, un evento che sostituisce una ricetta con una versione modificata è problematico. Potrebbe essere preferibile creare più eventi "contrassegna come preferito", "modifica quantità ingrediente", "inserisci passaggio", "modifica passaggio". Nota che le entità non devono essere referenziate da un ID che dipende dall'ordine degli eventi, ad es. un ID autoincrement. Invece, preferisci gli UUID.