Der Browser wird zur Datenbank
loading="lazy" width="400px">Thick-Client-Comeback?Avesun | shutterstock.com
Moderne Browser sind das Ergebnis jahrelanger praktischer Entwicklungs- und Testarbeit. Aber: Benötigt ein Browser Daten, muss er diese trotzdem bei einem Server anfragen. Klicken und Warten sind die Folgen der anhaltenden Abhängigkeit vom Backend für einen dauerhaften State. Allerdings entwickelt sich inzwischen ein Alternativansatz. Die Idee dahinter: Eine relationale Datenbank mit einem Teil der Daten direkt in den Browser einzubetten und über eine Synchronisations-Engine sicherzustellen, dass sämtliche Informationen konsistent sind. Der lokale Data Store, mit dem der Browser interagiert, wird im Hintergrund mit dem Server synchronisiert. So ist auf Frontend-Seite sofortige Interaktivität zu verbuchen, während gleichzeitig die Symmetrie mit dem Backend aufrechterhalten wird.
Diese Browser der nächsten Generation verfügen also nicht nur über einen temporären Cache, sondern einen widerstandsfähig(er)en Status of Record. Dieser Entwicklung haben mehrere Lösungen den Weg geebnet. In diesem Artikel werfen wir einen Blick auf die Evolution von Local-First-Datenbanklösungen der nächsten Generation.
PGLite
Der Star der Stunde in diesem Segment ist die SQL-Datenbank PGLite. Vor kurzem haben wir darüber geschrieben, wie WinterTC uns dem Traum vom isomorphen JavaScript näherbringt, bei dem Server und Client identisch sind. Der nächste Schritt besteht darin, eine ähnliche Homogenität über Data Stores hinweg zu erreichen. Das ist erst seit kurzem mit Web Assembly (Wasm) möglich: Die Technologie führt eine voll funktionsfähige PostgresSQL-Instanz im Browser aus. Diese Instanz, die Wasm-Datenbank, ist PGLite. Zwar kommt man mit SQLite einer Enterprise-Datenbank im Browser recht nahe, PGLite ist allerdings dieselbe Datenbank, die man auch im Rechenzentrum ausführen würde. Mit ihr entfällt ein wesentlicher Reibungspunkt, nämlich unterschiedliche Dialekte. Die Wasm-Laufzeitumgebung macht PGLite zu einer schlanken Version der eigentlichen Postgres-Codebasis. Anders ausgedrückt: Wir sind einem Thick Client näher als je zuvor.
Selbst wenn API und Implementierung identisch sind, können wir nicht einfach einen Teil der gesamten Datenbank im Browser erstellen. Diese wäre einerseits zu groß und andererseits unsicher. Stattdessen sollen nur die Daten genutzt werden, die der jeweilige User für eine bestimmte Session benötigt. Eine Idee, um das umzusetzen ist die „Shape-basierte“ Synchronisierung, die durch ElectricSQL (was auch die treibende Kraft hinter PGLite ist) populär wurde. Eine Shape ist vergleichbar mit einer „View“: Sie nutzt eine oder mehrere Queries, um die Client-seitige Datenbank mit einem Segment der relevanten Daten zu füllen. Über die vollständige Form verfügt lediglich der Server. Der Client abonniert einen bestimmten Shape innerhalb des Servers (zum Beispiel: SELECT * FROM issues WHERE assigned_to = ‚me‘).
Die Synchronisation unter der Haube basiert auf dem nativen Postgres-Protokoll Logical Replication. Die Synchronisierungs-Engine ist ein Middleware-Consumer: Sie überwacht das Write-Ahead-Log der Datenbank (den Echtzeit-Stream aller Änderungen, die auf dem Server stattfinden). Tritt eine Änderung auf, die mit der vom Client abonnierten Shape übereinstimmt, überträgt die Engine diese spezifische Aktualisierung über den WebSocket im Hintergrund an die PGLite-Instanz des Browsers. Diese Aktivität ist bidirektional. Lokale Schreibvorgänge werden sofort auf die Benutzeroberfläche angewendet, dann in eine Warteschlange gestellt und zurück zur zentralen Datenbank gestreamt – während die Engine die erforderliche Logik zur Konfliktlösung übernimmt. In den Anfangstagen von Progressive Web Apps (PWAs) musste man noch imperativen Code schreiben, um fehlgeschlagene Requests wiederzugeben, wenn der Benutzer wieder online war. Diese Technik funktionierte zwar, war aber anfällig und bot keine gute Entwicklererfahrung. Moderne Synchronisierungs-Engines bieten diesbezüglich eine elegantere Lösung.
Wenn Sie jetzt an die Vergänglichkeit des Browser Caches denken – stellen Sie sich die Architektur von Git vor:
Die Remote-Datenbank (GitHub) ist die Source of Truth,
die lokale Datenbank (Ihr Laptop) enthält die Arbeitsdaten.
Leert ein Benutzer seinen Browser-Cache, sind seine Daten nicht verloren: Er löscht damit lediglich sein lokales Repository. Meldet er sich erneut an, fährt die Synchronisierungs-Engine im Wesentlichen einen git clone und zieht seinen „Data Shape“ zurück auf das Device.
Wenn zwei Benutzer genau dieselben Daten offline bearbeiten, würde das in einer Standarddatenbank dazu führen, dass der letzte Schreibvorgang den vorherigen überschreibt. Es braucht also eine raffinierte Synchronisierungslogik, um eine Vielzahl von Clients zu verarbeiten, die mit Shapes arbeiten und ihre Synchronisierungsprozesse kontinuierlich in den zentralen Data Store übertragen. An dieser Stelle kommen CRDTs (Conflict-free replicated Data Types) ins Spiel. Hierbei handelt es sich um eine Reihe von mathematischen Konstrukten mit praktischen Anwendungen für das Synchronisierungsproblem. Diese Datenstrukturen (wie eine Map oder eine List) sind darauf konzipiert, mathematisch zusammengeführt zu werden. Dank der CRDT-Logik gehen die offline durchgeführten Änderungen der User nicht verloren, sondern werden nahtlos kombiniert, sobald die Verbindung wieder steht.
Das könnte man auch als eine ganze Menge zusätzlicher Architektur ansehen: zwei Datenbanken, eine Synchronisierungs-Engine und Shapes, die auf die Duplizierung einer SELECT-Anweisung hinauslaufen, die auf dem Server leben sollte. Gewissermaßen wird so das klassische Distributed-Computing-Problem auf den Data Store aufgeteilt, um zu vermeiden, Daten umständlich laden zu müssen. Das geht zu Lasten des bekannten JSON- und REST-Musters. Trotzdem lohnt sich dieser neue Ansatz. Denn er stellt in Aussicht, was Webentwickler seit zwanzig Jahren herbeisehnen: eine Desktop-Class-Experience. Durch die Interaktion mit lokalen Daten lässt sich eine Reaktionsfähigkeit auf der Benutzeroberfläche erreichen, die über direkte Netzwerkaufrufe schlicht nicht möglich ist. Eine vollwertige PostgreSQL-Instanz einzubinden, erspart zudem halbgare Lösungen wie lokale Caches.
Darüber hinaus bietet der Ansatz einen weiteren potenziellen Benefit mit Blick auf die Developer Experience: Mit der Backend-API entfällt eine ganze Kopplungsebene, mit der sich Webentwickler bislang auseinandersetzen mussten. Dadurch wird vermieden, Client-seitige Daten manuell in ein Transportformat, zurück in ein Data-Store-Format und anschließend erneut in ein Transportformat übersetzen zu müssen (ein Ideal, auf das auch Frameworks wie HTMX ausgerichtet sind – wenn auch mit einem anderen Ansatz). Im Idealfall eliminiert dieser Local-First-Ansatz das JSON-Marshaling: Wir schreiben einfach die gewünschte SQL-Anweisung, die Synchronisierungs-Engine übernimmt automatisch den Transport (basierend auf definierten Regeln). Und statt einen GET /todos-Endpunkt zu erstellen, schreiben wir einfach einen SQL-Request innerhalb unserer Komponente: SELECT * FROM todos.
IndexedDB, Wasm & OPFS
PGLite ist zwar eine faszinierende Technologie, aber längst nicht die einzige Entwicklung im Bereich der Local-First-Architektur. Vielmehr ist sie Teil einer größeren Konstellation von Technologien. IndexedDB etwa will dem Browser ebenfalls eine Datenbank zur Verfügung zu stellen (und kann auch eingesetzt werden, um eine PGLite-Instanz zu supporten), wird jedoch durch eine notorisch klobige API und Performance-Limitationen behindert. IndexedDB ähnelt eher einem Dateisystem-Bucket als einer Datenbank-Engine und bietet keinen Support für komplexe Queries, Verknüpfungen oder Einschränkungen. Um damit etwas zu erreichen, ist es nötig, die Datenbanklogik selbst in JavaScript zu schreiben und Daten in die Memory zu ziehen, um sie zu filtern. Das wirkt sich jedoch nachteilig auf die Performance aus, weswegen IndexedDB für reale Anwendungsfälle eher unpraktisch ist.
Die Grundlage der modernen Local-First-Ära wurde durch Wasm und das Origin Private File System (OPFS) geschaffen. Dank dieser Technologien müssen wir Datenbanken in JavaScript nicht mehr neu erfinden, sondern können bewährte Engines direkt in den Client portieren. Auch wenn es sich um obskure Browser-Spezifikationen handelt, ist OPFS für die moderne Local-First-Architektur von großer Bedeutung: Während Wasm die Laufzeitumgebung bereitstellt, sorgt OPFS für das Dateisystem – und ermöglicht dem Browser endlich direkten, performanten Zugriff auf die Festplatte der Benutzer.
Im Gegensatz zu IndexedDB, das dazu zwingt, ganze Dateien oder Objekte zu lesen/schreiben, ermöglicht OPFS Random-Access-Schreibvorgänge. Das befähigt Datenbanken wie PGLite etwa dazu, 4 KB innerhalb einer 1-GB-Datei zu ändern, ohne diese komplett neu zu schreiben. OPFS ist die Komponente, die dafür sorgt, dass Datenbanken auf Serverebene im Browser mit nahezu nativer Leistung ausgeführt werden können.
RxDB
Wenn PGLite der Vorreiter der „SQL on the Client”-Bewegung ist, dann ist RxDB (Reactive Database) das NoSQL-Äquivalent. Es basiert auf PouchDB, das seit vielen Jahren in der Praxis eingesetzt wird. Während PGLite darauf fokussiert, die Serverstruktur auf den Client zu übertragen, nimmt RxDB die Benutzeroberfläche und Reactivity in den Fokus. In einer Standard-Datenbank führen Sie eine Abfrage aus und erhalten anschließend ein Ergebnis.
Im Fall von RxDB abonnieren Sie eine Query:
// In RxDB, the database IS the state manager
db.todos.find().$.subscribe(todos => {
render(todos);
});
Wenn die Synchronisierungs-Engine neue Daten vom Server überträgt, wird die Benutzeroberfläche sofort aktualisiert. Weil die Datenbank dabei selbst die Quelle der reaktiven Wahrheit ist, sind keine State-Management-Bibliotheken wie Redux oder Pinia mehr erforderlich.
Die Local-First-Zukunft beginnt
Der Browser ist seinem Status als „Dokumentenbetrachter“ und „simples Terminal“ entwachsen. Local-First-Architekturen stellen eine radikale Abkehr von den vertrauten Lösungen auf REST-Basis dar – und bringen ihre eigenen Komplexitäten mit sich. Mit der Vereinheitlichung der Laufzeitumgebung (WinterTC) sowie dem Aufkommen Enterprise-tauglicher, lokaler Datenbanken (PGlite und RxDB) könnte der Browser sich künftig zu einer vollwertigen Anwendungsplattform entwickeln.
Dieser Shift dürfte sich allerdings nicht im Hauruck-Verfahren vollziehen, schließlich sind alte Gewohnheiten in der Welt der Softwareentwicklung regelmäßig umfangreicher Ballast, der Zeit braucht, um abgeworfen zu werden. Nichtsdestotrotz könnte der Local-First- und Synchronisierungsansatz eines Tages JSON und REST vom Thron stoßen. Falls er sich auch in der Praxis bewähren kann. (fm)
Hier finden Sie den kompletten Artikel: