← Όλα τα άρθρα

Offline-first web app: τοπικά δεδομένα, sync queue και conflicts χωρίς απώλειες

Πώς μια web εφαρμογή συνεχίζει offline και συγχρονίζει με ασφάλεια αργότερα, με IndexedDB, outbox, idempotency και επίλυση συγκρούσεων.

Offline-first web app: τοπικά δεδομένα, sync queue και conflicts χωρίς απώλειες

Μια εφαρμογή πεδίου, αποθήκης ή πωλήσεων δεν μπορεί να θεωρεί ότι η σύνδεση είναι πάντα σταθερή. Ο χρήστης μπορεί να συμπληρώνει φόρμα σε υπόγειο, να καταγράφει παράδοση σε απομακρυσμένη περιοχή ή να αλλάζει δίκτυο. Το μήνυμα «η σύνδεση χάθηκε, δοκιμάστε ξανά» δεν αρκεί όταν η εργασία έχει ήδη γίνει.

Offline-first σημαίνει ότι η εφαρμογή σχεδιάζει εξαρχής την τοπική εργασία και τον μεταγενέστερο συγχρονισμό. Δεν σημαίνει ότι κάθε feature λειτουργεί χωρίς internet ούτε ότι ο browser γίνεται ανεξάρτητη βάση παραγωγής. Χρειάζεται σαφές μοντέλο για cached reads, queued writes, conflicts και ασφάλεια.

Service worker για app shell και network strategy

Ο service worker λειτουργεί ανάμεσα στην εφαρμογή και το δίκτυο, μπορεί να interceptάρει requests και να απαντά από cache. Η MDN περιγράφει offline-first χρήση με cached assets και network fetch για νεότερα δεδομένα.

Το app shell, δηλαδή HTML, CSS, JavaScript και βασικά icons, μπορεί να προ-αποθηκευτεί με versioned cache. Στο activation καθαρίζονται παλιές εκδόσεις. Δεν πρέπει όμως να μπαίνουν όλα τα API responses σε ένα γενικό cache-first rule.

Κάθε resource χρειάζεται στρατηγική:

  • cache-first για versioned static assets
  • network-first με fallback για σχετικά φρέσκα δεδομένα
  • stale-while-revalidate για μη κρίσιμο content
  • network-only για ιδιαίτερα ευαίσθητες ή μη cacheable ενέργειες

Οι service workers απαιτούν HTTPS και έχουν lifecycle που χρειάζεται σωστή διαχείριση updates. Μια νέα έκδοση μπορεί να περιμένει μέχρι να κλείσουν παλιά tabs, άρα database migrations και UI compatibility πρέπει να σχεδιάζονται.

IndexedDB αντί για localStorage

Το IndexedDB είναι asynchronous browser database για structured data και μπορεί να χρησιμοποιηθεί σε service worker. Η MDN το προτείνει για σημαντικό όγκο offline δεδομένων, ενώ σημειώνει ότι δεν υλοποιεί από μόνο του συγχρονισμό με server. Αυτός ο μηχανισμός είναι ευθύνη της εφαρμογής.

Το localStorage είναι synchronous, μικρό και ακατάλληλο για σύνθετες offline ουρές. Στο IndexedDB μπορούμε να έχουμε stores για entities, pending mutations, sync metadata και attachments references.

Κάθε record χρειάζεται local ID, server ID όπου υπάρχει, version, modified time και sync state. Τα browser storage δεδομένα μπορεί να διαγραφούν από τον χρήστη, quota eviction ή private mode. Η εφαρμογή δεν πρέπει να τα θεωρεί μοναδικό μόνιμο αντίγραφο.

Outbox pattern για εγγραφές

Όταν ο χρήστης αποθηκεύει offline, η εφαρμογή γράφει πρώτα local state και mutation στην outbox μέσα στην ίδια IndexedDB transaction. Το UI δείχνει αμέσως την αλλαγή με κατάσταση «Αναμονή συγχρονισμού».

Κάθε mutation περιλαμβάνει operation ID, entity ID, base version, action, payload και creation time. Όταν υπάρχει σύνδεση, worker στέλνει τις εργασίες με τη σωστή σειρά. Το backend χρησιμοποιεί operation ID ως idempotency key ώστε retry να μην δημιουργεί διπλή παραγγελία ή επίσκεψη.

Η outbox δεν διαγράφεται όταν ξεκινήσει το request, αλλά μόνο όταν ληφθεί επιβεβαιωμένο αποτέλεσμα. Σε timeout, η εργασία παραμένει άγνωστη και επαναλαμβάνεται idempotently.

Background Sync με progressive enhancement

Η Background Synchronization API μπορεί να ειδοποιήσει service worker όταν επιστρέψει σταθερή σύνδεση. Η MDN τη χαρακτηρίζει limited availability, άρα δεν πρέπει να είναι ο μοναδικός μηχανισμός.

Η εφαρμογή συγχρονίζει επίσης όταν ανοίγει, όταν γίνεται focus, όταν ανιχνεύει online κατάσταση και μέσω χειροκίνητου command. Το navigator.onLine είναι ένδειξη, όχι απόδειξη ότι το API είναι προσβάσιμο. Η πραγματική επιτυχία προκύπτει από request προς το backend.

Το background job έχει περιορισμένο χρόνο ζωής. Μεγάλα uploads ή μακρόχρονες εργασίες χρειάζονται resumable protocol ή διαφορετική στρατηγική, όχι υπόθεση ότι ο service worker θα τρέχει απεριόριστα.

Server version και conflicts

Δύο συσκευές μπορεί να αλλάξουν το ίδιο record offline. Κάθε update στέλνει base version. Αν ο server έχει νεότερη version, επιστρέφει conflict αντί να εφαρμόσει last-write-wins σιωπηλά.

Η επίλυση εξαρτάται από το domain:

  • append-only events μπορούν συχνά να συνδυαστούν
  • διαφορετικά ανεξάρτητα fields μπορούν να κάνουν field-level merge
  • αριθμητικές ποσότητες μπορεί να χρειάζονται επιχειρησιακό κανόνα
  • αλλαγή status ή ανάθεση μπορεί να απαιτεί άνθρωπο

Το UI εμφανίζει local και server value με context και επιτρέπει επιλογή ή νέα σύνθεση. Δεν δείχνει raw JSON error. Αν η λειτουργία είναι υψηλού ρίσκου, μπορεί να απαιτεί online επιβεβαίωση και να μην επιτρέπεται offline.

Temporary IDs και εξαρτώμενες ενέργειες

Ο χρήστης μπορεί να δημιουργήσει πελάτη offline και αμέσως να προσθέσει επίσκεψη σε αυτόν. Το local ID πρέπει να είναι globally unique, για παράδειγμα UUID, και να χρησιμοποιείται στις εξαρτήσεις της outbox.

Όταν ο server επιστρέψει canonical ID, η εφαρμογή αποθηκεύει mapping και ενημερώνει pending references transactionally. Δεν πρέπει να βασίζεται σε αρνητικούς αριθμούς ή τοπικούς counters που συγκρούονται ανάμεσα σε συσκευές.

Οι mutations εκτελούνται με dependency graph ή per-entity σειρά. Αν αποτύχει η δημιουργία parent, τα child operations δεν στέλνονται τυφλά.

Attachments και μεγάλα αρχεία

Οι φωτογραφίες και τα PDF δεν πρέπει να μπαίνουν ως base64 μέσα στην ίδια mutation. Αποθηκεύονται ως Blob όπου υποστηρίζεται και ανεβαίνουν με resumable, chunked process όταν επιστρέψει σύνδεση.

Χρειάζονται όρια, compression, hash και σαφές progress. Αν το browser storage πλησιάζει quota, ο χρήστης ενημερώνεται πριν τραβήξει άλλες φωτογραφίες. Τα ανεβασμένα attachments συνδέονται idempotently με την entity.

Ασφάλεια σε κοινόχρηστη συσκευή

Offline data παραμένουν στη συσκευή. Σε ευαίσθητη εφαρμογή, πρέπει να οριστεί τι επιτρέπεται να αποθηκευτεί, για πόσο καιρό και τι συμβαίνει στο logout. Η απλή διαγραφή UI state δεν αρκεί αν IndexedDB και Cache Storage διατηρούν δεδομένα.

Τα authorization permissions μπορούν να αλλάξουν ενώ η συσκευή είναι offline. Κατά τον συγχρονισμό, ο server επανελέγχει κάθε mutation. Το ότι η ενέργεια δημιουργήθηκε παλαιότερα δεν παρακάμπτει τα τρέχοντα permissions.

Δεν αποθηκεύουμε μακρόβια secrets στον browser. Τα tokens έχουν ασφαλή διαχείριση και τα queued payloads περιορίζονται στα αναγκαία δεδομένα.

UX που δείχνει την αλήθεια

Ο χρήστης πρέπει να βλέπει αν είναι offline, πόσες αλλαγές εκκρεμούν και αν κάποια απέτυχε. Η ένδειξη «Αποθηκεύτηκε» διαχωρίζεται από «Συγχρονίστηκε».

Υπάρχει sync center με pending, syncing, conflict και failed items, command retry και κατανοητό reason. Τα errors δεν εξαφανίζονται μετά από toast. Σε critical workflow, η εφαρμογή δεν αφήνει τον χρήστη να κλείσει χωρίς να γνωρίζει ότι παραμένουν μη συγχρονισμένα δεδομένα.

Πώς το υλοποιεί η Ai Foundry

Στην Ai Foundry ξεκινάμε από τις πραγματικές offline εργασίες και διαχωρίζουμε ποια reads, writes και approvals μπορούν να γίνουν χωρίς σύνδεση. Σχεδιάζουμε IndexedDB schema, outbox και version protocol μαζί με το backend, ώστε ο συγχρονισμός να είναι μέρος του domain και όχι πρόσθετο script.

Στις PWA και web εφαρμογές που αναπτύσσουμε, χρησιμοποιούμε idempotent operation IDs, conflict detection, dependency-aware queues και καθαρά sync states. Δοκιμάζουμε airplane mode, διακοπή στη μέση request, δύο συσκευές, logout και storage eviction. Έτσι ο χρήστης γνωρίζει πάντα αν η εργασία αποθηκεύτηκε τοπικά, συγχρονίστηκε ή χρειάζεται απόφαση.

Checklist

  • Έχει κάθε resource κατάλληλη cache strategy;
  • Χρησιμοποιείται IndexedDB για structured offline state;
  • Γράφονται local state και outbox atomically;
  • Είναι idempotent κάθε mutation;
  • Υπάρχει fallback όταν Background Sync δεν υποστηρίζεται;
  • Ελέγχεται server version και εμφανίζονται conflicts;
  • Υποστηρίζονται temporary IDs και dependencies;
  • Επανελέγχει ο server authorization στο sync;
  • Ξεχωρίζει το UI το local save από το server sync;

Offline-first δεν σημαίνει απλώς ότι η εφαρμογή ανοίγει χωρίς internet. Σημαίνει ότι η εργασία του χρήστη παραμένει κατανοητή και ασφαλής μέχρι να συμφωνήσουν ξανά η συσκευή και ο server.

Πηγές