2 Punkte von GN⁺ 2024-04-05 | 1 Kommentare | Auf WhatsApp teilen
  • Das Sequin-Team hat sowohl React-SPAs als auch reines Phoenix LiveView ausprobiert und kam zu dem Schluss, dass die Kombination von LiveView und Svelte mit LiveSvelte am produktivsten ist
  • LiveView kann mit Server-State und inkrementellen DOM-Updates ohne SPA reichhaltige UIs bauen, doch sobald Client-State nötig wird, vermischen sich JS-Module, Hooks und LiveView-State, und die Grenzen verschwimmen
  • In reinem LiveView unterscheiden sich LiveView, LiveComponent und Component in Lebenszyklus, Props-Weitergabe und Kommunikationsweise, was Refactoring und State-Platzierung umständlicher macht als in React
  • LiveSvelte bietet eine klare bidirektionale Grenze: Elixir gibt Props nach unten weiter, Svelte schickt Events mit live.pushEvent nach oben
  • In dieser Kombination können Routing, Business-Logik und Datenaufbereitung auf dem Server bleiben, während Svelte UI-State, Animationen und bedingtes Rendering übernimmt und so die Komplexität einer SPA reduziert

Das Entwicklungsmodell von LiveView

  • Traditionelle serverseitig gerenderte Apps halten auf dem Server keinen State und rendern die Seite bei jeder Client-Anfrage neu
  • SPAs überlassen dem Client den Seitenaufbau, lesen und schreiben Daten über Backend-APIs und verwalten Client-State etwa mit Reacts useState
  • LiveView lässt den Server weiterhin die Seite rendern, hält dabei aber State, verarbeitet Frontend-Aktionen und aktualisiert das DOM inkrementell wie eine SPA
  • Die Komplexität von SPAs wächst, weil die Client-JS-App faktisch wie ein eigener Frontend-Microservice agiert
    • Wegen frontend-spezifischer Anforderungen nehmen Backend-Routes und Controller zu
    • Eine einzelne Anfrage kann onMount, API-Aufruf, Response-Parsing, Router, Authentifizierungs-Plug, Controller, Domain-Funktion und Response-Aufbereitungsschicht durchlaufen
  • Der Vorteil von LiveView liegt darin, auch ohne Frontend-Microservice eine reichhaltige Client-Erfahrung schaffen zu können
    • Datenbankabfragen können nahe an der Funktion liegen, die eine Tabelle rendert
    • Kommt eine neue Zeile hinzu, wird sie in die Tabelle gepusht, und LiveView aktualisiert den Client
    • So lassen sich Interaktionen bauen, die näher an einem stateful Frontend-Framework liegen als am klassischen Server-Rendering, das den gesamten State pro Anfrage neu aufbaut

Reibungspunkte in reinem LiveView

  • Clientseitiger State lässt sich nicht vermeiden

    • LiveView kann die Roundtrip-Latenz zwischen Server und Nutzer nicht vermeiden
    • Aufgaben wie Animationen, Tooltips, DOM-Elemente ein- und ausblenden oder Formularfelder deaktivieren müssen mitunter auf dem Client erledigt werden
    • In einem Formular mit zwei voneinander abhängigen Dropdowns ist es für eine gute UX nötig, direkt nach der Auswahl im ersten Dropdown das zweite sofort zu deaktivieren und es wieder zu aktivieren, nachdem der Server die neue Liste befüllt hat
    • Will man dieses Verhalten nur mit LiveView umsetzen, braucht man mehrere Konzepte zusammen
      • Mit dem JS module von LiveView wird das zweite Dropdown deaktiviert, wenn sich das erste ändert
      • Mit einem hook wird ein Event-Listener am zweiten Dropdown registriert, und vom Backend wird eine Aktion zum erneuten Aktivieren geschickt
      • Soll es nur bedingt wieder aktiviert werden, kann auch LiveView-State dazukommen
    • Wenn JS, Hooks und LiveView-State zusammen genutzt werden, existiert ein Teil des DOM-State außerhalb von LiveView, und es wird schwierig, ein konsistentes Muster zu etablieren
  • Andere State-Grenzen als in React

    • React verarbeitet die meisten Übergänge über die Kernkonzepte State und Actions, und die Grenze zwischen DOM-State und Component-State ist vergleichsweise klar
    • Da es keine Verzögerung zwischen Client-Aktion und Client-State gibt, kann React Aktionen und Übergänge über sein State-Paradigma behandeln
    • Da der State von LiveView serverseitig liegt, muss man die Latenz zwischen Client-Aktion und Server-State berücksichtigen
    • Beim Beispiel eines Eingabefelds durchläuft in React der State-Pfad die Eingabe, bevor das Zeichen erscheint, und die Komponente rendert bei jedem Tastendruck neu
    • LiveView entspricht eher einem Modell, in dem der Nutzer zuerst das Eingabefeld verändert und der Server-State dies kurz darauf erfährt und darauf reagiert
    • Bei kaum spürbarer Latenz wirkt es wie React, doch je größer die Latenz wird, desto deutlicher unterscheiden sich die beiden Modelle

Komplexität durch drei Component-Typen

  • In LiveView gibt es drei Component-Typen: LiveViews, LiveComponents und Components
  • LiveViews und LiveComponents ähneln stateful Components in React, Components eher funktionalen Components
  • LiveView ist immer die oberste Parent-Component; darunter werden LiveComponents und Components gerendert
  • In React kann man durch Hinzufügen oder Entfernen des useState-Hooks leicht zwischen stateful und funktionalen Components wechseln
    • Beide erhalten Props auf dieselbe Weise
    • Abgesehen vom State ist ihr Funktionsumfang gleich
    • Auch das Registrieren von DOM-Events und die Reaktion darauf funktionieren gleich
  • In LiveView unterscheiden sich die drei Component-Typen stark, was das Refactoring einer LiveView zu einer LiveComponent umständlich macht
    • LiveViews und LiveComponents haben unterschiedliche Syntax für Rendering und Props-Übergabe
    • Ihre Lebenszyklen unterscheiden sich
    • Die Kommunikationsoptionen sind verschieden: Für LiveViews nutzt man send, für LiveComponents send_update
    • LiveComponents sind keine Prozesse und können daher nicht wie LiveViews mit anderen Teilen des Systems interagieren

Einschränkungen von LiveComponent

  • LiveView ist ein Elixir-Prozess und hat daher pid, State und Inbox und kann sich wie andere Prozesse im Elixir/OTP-System verhalten
  • In LiveView kann man Pub/Sub nutzen, um systemweite Änderungen zu abonnieren
  • LiveComponent ist kein eigener Prozess, sondern ein Modul, das von LiveView aufgerufen wird
  • Da der Parent-LiveView-Prozess den State der untergeordneten Components hält, muss LiveView auch das Message-Routing zu den Child-LiveComponents übernehmen
  • Damit LiveComponents unabhängiges State-Management und Action-Handling bieten könnten, müssten sie jeweils Prozesse sein; das aktuelle Modell ist das aber nicht
  • In der Praxis gibt es keinen guten Weg, Events oder Actions an eine LiveComponent zu senden, selbst wenn man das möchte, und es wird unklar, ob send_update für die Übergabe von Actions oder für State-Patches genutzt werden soll
    • Nutzt man es für State-Patches, muss man auch überlegen, wie man in der update-Klausel zwischen Mount und Update unterscheidet

Die Grenze, die LiveSvelte schafft

  • LiveSvelte ermöglicht es, Svelte-Components in LiveView zu rendern
  • Innerhalb von render in LiveView kann man Elixir-Datenstrukturen als plain map fürs Frontend enkodieren und als Props an eine Svelte-Component übergeben
  • Svelte-Components empfangen Props über export let und kommunizieren über das von LiveSvelte bereitgestellte live-Prop mit LiveView
  • Auf der Svelte-Seite kann man mit live.pushEvent Events an den Server senden
    • Im Beispiel sendet ein Reactive Block von Svelte bei Änderungen an form das Event "form_updated" an den Server
    • LiveView dekodiert die Frontend-Parameter in handle_event("form_updated", ...) und aktualisiert das Changeset
    • Validierungsfehler des Changesets werden anschließend über das errors-Prop zurück ans Frontend übergeben
  • Daten fließen von Elixir als Props nach Svelte hinunter; von Svelte steigen sie als WebSocket-Events zu LiveView auf
  • Der LiveView-Prozess kann jederzeit Props aktualisieren und damit ein erneutes Rendering der Svelte-Component auslösen

Kerneigenschaften der Kombination aus LiveView und Svelte

  • Der Kern dieser Kombination ist, dass ein stateful Backend-Prozess und eine stateful Frontend-Component zusammenarbeiten
  • Drei Eigenschaften stützen diese Kombination
    • Das Backend kontrolliert die Props der Frontend-Component
    • Frontend und Backend halten beide State
    • Beide Seiten haben einen privaten bidirektionalen Kommunikationskanal, über den sie Nachrichten initiieren können
  • Die erste Eigenschaft wird durch die Rendering-Weise von LiveView ermöglicht
    • Ergebnisse des serverseitigen Re-Renderings werden automatisch zum Client gepusht und angewendet
    • Der Server kann Component-Props wie eine übergeordnete JS-Component aktualisieren
  • Die zweite Eigenschaft ergibt sich daraus, dass LiveView ein Elixir-Prozess ist
    • Elixir-Prozesse sind eine Art, State zu kapseln
  • Die dritte Eigenschaft wird durch die permanente WebSocket-Verbindung ermöglicht, die LiveView bereitstellt

Unterschiede zu einer SPA

  • Die Browser-Routing-Struktur wird einfacher, weil das gesamte Browser-Routing im Backend erfolgt
    • In einer typischen SPA muss man zwei Arten von Routes pflegen: Browser-Routes und API-Routes
  • Das Backend hält State und kennt die aktuelle Route sowie die Ressource, an der gearbeitet wird
    • Jede Action kann als Änderung des Backend-eigenen State verarbeitet werden, statt den State von Grund auf neu aufzubauen
  • Die Kommunikation zwischen Frontend und Backend ist privat und gekoppelt
    • Öffentliche Server-Routes werden nicht durch RPC-Aufrufe aufgebläht, die nur eine einzelne Component unterstützen
    • Wenn man im Client pushEvent sieht, weiß man, dass der zugehörige Handler im kooperierenden Elixir-Modul liegt
  • Eine Funktionalität verteilt sich meist auf zwei Dateien
    • Das Backend-Modul ruft Backend-Funktionen wie Datenbankabfragen auf
    • Das Frontend bringt Component und Styles mit
    • Der Roundtrip zwischen Frontend und Backend läuft nicht durch API-Modul, Router und Controller-Stack
  • Auch der Kommunikationsablauf wird reduziert
    • Das Backend kann Frontend-Änderungen allein durch Props-Updates signalisieren
    • Das Frontend kann pushEvent senden, ohne Handler für abgelaufene Tokens, Timeouts oder Fehlerbehandlung
    • Ist der WebSocket offen, läuft der Server; ist er geschlossen, zeigt LiveView ein globales „disconnected“-Banner an

Verantwortungstrennung und Codestruktur

  • In dieser Struktur verhält es sich so, als wäre der Frontend-Microservice entfernt worden
  • Business-Logik bleibt im Backend
    • Wie Daten geladen werden
    • Welche Daten geladen werden
    • Wie sortiert und gefiltert wird
    • Validatoren
  • Der Svelte-Frontend-Code wird einfacher
    • if/end-Blöcke für bedingtes Rendering
    • Animationen
    • Einfache pushEvent-Funktionen zum Senden an den Server
  • In typischen SPA-Frontends kann viel map-, reduce- und filter-Logik für die Verarbeitung von Serverdaten, die Aufbereitung von Anzeigedaten und die Vorbereitung von Daten für den Versand an den Server entstehen
  • In LiveSvelte-Apps kann diese Verarbeitung serverseitig erfolgen
    • LiveView bereitet Daten in der Form auf, die die Svelte-Component benötigt
    • Die Komplexität bleibt in der Serversprache, den Server-Datenstrukturen und der Server-Testsuite
  • LiveView und Svelte-Component sind weniger zwei lose gekoppelte Elemente als vielmehr zwei Hälften einer Funktionalität
    • LiveView rendert nur diese Svelte-Component
    • Diese Svelte-Component wird nur von dieser LiveView gerendert

Was sich gegenüber reinem LiveView ändert

  • Der LiveSvelte-Ansatz akzeptiert State und State-Übergänge im Frontend
  • Die Grenzschicht zwischen Frontend und Backend wird klarer
  • Man kann das ausgereifte Component-Paradigma von Svelte nutzen
  • Auch das JavaScript-Framework-Ökosystem, das mit reinem LiveView schwer zugänglich ist, lässt sich verwenden
    • Svelte bietet Animations-Primitives
  • Indem mehr Verantwortung ins Frontend wandert, verringert sich das Gefühl, in einer unbequemen Zwischenzone festzustecken

Warum Svelte gewählt wurde und wo LiveView steht

  • LiveSvelte wurde gewählt, weil es für React keine ähnlich ausgereifte LiveView-Library gab
  • Svelte gilt bei Basis-State und Reaktivität als leichtgewichtiges und schnelles Frontend-Framework
  • Da LiveView einen großen Teil des State-Managements übernimmt, bleibt das State-Management auf Svelte-Seite einfach
  • Auch die Template-Funktionen von Svelte werden gegenüber React bevorzugt
    • Statt ternärer Operatoren kann man if/else verwenden
    • Bedingte Attribute lassen sich setzen
  • Die runes von Svelte 5 sollen Svelte leichter lernbar und leichter nachvollziehbar machen
  • LiveView glänzt am meisten als Backend-for-Frontend, das Frontend-Components rendert, inkrementelle Updates ausführt, einen stateful Backend-Prozess hält und eine WebSocket-API bereitstellt

1 Kommentare

 
GN⁺ 2024-04-05
Hacker-News-Meinungen
  • Ein Muster, das in Multiplayer-Spielen oft verwendet wird, besteht im Grunde darin, denselben Code sowohl auf Client- als auch auf Serverseite auszuführen.
    Der Client-Code führt zuerst eine Vorhersage des Serverzustands aus, und wenn der Serverzustand eintrifft, überschreibt er den Client-Zustand.
    In Spielen passt der Begriff „Vorhersage“ gut, weil der Client die Ergebnisse der eigenen Eingaben recht gut abschätzen kann, die Eingaben anderer Spieler aber nicht kennt.
    Man kann dieses Verfahren aber auch nutzen, um sofort zu reagieren, während man auf den offiziellen Serverzustand wartet, etwa beim Aktivieren/Deaktivieren eines Dropdowns oder bei einem Lade-Spinner.
    Es gibt außerdem viel Client-Zustand, den man auf dem Server gar nicht ausführen muss, etwa Partikelsysteme oder Ragdolls, und wenn eine dauerhafte Serververbindung besteht, sehe ich keinen Grund, warum das nicht auch in einem reaktiven Paradigma funktionieren sollte.

    • Auf https://territoriez.io/ wurde genau dieser Ansatz verwendet.
      Es ist ein Klon von https://generals.io/ und wurde mit LiveSvelte gebaut.
      Eigentlich ist es ein Brettspiel, und bei 2 Ticks pro Sekunde braucht es keine Prediction, aber es nutzt optimistische Updates, um den Spielzustand schon vor dem Server-Update anzuzeigen.
      Wenn es nicht mit dem Server synchron ist, überschreibt der Server den Spielzustand.
      Die gesamte Spiellogik steckt in Elixir, und um Prediction richtig umzusetzen, müssten Server und Client die Logik teilen.
      Andernfalls schreibt man die Logik doppelt, und das ist der Weg in die Katastrophe.
      Ich habe es nicht untersucht, aber eine mögliche Lösung wäre, die gesamte Spiellogik in Gleam (https://gleam.run/) zu schreiben.
      Gleam ist mit Elixir kompatibel und lässt sich auch nach JavaScript kompilieren, sodass man theoretisch denselben Code auf Server und Client ausführen könnte.
      Allerdings ist „Dann schreib doch einfach alles in JS“ auch ein ziemlich berechtigter Einwand, dem ich vermutlich zustimmen würde.
      Der Vorteil wäre trotzdem, BEAM und seine guten Eigenschaften für verteilte Echtzeitsysteme nutzen zu können; abgesehen vom Frontend passt das perfekt zu Multiplayer-Spielen.
    • Wenn man Spiele wie Overwatch spielt, die Ragdoll-Physik lokal und nichtdeterministisch simulieren, wünscht man sich, sie hätten das richtig umgesetzt.
      Wenn jemand stirbt und die Leiche davonfliegt oder in einer lustigen Pose stecken bleibt, sieht das niemand sonst, und auch in Replays ist es nicht erneut zu sehen, weil jeder Client es anders rendert.
      Wenn man es nicht per Echtzeitaufnahme festhält, ist es für immer weg.
      Bei einem gelegentlich albernen Spiel wie Overwatch ist es schade, dass niemand sonst es gemeinsam sehen kann.
    • Fighting Games treiben solche Probleme der niedriglatenzigen Konfliktauflösung bis zum Extrem, und es gibt ein eigenes Teilgebiet von „Netcode“-Techniken zur Abstimmung von Echtzeit-Event-Streams.
      https://arstechnica.com/gaming/2019/10/explaining-how-fighti... ist eine hervorragende Erklärung; verwandte Diskussionen gibt es unter https://news.ycombinator.com/item?id=34399790 und https://news.ycombinator.com/item?id=26289933.
      Wenn wie bei MMOs serverseitige Persistenz hinzukommt, muss man auch I/O-Engpässe berücksichtigen.
      https://prdeving.wordpress.com/2023/09/29/mmo-architecture-s... ist aus dieser Perspektive ein interessanter Artikel; die Diskussion dazu steht unter https://news.ycombinator.com/item?id=37702632.
    • Sind das nicht einfach optimistische Updates? Ist das in clientseitiger Logik nicht schon seit Langem üblich?
    • Damit muss man vorsichtig sein.
      Zum Beispiel kann ein Live-/interaktives Formular scheinbar gut funktionieren, während es abgeleitete Daten auf der Seite aktualisiert; wenn später wegen eines Serverfehlers oder Netzwerkproblems alles zurückgerollt wird, ist das unangenehm.
      Schlimmer noch: Der Nutzer könnte die Seite schließen oder verlassen, ohne das zu bemerken.
  • Ich mag die Kombination LiveView + Svelte.
    Auf der ElixirConf 2022 habe ich vorgestellt, wie man beide kombiniert, aber die Beitragenden von live_svelte haben es tatsächlich nutzbar gemacht.
    Ich denke, Apps mit reichhaltiger User Experience brauchen immer clientseitigen Zustand.
    Besonders wenn man in New York lebt, ist eine Netzwerkverbindung unterwegs keineswegs selbstverständlich.
    Eine starke Funktion, die im Artikel nicht behandelt wurde, ist, dass man mit Phoenix PubSub auch serverseitige Zustandsänderungen, die auf anderen Servern passieren, reaktiv an alle Clients pushen kann.
    Bei mittlerem bis höherem Traffic ist es ziemlich üblich, mehrere Webserver zu betreiben.

    • Mobile Netze haben insgesamt ebenfalls hohe Latenz, und oft braucht man auf dem Client ein Local-first-Caching-System.
      Wenn man eine User Experience wie bei LiveView möchte, aber auch mit instabilen Netzwerken zurechtkommen will, habe ich Svelte-5-Bindings zu Triplit[1] hinzugefügt.
      Man kann clientseitig Queries schreiben und sie per WebSocket mit dem Server synchronisieren.
      Für Elixir-Entwickler ist das vielleicht nicht besonders attraktiv, aber wenn Svelte im Mittelpunkt steht, ist Triplit eine gute Wahl.
      [1] https://www.triplit.dev/docs/frameworks/svelte
    • Wie brauchbar ist eine LiveView-Seite offline, wenn die Netzwerkverbindung nur sporadisch verfügbar ist?
  • Einfach neue Zeilen in die Tabelle zu pushen und LiveView den Client aktualisieren zu lassen, sollte man bei interaktiven Business-Apps eher vermeiden
    Wegen kognitiver Verzögerung klicken Nutzer leicht auf den falschen Eintrag, schicken eine E-Mail an den falschen Kunden oder erstatten die falsche Transaktion
    Die bevorzugte User Experience ist ein fixer Banner wie „Daten wurden geändert. Hier klicken, um zu aktualisieren“
    Wenn es dringend ist, sollte man neue Zeilen nur anhängen und dafür sorgen, dass sich die Scrollposition nicht ändert

    • Wenn man neue Zeilen langsam animiert einfügt und Klicks deaktiviert, während neue Daten eintreffen, kann dieser Ansatz auch funktionieren
  • Bei BeaconCMS werden Svelte und LiveView gemeinsam eingesetzt
    Es gibt sicher gute Anwendungsfälle, in denen man auf dem Client eine feinere Kontrolle über die UI braucht, aber Teams sollten vorsichtig sein, Svelte + LiveView pauschal überall einzuführen
    Auch in Phoenix ist LiveView nicht immer die Antwort; manchmal reicht eine normale gerenderte Seite
    Man sollte nicht alles nach dem Alles-oder-nichts-Prinzip entscheiden
    Wie im Artikel gesagt, gibt es gute Anwendungsfälle, in denen man vom „LiveView-Weg“ abweichen sollte
    Wenn die Roundtrip-Zeit 1.000 ms beträgt, sollte man zwar auch andere Dinge in Betracht ziehen, aber es gibt Teams, die etwa aus Kostengründen keine geografisch nahen Server betreiben können; dann kann clientseitiges State Management eine Lösung sein

  • Heißt das, statt Client-State zu verwalten, will man State sowohl auf Client- als auch auf Serverseite verwalten?
    Der Vorteil, keine weitere API bauen zu müssen, ist zwar da, aber wie eine Verbesserung wirkt es nicht

    • Der State einer Webanwendung ist nicht so simpel
      Es gibt immer State, den der Client verfolgen muss, und State, den der Server verfolgen muss; dazu kommen flüchtiger State, der nur im Speicher liegen muss, und State, der über Sessions hinweg erhalten bleiben muss
      Die Möglichkeiten, das zu verwalten, sind nahezu endlos, und weil es in JavaScript „egal was man tut, keine richtige Antwort“ gibt, nutzt man entweder den vom Framework vorgegebenen Weg oder Workarounds – also Chaos
      In der letzten komplexen Single-Page-App, an der ich gearbeitet habe, haben wir den Client bei jedem Laden der Seite synchronisieren lassen
      Jeder lokale State und jedes Asset hatte einen datumsbasierten Hash; unterschied er sich vom Wert auf dem Server, lieferte der Server die aktualisierte Version aus
      Das galt gleichermaßen für einfache Variablen, riesige JSON-Objekte oder komplette Image-/Audio-Blobs
      Wenn der Client etwas tat, das eine Änderung des Server-State erforderte, schickte er dieses State-Update per WebSocket, und 99 % der App basierten auf WebSocket
      Wir nutzten kein spezielles Framework oder Pattern; wenn wir beim Schreiben des Codes dachten „das sollte der Server verfolgen“, schickten wir den neuen State als Nachricht an den Server, und der Server entschied, ob er beim Laden der Seite synchronisiert wird
      Für diese Art von State Management halte ich das für so ziemlich den einfachsten Mechanismus
      WebSocket hilft bei State Management enorm
    • Am Ende wird vermutlich irgendeine Form dieses Paradigmas gewinnen
      Reale Anwendungen brauchen State sowohl auf Client- als auch auf Serverseite
      Der Server kann dem Client nicht vertrauen und muss daher den autoritativen State besitzen, aber auch der Client muss ohne Roundtrip auf Nutzerinteraktionen reagieren und neu rendern können
    • Manche Dinge bieten eine bessere Experience, wenn sie komplett clientseitig behandelt werden
      Man sollte nicht alles so machen, sondern es nur dort einsetzen, wo es sinnvoll ist
      Was mir außerdem gefällt: Man kann Svelte als Template-Sprache statt Heex verwenden
    • Das ist eher so, als würde eine neue Generation ColdFusion, Web Forms, JSF, PHP, Spring und Rails wiederentdecken
    • Das unterscheidet sich nicht von Optimierungen in anderen Software-Ökosystemen
      Wenn man es nicht braucht, gibt es keinen Grund dafür; wenn man es braucht, dann wegen Performance und User Experience
      Wenn eine Webanwendung viele Nutzerinteraktionen hat und daher Client-JavaScript braucht, und wenn es viele Experimente gibt, sodass sich das Bundle pro Request unterscheidet, will man die Logik zwischen Server und Client aufteilen, um die Menge des an den Client gesendeten JavaScript zu reduzieren
      Andernfalls hat man ein JavaScript-Bundle im MB-Bereich, das alle möglichen Fälle enthält, denen die Anwendung begegnen kann
      Bei einer App wie der Webversion von Photoshop, bei der Nutzer die anfängliche Ladezeit verstehen, kann das in Ordnung sein; bei Apps wie Stripe oder Gmail, von denen man erwartet, dass sie sofort laden, will man die anfängliche Verzögerung der Seite reduzieren
      Man kann auch alles auf den Server verlagern, aber wie GitHub erfahren hat, verschlechtert sich dann die Interaktivität der Seite, weil Nutzeraktionen, die sofort reagieren sollten, einen Server-Roundtrip benötigen
      Dann werden Lichtgeschwindigkeit und Serververteilung zum Problem
      Man kann Bundles auch mit Dingen wie async import verzögert laden, aber wenn das verzögert geladene JavaScript dann eine weitere Anfrage sendet, um seine Daten zu holen, entsteht ein Wasserfallproblem
      Wenn man auf all diese Probleme trifft, sucht man am Ende nach einer Lösung, die das komplexe Problem des Teilens von Logik/State zwischen Client und Server leicht handhabbar macht
  • Dieser Ansatz hat buchstäblich ein Limit durch die Lichtgeschwindigkeit, weil „es Grenzen dafür gibt, wie nah der Server am Nutzer sein kann“
    Der nächste Schritt ist, den Server nach WebAssembly zu kompilieren und an den Client zu schicken
    Dann kann man die Antwort optimistisch rendern, während man auf die echte Serverantwort wartet
    Das klingt ein bisschen verrückt, aber ich habe es in einem echten Projekt gemacht, und es fühlte sich magisch an

    • Wie macht man Persistenz? Das sehe ich als den Kernzweck eines Backends
      Einen Server auf dem Client auszuführen löst das nicht; am Ende bleibt die Netzwerklatenz der Persistenz bestehen
      Der Server-State, der letztlich gewinnen muss, ist genau diese Persistenz
    • Klingt nach Overengineering
      Viel Overengineering wird auch ohne besonderen Grund tatsächlich umgesetzt
    • Gibt es einen Grund, nicht einfach einen Service Worker zu verwenden?
    • Kannst du mehr Details dazu teilen?
    • Das klingt sehr ähnlich wie die clientseitige Vorhersage, die moderne Multiplayer-Spiele nutzen
  • Da ich LiveSvelte gebaut habe, sag Bescheid, wenn du Fragen hast

    • Wenn man LiveSvelte nutzt, gibt es dann trotzdem einen Grund, JS Hooks zu verwenden?
  • Insgesamt wollte ich schon immer Apps nach so einem Modell bauen.
    Ereignisorientiert, bidirektionale Echtzeit-Updates mit dem Server, geordnete Events, lokaler/entfernter Zustand und Ähnliches.
    LiveView kannte ich nicht, und ich habe auch nie eine Sprache aus der Erlang-Familie benutzt, aber offenbar ist da definitiv etwas dran.
    Das traditionelle Request-Response-Modell verursacht subtil und häufig Probleme mit Konsistenz und veralteten Daten.
    Wenn die letzten zehn Jahre davon geprägt waren, Konzepte der funktionalen Programmierung in Mainstream-Sprachen zu integrieren, dann hoffe ich, dass die nächsten zehn Jahre in Richtung zustandsbehafteter, nachrichtenorientierter Programmierung im Mainstream-Fullstack gehen.

    • Es gibt LiveView-Backends in JavaScript (http://liveviewjs.com), Go (https://github.com/canopyclimate/golive), Java (https://github.com/floodfx/undead) und Python (https://github.com/ogrodnek/pyview).
      Nebenbei: Die ersten drei habe ich selbst gebaut oder beim Bauen geholfen.
    • Mit asdf kann man erlang/BEAM/OTP und elixir installieren; wenn man etwas Erfahrung mit Tools hat, dauert das nur ein paar Minuten.
      Wenn man der Anleitung folgt und ein wenig damit herumspielt, sollte man in ungefähr zwei Stunden eine einfache Phoenix-Chat-App im Browser zum Laufen bringen können.
    • „Zustandsbehaftete, nachrichtenorientierte oder reaktive Programmierung in den Mainstream-Fullstack integrieren“ wurde auch einmal MVC genannt, bevor Rails den Begriff neu definiert hat.
  • Das ist eine gute Lösung.
    In meiner App verwende ich zusammen mit LiveView wiederverwendbare Stimulus-Controller, und auch das funktioniert reibungslos.
    Insgesamt macht die Entwicklung mit LiveView Spaß, aber je mehr ich es in realen Szenarien einsetze, desto stärker sehe ich auch die Vorteile zustandsloser HTTP-Frameworks wie Hotwire.
    Es fühlt sich performanter und robuster bei Reconnects an, und man muss für die Stabilität weniger Server näher an den Nutzern platzieren.

    • Als Stimulus/Turbo erstmals angekündigt wurde, hatte ich ziemlich gehofft, dass es helfen würde, die vom Autor beschriebenen Probleme zu lösen.
      Stimulus bietet jedoch keine elegante Möglichkeit, Zustand auf dem Client zu halten.
      Es läuft darauf hinaus, dass „der Zustand einer Stimulus-Anwendung als Attribute im DOM existiert“, und ist damit nicht besser als Vanilla JS oder jQuery.
      Ich habe Stimulus allerdings noch nicht in einem echten Projekt verwendet; möglich also, dass Values und Change-Callbacks eine bessere Erfahrung bieten, als ich zunächst dachte.
    • Ich nutze Stimulus ebenfalls zusammen mit LiveView.
      Diese Kombination trifft genau das richtige Komplexitätsniveau, während Svelte zusammen mit LiveView gefühlt viele Dinge behandelt, die von vornherein gar kein Problem sind.
      Was man braucht, ist Vanilla JS oder eine dünne Schicht darüber, kein vollständiges Framework.
      Man muss ohnehin nicht viel JavaScript schreiben.
      Das JS in unserer produktiven App umfasst keine 200 Zeilen, und einige Stimulus-Controller könnte ich vermutlich sogar entfernen.
      So gut ist LiveView.
      Ich habe außerdem einen ziemlich hackigen Stimulus-LiveView-Hook-Adapter gebaut, damit Stimulus-Controller direkt Events an den LiveView-Prozess senden können.
      Und LiveView verlangt nicht zwingend geografisch verteilte Server, sofern man nicht zu viel von der fly.io-Kool-Aid getrunken hat.
      Verbindungsabbrüche behandelt es hervorragend.
      Für Zero-Downtime-Updates musste ich separat nichts tun.
      Der gesamte Ablauf, bei dem der Client die WebSocket-Verbindung verliert, sich mit der neuen Version wieder verbindet und den Zustand wiederherstellt, wird standardmäßig automatisch erledigt.
      Was will man mehr?
  • Unser Unternehmen hat stark davon profitiert, LiveView zusammen mit Alpine zu verwenden, insbesondere weil wir auf dem Client kein eval erlauben können und daher den CSP-Modus nutzen.
    Als ich LiveSvelte zum ersten Mal sah, wirkte es sehr neuartig und noch wenig erprobt, und in strengen CSP-Umgebungen gab es auch ungelöste Auswirkungen.
    Es freut mich, dass es inzwischen ziemlich nützlich geworden zu sein scheint.
    Unser Muster könnte man grob LiveAlpine nennen.
    Wenn eine Komponente HTML braucht, verwenden wir eine HTML Component; wenn es serverseitigen Zustand gibt, verwenden wir eine Live Component.
    Wenn clientseitiges Verhalten oder clientseitiger Zustand nötig ist, definieren wir zusätzlich eine Alpine-Komponente.
    Wenn wir auf dem Server Client-Events empfangen oder HTTP-Requests ausführen müssen, definieren wir auch einen Phoenix Hook.