Die Offlinefähigkeit auf die Probe gestellt

Offlinemodus: Die Achillesferse der Progressive Web Apps?
Keine Kommentare

Die Offlinefähigkeit ist ein relevantes Teilproblem des Designs von Progressive Web Apps (PWAs). Mit der ApplicationCache-Schnittstelle waren Webapplikationen grundsätzlich früher schon offlinefähig. Doch erst mit der Integration von Service Workers erhalten Entwickler sehr feingranulare Steuerungsmöglichkeiten über das Kommunikationsverhalten der Applikation.

Mit Safari unterstützt nun auch der letzte der großen Browser die Service-Worker-Spezifikation des W3C. Was der XmlHttpRequest für Ajax war, sind Service Worker und das Web App Manifest für Progressive Web Apps. Bei einer PWA handelt es sich um eine Sonderform einer Webapplikation. Ihnen liegt der Ansatz des Progressive Enhancements zugrunde. Das bedeutet, dass eine solche Applikation auf nahezu jeder Umgebung in ihren Grundzügen lauffähig ist. Ihre wahre Stärke spielt eine PWA jedoch auf einem modernen Browser aus, der die erforderlichen Schnittstellen zur Verfügung stellt.

Progressive Web Apps

Beschäftigen Sie sich mit PWAs, werden Sie schnell auf Service Worker aufmerksam. Diese Schnittstelle ist jedoch nur ein Teil dessen, was eine PWA ausmacht. Um festzustellen, ob es sich bei einer Applikation um eine PWA handelt, beziehungsweise um zu prüfen, inwiefern die Konzepte von PWAs umgesetzt wurden, existieren verschiedene Werkzeuge, auf die Sie zurückgreifen können. Allen voran hat Google eine einfache Checkliste veröffentlicht, die Sie unter hier finden. Diese Liste weist insgesamt zehn Punkte auf, von denen einige auch die Offlinefähigkeit der Applikation betreffen. Diese zehn Punkte sind im Einzelnen:

  • Progressive: Wie bereits erwähnt, soll eine PWA auch in älteren Browsern funktionieren, sodass ein Benutzer zumindest die Grundfunktionalität der Applikation verwenden kann. Umgesetzt wird dieses Kriterium vor allem durch Feature-Detection, wenn es darum geht, bestimmte moderne Browserschnittstellen anzusprechen. Sind diese Schnittstellen nicht vorhanden, können entweder Polyfills verwendet werden, um die Schnittstelle zu simulieren, oder Sie deaktivieren das entsprechende Feature in der Applikation komplett.
  • Responsive: Eine PWA soll nicht nur auf jedem Browser, sondern auch auf jedem Gerät benutzbar sein. Das bedeutet, dass sich das Aussehen und die Kontrollelemente an die jeweilige Umgebung anpassen sollen. Diese Aufgabe wird vor allem durch CSS und entsprechende Mediaqueries erledigt.
  • Connectivity Independent: Dieser Punkt zielt auf die Offlinefähigkeit der Applikation ab. Eine PWA soll auch ohne aktive Netzwerkverbindung einsatzfähig sein.
  • App-like: Mit modernen Browserschnittstellen rücken PWAs näher an native Apps heran. Das betrifft sowohl mobile als auch Desktop-Apps.
  • Fresh: Bei Webapplikationen wird es als selbstverständlich erachtet, dass die Applikation stets auf dem aktuellen Stand ist. Diese Tatsache ergibt sich, da sie nicht installiert werden müssen. Bei einer PWA sieht die Situation etwas anders aus, da eine solche Applikation auf dem Zielsystem installiert werden kann und offlinefähig ist. Daraus ergeben sich Probleme, die bei der Konzeption bedacht werden müssen.
  • Safe: Einige Schnittstellen, auf die PWAs setzen, sind nur verfügbar, wenn die Applikation über eine verschlüsselte Verbindung, also über HTTPS ausgeliefert wird. Mittlerweile sollte die sichere Variante des HTTP-Protokolls jedoch weitestgehend Standard für Webapplikationen sein.
  • Discoverable: Suchmaschinen sollen PWAs als solche erkennen und gesondert indexieren können. Durch die Registrierung eines Service Workers und das Vorhandensein eines Web App Manifests ist eine PWA als solche identifizierbar. Microsoft beispielsweise macht sich diese Tatsache zunutze und nimmt bestimmte PWAs in den Microsoft Store auf, sodass sie auf Windows-Systemen installiert werden können.
  • Re-engageable: Mit der Service-Worker-Schnittstelle ist eine PWA in der Lage, Push Notifications an den Benutzer zu senden, obwohl dieser die Applikation nicht aktiv benutzt. Bei sparsamem Einsatz kann einem Benutzer hierdurch ein Mehrwert entstehen und die Aufmerksamkeit wieder auf die Applikation gelenkt werden.
  • Installable: Eine PWA kann, ähnlich wie eine native App, auf dem System installiert werden. Dadurch kann die Applikation schneller und einfacher vom Benutzer gestartet werden. In Kombination mit einem responsiven Design und der Offlinefähigkeit entsteht der Eindruck einer nahezu nativen Applikation.
  • Linkable: Trotz aller Features, die eine PWA einer nativen App ähnlicher machen, ist und bleibt eine PWA eine Webapplikation. Diese kann über einen Hyperlink adressiert, gebookmarkt und an Freunde und Kollegen versendet werden.

Der Grad der Umsetzung dieser PWA-Kriterien lässt sich nicht nur mithilfe solcher Checklisten überprüfen, sondern auch maschinell über verschiedene Werkzeuge. Eins der bekanntesten ist das von Google entwickelte Lighthouse. Dieses Hilfsmittel existiert sowohl als Webtool als auch als Browser-Plug-in. Mit solchen Werkzeugen lassen sich die meisten Aspekte recht gut überprüfen, die Offlinefähigkeit einer Applikation kann jedoch nur sehr eingeschränkt bewertet werden. Der Grund hierfür ist, dass für eine Überprüfung die Workflows der Applikation bekannt sein und verschiedene Aktionen ausgeführt werden müssen, um festzustellen, ob diese auch ohne Internetverbindung fehlerfrei arbeiten. Deshalb liegt es an den Ersteller der App, ein Konzept für die Offlinefähigkeit der Applikation zu erstellen und umzusetzen.

Frameworks und PWAs

Mittlerweile unterstützen alle großen JavaScript-Frameworks PWAs. So wird beispielsweise bei einer React-Applikation, die mit Create React App erzeugt wurde, automatisch ein Web App Manifest mit einer Standardkonfiguration sowie eine Datei mit dem Namen registerServiceWorker.js erzeugt. Diese Datei enthält eine Basiskonfiguration für den Service Worker, sodass die Dateien der Applikation vom Service Worker ausgeliefert werden, sobald sie einmal gecacht wurden. Ähnlich sieht die Situation bei Angular aus: Hier kann über das Paket @angular/pwa die Unterstützung von Service Workern aktiviert werden. Außerdem wird eine manifest.json-Datei erzeugt. Für Vue existiert ein Plug-in für die Vue CLI. Es sorgt, wie schon die Kommandozeilenwerkzeuge der anderen beiden Frameworks, dafür, dass eine Manifestdatei und ein Service Worker erzeugt werden. In allen drei Fällen bildet die Kombination aus Web App Manifest und Service Worker die Basis für eine offlinefähige PWA.

Offline First

In einer normalen Webapplikation ist die Abwesenheit einer stabilen Netzwerkverbindung stets ein Ausnahmezustand, auf den in der Regel mit einer Fehlermeldung reagiert wird. Implementieren Sie eine PWA, die offlinefähig sein soll, ändert sich an dieser Stelle die Betrachtung grundlegend. Der Offlinezustand wird zum Normalfall, der beim Entwurf der Applikation so selbstverständlich betrachtet werden muss wie der Onlinezustand. Das bedeutet, dass Sie bei der Arbeit an der Applikation nicht nur dafür sorgen müssen, dass die statischen Assets wie HTML, CSS, JavaScript sowie Mediendaten vom Browser vorgehalten werden, sondern auch die dynamischen Daten der Applikation behandelt werden. Offline First bedeutet für die Implementierung einer Applikation, dass Sie die Daten, die ein Benutzer in der Applikation generiert, auch zunächst dort vorhalten und, falls eine Verbindung zum Server besteht, diese dorthin schicken. Gleiches gilt für Daten, die in der Applikation konsumiert werden. Besteht eine Serververbindung, können die Daten vom Server geholt werden – diese werden dann nicht nur angezeigt, sondern auch in einem lokalen Speicher zwischengespeichert, sodass sie von dort zu einem späteren Zeitpunkt ausgelesen werden können, falls die Applikation nicht über eine Netzwerkverbindung verfügt. Ein wichtiges Instrument für diese Art der Zwischenspeicherung ist die IndexedDB des Browsers. Auf diese kann sowohl vom Service Worker als auch vom Hauptprozess des Browsers aus zugegriffen werden und Daten geschrieben und gelesen werden.

International PHP Conference

Migrating to PHP 7

by Stefan Priebsch (thePHP.cc)

A practical introduction to Kubernetes

by Robert Lemke (Flownative GmbH)


Andere Speichermechanismen des Browsers wie beispielsweise die LocalStorage kommen hier aus mehreren Gründen nicht infrage. Zunächst ist der Speicherplatz beschränkt, was die Möglichkeiten der Speicherung deutlich einschränkt. Außerdem können Sie nicht vom Service Worker aus auf die LocalStorage zugreifen.

IndexedDB – die Datenbank im Browser

Die Interaktion mit der IndexedDB erfolgt asynchron und ist in mehrere Schritte unterteilt. Zunächst müssen Sie die Datenbankverbindung aufbauen, indem Sie die Datenbank öffnen. Die Datenbanken der IndexedDB sind mit einer Versionsnummer versehen, die Sie verwenden können, um Änderungen durchzuführen. Bei einem Versionsupdate können Migrationsskripte ausgeführt werden, die dafür sorgen, dass die Struktur der Datenbank aktualisiert wird. Mit der Referenz auf die Datenbank erzeugen Sie anschließend einen Objektspeicher. In diesem werden die Objekte festgehalten. Auf dem Objektspeicher können Sie einen oder mehrere Indizes definieren, die das Durchsuchen bestimmter Eigenschaften der Objekte des Objektspeichers erheblich beschleunigen. Allerdings sollten Sie hier bedenken, dass sich zu viele Indizes in einer Datenbank negativ auf die Performance auswirken können – vor allem, wenn es um Schreiboperationen geht. Die Operationen der IndexedDB sind in Transaktionen gekapselt, sodass Sie mehrere atomare Operationen zu Gruppen zusammenfassen können, falls Sie sicherstellen möchten, dass entweder alle oder keine Operation der Gruppe angewendet wird. Die IndexedDB-Schnittstelle des Browsers nutzt Events und Callback-Funktionen für den Umgang mit Asynchronität. Diese Tatsache hat dazu geführt, dass die IndexedDB nicht gerade eine der beliebtesten Browserschnittstellen ist. Allerdings gibt es Abhilfe dafür in Form verschiedener Bibliotheken, die Sie für den Zugriff auf die IndexedDB verwenden können. Ein Beispiel für eine solche Bibliothek, die unter anderem vom Mozilla Developer Network empfohlen wird, ist Dexie.js. Dabei handelt es sich um einen relativ leichtgewichtigen Wrapper um IndexedDB, der die Asynchronität über Promises abbildet, die schließlich mit async/await in einer Applikation verwendet werden können. Auch die Syntax der Abfragen selbst ist einfach gehalten und führt zu kompaktem und gut lesbarem Code.

Going offline

Für die Implementierung einer offlinefähigen PWA gibt es zahlreiche Ansätze. Im Folgenden möchte ich Ihnen drei mögliche Lösungsszenarien vorstellen.

Datensynchronisierung über bestehende Lösungen

Eine der einfachsten Varianten besteht darin, eine offlinefähige Datenbank auf Clientseite zu verwenden. Als konkrete Lösung kann hier PouchDB verwendet werden. Diese Datenbank orientiert sich an der Schnittstelle von CouchDB, ist als Open-Source-Projekt komplett in JavaScript geschrieben. Die Verwendung der Datenbank gestaltet sich ähnlich einfach wie die Arbeit mit Dexie.js. Ihnen stehen eine Reihe von Methoden zum Erstellen, Editieren, Abfragen und Löschen von Datensätzen zur Verfügung. PouchDB läuft in verschiedensten Umgebungen wie dem Browser, aber auch unter Node.js. Für die Implementierung einer PWA ist jedoch hauptsächlich der Browser als Umgebung relevant. Befindet sich dieser im Offlinezustand, werden die Daten lokal vorgehalten. Sobald wieder eine Verbindung besteht, können Sie mit einem Aufruf der sync-Methode den Datenbestand mit einer CouchDB oder PouchDB synchronisieren, die auf einem Server ausgeführt wird. Bei dieser Lösung müssen Sie sich als Entwickler nicht selbst um die Synchronisierung kümmern, da die Datenbanklösung Ihnen dies abnimmt.

Synchronisierung über den Service Worker

Eine weitere Lösung für den Umgang mit Offlinezuständen ist die Umsetzung der Synchronisierungslogik im Service Worker. Nach dem Progressive-Enhancement-Grundsatz funktioniert die Applikation auf jeden Fall. Wird der Service Worker nicht erfolgreich registriert, kann die Applikation nicht offline genutzt werden. Das liegt zum einen daran, dass der Service Worker die statischen Assets nicht ausliefern kann und zum anderen die dynamische Serverkommunikation nicht abfangen und behandeln kann. Wurde der Service Worker erfolgreich registriert, können Sie die einzelnen Anfragen an den Server abfangen. Besteht eine aktive Internetverbindung, werden sowohl die lesenden als auch die schreibenden Anfragen direkt an den Server weitergeleitet. Ergibt die Prüfung, dass der Browser über keine Verbindung zum Server verfügt, wird die Anfrage durch den Service Worker beantwortet. Dabei müssen Sie zwischen lesenden und schreibenden Anfragen unterscheiden. Um lesende Anfragen an einen Server beantworten zu können, müssen Sie den aktuellen Datenstand lokal vorhalten. Hierfür bietet sich die IndexedDB an. Sobald der Browser im Onlinezustand Daten vom Server abfragt, sorgt der Service Worker dafür, dass die Daten in die IndexedDB gespeichert werden. Fragt der Benutzer die Daten im Offlinezustand erneut ab, wird die zuletzt gespeicherte lokale Kopie ausgeliefert. Ein Nachteil bei dieser Umsetzung ist, dass dem Benutzer auf diese Weise ein potenziell veralteter Datenstand präsentiert wird. In den meisten Fällen ist des jedoch besser, als keine Daten anzuzeigen.

Bei schreibenden Zugriffen ist die Situation ähnlich. Besteht keine Verbindung, müssen die Schreiboperationen zwischengespeichert werden. Erst wenn wieder eine Verbindung besteht, können die Informationen zum Server geschickt werden. Bei der konkreten Implementierung gibt es den Aufwand betreffend unterschiedliche Abstufungen. Die am wenigsten aufwendige Lösung sorgt lediglich dafür, dass die Schreiboperationen lokal vorgehalten werden, auch hier können Sie einen Objektspeicher in der IndexedDB verwenden. Bei dieser leichtgewichtigen Implementierung wirkt sich eine Schreiboperation nicht auf den Datenbestand aus, den der Service Worker zur Beantwortung lesender Anfragen vorhält. Um dem Benutzer jedoch die Auswirkungen seiner Aktionen darzustellen, sollten Sie dafür sorgen, dass der vorgehaltene Datenstand entsprechend aktualisiert wird. Kann zu einem späteren Zeitpunkt wieder eine Verbindung zum Server hergestellt werden, werden die schreibenden Operationen an den Server übermittelt. Anschließend sollte auch der modifizierte lokale Datenstand verworfen und neu angefragt werden. Durch diese Vorgehensweise können Inkonsistenzen vermieden werden, die durch parallele Modifikationen entstehen.

Da der Service Worker die Synchronisierung zwischen Client und Server übernimmt, muss die eigentliche JavaScript-Applikation, die im Browser ausgeführt wird, keine Kenntnis über den Online- oder Offlinezustand haben. Sie können also wie gewohnt entwickeln. Ein entscheidender Nachteil, der durch diese Architektur entsteht, ist, dass Sie eigentlich zwei Clientapplikationen entwickeln. Eine Applikation, mit der der Benutzer interagiert, und eine weitere, die die Verwaltung des Service Workers und die Synchronisierung der Datenstände übernimmt.

Offlinezustand als Bestandteil der Applikation

Die dritte Lösung für den Umgang mit offline-PWAs besteht darin, dass die JavaScript-Applikation selbst unter den Offline-First-Gesichtspunkten entwickelt wird und die Aufgaben der lokalen Datenhaltung, wie bei der Service-Worker-Lösung, selbst übernimmt. Bei der Vorstellung dieses Ansatzes werde ich auf einige Implementierungsdetails eingehen, die Sie auch für eine Service-Worker-Lösung einsetzen können.

Für die konkrete Betrachtung dieses Lösungsszenarios gehen wir von einer React-Applikation mit Redux und redux-observable aus. Ähnliche Architekturen finden Sie in Vue in Kombination mit Vuex und Angular mit ngRx. Bei der Flux-Architektur, die diesen Implementierungen zugrunde liegt, werden die Daten der Applikation in einem Store vorgehalten. Modifiziert wird der Datenstand der Applikation über sogenannte Actions, das sind JavaScript-Objekte, die die Änderungen beschreiben und vom Reducer verarbeitet werden. Diese Instanz ist für die Modifikationen am Store verantwortlich. Im Fall von Redux übernimmt redux-observable die Behandlung asynchroner Seiteneffekte. Ein typischer Seiteneffekt ist die Kommunikation mit einem Server. Die Strukturen, die für die Seiteneffekte verantwortlich sind, heißen Epics und sind die Stelle, an der Sie sich um die Offlinefähigkeit Ihrer Applikation kümmern müssen.

Lesende Zugriffe

Listing 1 zeigt ein Beispiel, bei dem Daten vom Server gelesen werden.

const getDataEpic = action =>
  action.pipe(
    ofType(GET_DATA),
    mergeMap(() => {
      if (navigator.onLine) {
        from(fetch('/api')).pipe(
          mergeMap(response => from(response.json())),
          mergeMap(response =>
            merge(
              of(response).pipe(map(res => getDataSuccessAction(res))),
              of(storeDataLocallyAction(response)),
            ),
          ),
        );
      } else {
        const db = getDb();
        return from(db.items.toArray()).pipe(
          map(items => getDataSuccessAction(items)),
        );
      }
    }),
  );

const storeResponseLocallyEpic = action =>
  action.pipe(
    ofType(STORE_DATA_LOCALLY),
    tap(async data => {
      await getDb().items.add(data);
    }),
  );

Wie Sie im Quellcode sehen können, basiert redux-observable auf der Bibliothek RxJS. Hier unterscheiden sich die React/Redux- und die Angular-Implementierung mit ngRx wenig voneinander. Im Quellcode von Listing 1 sehen Sie im getDataEpic, dass auf eine Action vom Typ GET_DATA reagiert wird. Über die Abfrage navigator.onLine prüfen Sie, ob eine Netzwerkverbindung besteht. Ist das der Fall, laden Sie über das Fetch-API des Browsers die Daten. Trifft die Antwort des Servers ein, müssen Sie zunächst die JSON-Daten der Nachricht extrahieren und anschließend zwei weitere Actions dispatchen. Die Action, die die Funktion getDataSuccessAction zurückgibt, sorgt dafür, dass der Reducer die Daten korrekt in den Store einfügen kann. Die storeDataLocallyAction ist dafür verantwortlich, dass die eben erhaltenen Daten auch lokal vorgehalten werden. Das storeResponseLocallyEpic kümmert sich um das lokale Speichern der Daten. Hierfür wird über eine Hilfsfunktion eine Referenz auf die Dexie-Datenbank erzeugt und die Daten anschließend dort gespeichert.

Hat die Prüfung im getDataEpic ergeben, dass keine Verbindung besteht, wird ebenfalls eine Datenbankreferenz erzeugt, die hinterlegten Daten ausgelesen und wiederum die getDataSuccessAction ausgelöst.

Schreibende Zugriffe

Beim Schreiben der Daten gehen Sie auf eine ähnliche Art vor. In Listing 2 finden Sie den Code des zugehörigen Epics.

const createItemEpic = action =>
  action.pipe(
    ofType(CREATE),
    mergeMap(action => {
      if (navigator.onLine) {
        from(
          fetch({
            url: '/api',
            method: 'POST',
            body: JSON.stringify(action.payload),
          }),
        ).pipe(
          mergeMap(response => from(response.json())),
          mergeMap(response => of(createSuccessAction(response))),
        );
      } else {
        of(action.payload).pipe(
          merge(
            of(action.payload).pipe(
              map(data => addOperationAction({ type: 'craete', data })),
            ),
            of(action.payload).pipe(map(data => createSuccessAction(data))),
          ),
        );
      }
    }),
  );

Im createItemEpic prüfen Sie im ersten Schritt, ob eine Netzwerkverbindung besteht. Ist das der Fall, senden Sie die zu speichernden Daten über das fetch API mit einer POST-Anfrage an den Server. Als Antwort erhalten Sie im Normalfall den neu erstellten Datensatz, diesen müssen Sie zunächst decodieren und können ihn anschließend in die createSuccessAction verpacken, damit der Reducer den neuen Datensatz korrekt einfügen kann.

Ist der Browser offline, verzweigen Sie den Programmfluss, indem Sie zwei neue Actions dispatchen. Die addOperationAction sorgt dafür, dass ein weiteres Epic die Repräsentation der Aktion in die lokale IndexedDB speichert. Diese Aktionen werden, sobald eine Netzwerkverbindung besteht, zum Server gesendet. Die zweite Action, die Sie dispatchen, ist die createSuccessAction. Dies simuliert für die Applikation, dass der Speichervorgang erfolgreich war und die Applikation funktioniert, als hätte sie eine Netzwerkverbindung.

Umgang mit Konflikten

Egal, ob Sie sich für die Service-Worker-Implementierung oder die Umsetzung der Synchronisierung in der Applikation entscheiden: Sie müssen eine Strategie für den Umgang mit Konflikten entwickeln. Bei offlinefähigen Applikationen treten diese mit einer höheren Wahrscheinlichkeit ein als bei Applikationen, die stets online sind. Besteht zu jedem Zeitpunkt im Lebenszyklus der Applikation eine Netzwerkverbindung, haben Sie die Möglichkeit, beispielsweise mit WebSockets Änderungen an den Daten nahezu in Echtzeit an alle angeschlossenen Clients verteilen können und diese so immer auf einem aktuellen Datenstand arbeiten. Bei einer Applikation im Offlinezustand arbeiten Sie auf einer potenziell veralteten Version der Daten, was zu Problemen führen kann. Nehmen Sie an, Sie sind Benutzer einer PWA und sind gerade mit der Bahn unterwegs. Die Verbindung ist gerade wieder abgebrochen, weil Sie mit Tempo 300 durch ein Tunnel fahren. Da Ihre Applikation jedoch offlinefähig ist, können Sie weiterhin arbeiten und modifizieren einen Datensatz. Parallel dazu löscht Ihr Kollege, der gerade im Büro sitzt, den gleichen Datensatz. Sobald Sie wieder über eine stabile Verbindung verfügen, versucht die Applikation, die Daten mit dem Server zu synchronisieren. Die Operationen, die Sie im Offlinezustand in die lokale IndexedDB gespeichert haben, werden nacheinander an den Server gesendet und dort angewendet. Sobald der Server versucht, Ihre Modifikation anzuwenden, wird ein Konflikt festgestellt: Sie versuchen einen nicht existierenden Datensatz zu modifizieren – doch was nun? An dieser Stelle sollte die von Ihnen gewählte Strategie zur Konfliktauflösung greifen. Hier haben Sie wieder eine Vielzahl von Möglichkeiten, die von der sehr einfachen Umsetzung bis zu hochkomplexen Algorithmen reichen. Hier einige Beispiele:

    • Der Letzte gewinnt: Die letzte Schreiboperation überschreibt die vorhergehenden. Angewendet auf die Datenmodifikation im Zug bedeutet das, dass Ihre Modifikation die Löschoperation Ihres Kollegen überschreibt und der Datensatz quasi wiederhergestellt wird.
    • Merge: Eine Strategie, die beispielsweise das Versionskontrollsystem git verfolgt. Aufgrund der vorhandenen Informationen versucht das System Konflikte aufzulösen. An bestimmten Punkten gelangen jedoch selbst ausgefeilte Algorithmen an ihre Grenzen und fallen auf die nächste Strategie zurück.
    • Der Benutzer entscheidet: Tritt ein Konflikt auf, wird der Benutzer darüber informiert und erhält die Möglichkeit, den Konflikt aufzulösen. Das bedeutet, dass die Applikation im Fall der Modifikation einen Dialog anzeigt, in dem Sie die Wahl haben zwischen „Die Daten werden verworfen“ oder „Der Datensatz wird wiederhergestellt und modifiziert“.

Egal, für welche Strategie Sie sich entscheiden, sollten Sie stets dafür sorgen, dass die Änderungen zu jedem Zeitpunkt nachvollzogen werden können. Das erreichen Sie, indem Sie die Konfliktauflösungen explizit protokollieren. Außerdem sollten Sie Ihre Benutzer informiert halten. Tritt ein Konflikt auf, sollten Sie zumindest eine kurze Information anzeigen, auch wenn der Konflikt automatisch aufgelöst wurde.

Das Background Sync API

Die Spezifikation der Service Worker orientiert sich an der Funktionalität, die eine PWA zur Verfügung stellen soll. So ist der primäre Einsatzzweck eines Service Workers die Proxy-Funktion zwischen Client und Server, die die Offlinefähigkeit ermöglicht. Mit Push Notifications wird eine PWA re-engageable. Mit diesem Feature kann eine PWA dem Benutzer Benachrichtigungen anzeigen, obwohl die Applikation nicht im Vordergrund genutzt wird. Zur Unterstützung der Verbindungsunabhängigkeit existiert neben der Kernfunktionalität des Service Workers noch eine weitere Schnittstelle: das Background Sync API. Diese Browserschnittstelle ist vor allem für die Offlinevariante interessant, bei der der Service Worker die Kontrolle über den Synchronisierungsprozess übernimmt. Ein Problem, das diese Schnittstelle aktuell noch hat, ist ihre schlechte Verbreitung. Von den großen Browsern ist sie aktuell nur in Chrome implementiert. Bei Edge und Firefox befindet sie sich gerade in der Entwicklung. Es ist also absehbar, dass sich das Background-Sync-Feature in naher Zukunft deutlich besser verbreiten wird. Der entscheidende Vorteil dieser Schnittstelle ist, dass Nachrichten lokal vorgehalten werden, wenn sich das Gerät im Offlinemodus befindet. Sobald eine Verbindung besteht, werden die zu sendenden Nachrichten verschickt, unabhängig davon, ob die Applikation aktuell ausgeführt wird oder nicht. Wie bei allen anderen Schnittstellen, die Sie in einer PWA nutzen, sollten Sie auch hier zunächst prüfen, ob das Feature im Browser existiert. Ist dies nicht der Fall, sollten Sie Ihren Benutzern eine alternative Möglichkeit für die Synchronisierung der Daten der Applikation anbieten. In Listing 3 sehen Sie ein einfaches Beispiel für eine solche Synchronisierung.

if ('serviceWorker' in navigator && 'SyncManager' in window) {
  navigator.serviceWorker.ready
    .then(registration => {
      return registration.sync.register('backgroundSync');
    })
    .catch(() => {
      sendData();
    });
} else {
  sendData();
}

self.addEventListener('sync', () => {
  if (event.tag === 'backgroundSync') {
    event.waitUntil(syncData());
  }
});

Im Beispielcode sehen Sie, dass, nachdem der Service Worker bereit ist, die Registrierung des Background Sync API erfolgt. Wird die Schnittstelle nicht unterstützt oder schlägt die Registrierung fehl, wird eine Polyfill-Lösung angeboten. Über den Aufruf der addEventListener-Methode registrieren Sie eine Callback-Funktion auf das sync-Event. Die Callback-Funktion wird ausgeführt, sobald der Service Worker eine stabile Netzwerkverbindung entdeckt. Bei der Abarbeitung der Funktion wartet der Service Worker dann so lange, bis die Methode syncData abgeschlossen ist.

Fazit

Progressive Web Apps sind ganz klar auf dem Vormarsch. Große Unternehmen wie beispielsweise Google oder Microsoft unterstützen die Entwicklung mit Dokumentation und Werkzeugen und integrieren fortlaufend neue Features in ihre Browser, die die PWAs immer mächtiger werden lassen.

Ein wichtiger Aspekt ist die Unabhängigkeit von der Netzwerkverbindung. Dieser lässt sich jedoch mit Hilfsmitteln und Checklisten eher schlecht überprüfen, sodass Sie an dieser Stelle auf sich gestellt sind. Bei der Umsetzung einer offlinefähigen PWA können Sie jedoch auf eine Vielzahl von Bibliotheken zurückgreifen, die Ihnen bereits viel Arbeit abnehmen.

Traditionelle Webapplikationen sind auf stabile Netzwerkverbindungen angewiesen, um ihren vollen Funktionsumfang auszuspielen. Ist Ihre PWA allerdings gut umgesetzt, stellt sie eine echte Alternative zu einer nativen App dar, egal für welche Plattform. Die Offlinefähigkeit spielt hier eine herausragende Rolle, da sie lange Zeit die Achillesferse von Webapplikationen war.

PHP Magazin

Entwickler MagazinDieser Artikel ist im PHP Magazin erschienen. Das PHP Magazin deckt ein breites Spektrum an Themen ab, die für die erfolgreiche Webentwicklung unerlässlich sind.

Natürlich können Sie das PHP Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -