Vorgehen

Kontrolle und Verantwortung zurück auf den Server

Wir bauen Web-Applikationen so, dass der Server wieder die Kontrolle über den Anwendungszustand hat. Diese Seite beschreibt die Prinzipien dahinter und beantwortet die Einwände, die wir dazu am häufigsten hören.

Unsere Architekturprinzipien

Wir wählen Technologien nicht nach Trends, sondern nach Wirkung. Diese Prinzipien ziehen sich durch unsere Projekte, unabhängig von Frameworks oder Hypes.

Ein langlebiger Serverprozess

Eine Web-Anwendung muss sich nicht für jede Anfrage neu aufbauen und danach alles verwerfen, wie es das klassische Request/Response‑Modell von PHP vorsieht. Wir betreiben sie als einen Serverprozess, der dauerhaft läuft: in Go oder in PHP mit einer asynchronen Runtime wie Swoole.

In einem solchen Prozess lassen sich:

  • langlebige Verbindungen
  • parallele IO‑Operationen
  • Event‑Loops

effizient umsetzen, bei gleichzeitigem Zugriff auf ein grosses, stabiles Ökosystem.

Das Resultat ist hohe Performance ohne Verzicht auf Wartbarkeit, Debuggability oder Tooling. Dazu ist der Code strikt typisiert: Static Analysis prüft ihn frühzeitig, und er bleibt langfristig stabil. In Go übernimmt das der Compiler, in PHP ein Werkzeug wie PHPStan (inkl. Generics).

Pushing statt Polling (SSE)

Moderne Webanwendungen müssen nicht permanent «nachfragen», ob sich etwas geändert hat.
Mit Server‑Sent Events (SSE) hält der Server eine langlebige HTTP‑Verbindung offen und pusht Zustandsänderungen aktiv an den Client.

SSE ist:

  • einfaches, standardisiertes HTTP
  • hervorragend komprimierbar (z. B. mit Brotli oder zstd)
  • ideal für hochfrequente Payloads

In der Praxis lassen sich dadurch sehr hohe Kompressionsraten erreichen, vor allem bei kontinuierlichen Updates. Dies ohne komplexe Protokolle oder Client‑State‑Management.

Backpressure & kontrollierter Datenfluss

Ein oft unterschätzter Vorteil serverautoritärer Architekturen ist Backpressure:
Der Server bestimmt wann, wie oft und wie viel gesendet wird.

Ein typisches Muster:

  • Der Server berechnet den neuen Zustand einmal pro Änderung oder mit fixer Frequenz (z. B. 5 Hz)
  • Alle verbundenen Clients erhalten denselben Update‑Stream (z. B. via SSE)

Unabhängig davon, ob 1 oder 1’000 Benutzer verbunden sind, bleibt:

  • die Anzahl State‑Berechnungen konstant
  • der Datenfluss kontrollierbar

Was jede Verbindung kostet, ist ihre Kompression. Brotli führt sein Fenster pro Verbindung: Jeder Stream komprimiert jeden Frame für sich und hält dafür eigenen Speicher. Kompressionsstufe und Fenstergrösse sind die Stellschrauben, die Speicher und CPU gegen Bytes auf der Leitung tauschen: Je höher die Stufe und je grösser das Fenster, desto mehr Speicher braucht jede Verbindung, und desto weniger Bytes sendet sie.

Gemessen an unserem Kanban-Board: Der Server rendert das Board pro Änderung einmal für alle, in 2.4 ms pro Frame bei 200 Tabs, und ein Frame nach einer verschobenen Karte kostet 0.5 bis 1 kB auf der Leitung. Ein Stream, der nicht nachkommt, erhält den neuesten Frame statt eines Rückstaus. Bei 200 aktiven Tabs ging der grösste Teil von 5.1 CPU-Kernen ins Komprimieren. Jeder aktive Stream hält bei Stufe 4 rund 2.2 MB Brotli-Zustand, und der Garbage Collector von Go verdoppelt das im Arbeitsspeicher (RSS) ungefähr. Stufe 3 braucht etwa ein Viertel weniger Speicher und sendet rund 60 Prozent mehr Bytes.

Das Resultat sind stabile Lastprofile und sehr gut vorhersagbares Systemverhalten: Die Zustandsberechnung wird nicht multipliziert, und was jede Verbindung kostet, lässt sich messen und einstellen.

Embedded Databases (z. B. SQLite)

Netzwerk-Datenbanken sind in vielen Workloads primär durch Latenz gedeckelt: Jede Query ist mindestens ein Roundtrip, unabhängig davon, wie schnell der Server selbst rechnet.

Embedded Databases wie SQLite laufen im gleichen Prozess (oder zumindest auf derselben Maschine) wie die Applikationslogik. Das eliminiert Netzwerk-Roundtrips und ermöglicht extrem schnelle Reads, insbesondere bei typischen Query-lastigen Anwendungen.

In Kombination mit CQRS ist das besonders wirkungsvoll:

  • Writes gehen gezielt in einen kontrollierten Pfad (z. B. zentraler Write-Store)
  • Reads bedienen wir aus optimierten, lokalen Read-Modellen (z. B. SQLite, Projections)

Wie viel Performance das bringt, hängt von Datenmodell und Zugriffsmuster ab: Pro Abfrage fällt die Latenz eines Netzwerk-Roundtrips weg.

view = f(state)

Die Darstellung ist eine reine Funktion des States.
Es existiert kein impliziter oder verteilter View‑State im Client.

Das bedeutet:

  • derselbe State erzeugt immer dieselbe Darstellung
  • Rendering ist deterministisch
  • die View ist jederzeit vollständig rekonstruierbar

Der Server hält den autoritativen Zustand, Clients erhalten Updates, und das UI folgt automatisch. Ganze Klassen von Fehlern (inkonsistente Views, verlorene Updates, schwer reproduzierbare Edge‑Cases) entfallen vollständig.

Auch in einer serverzentrierten Architektur sind reaktive UIs möglich: Im Client nutzen wir Signals, um Zustandsänderungen gezielt und effizient in der Oberfläche abzubilden, ohne SPA-typischen Overhead.

Weil der Server fertiges HTML schickt statt Daten, kommt im Browser nur an, was die Seite zeigt. Eine JSON-API liefert oft mehr Felder, als die Oberfläche braucht, und wer die Antwort im Browser öffnet, sieht sie alle: eine häufige Ursache für Datenlecks (Information Disclosure).

DOM‑Morphing

Statt komplette Views neu zu rendern, werden nur die tatsächlich notwendigen Änderungen am DOM vorgenommen.

Beim DOM‑Morphing vergleicht der Client den aktuellen DOM mit serverseitig generiertem HTML und wendet gezielte Updates an.

Vorteile:

  • minimale DOM‑Operationen
  • Erhalt von Fokus‑, Scroll‑ und Input‑Zuständen
  • keine Client‑Render‑Pipeline, kein virtuelles DOM

Der Server bleibt die Quelle der Wahrheit, der Client bleibt schlank.

Multiplayer & kollaborative Systeme

Kollaborative Echtzeit‑Software wird erheblich einfacher, wenn der Server die alleinige Autorität über den Zustand besitzt.

Das Modell ist klar:

  • Clients senden Intents (Aktionen)
  • der Server entscheidet über den neuen Zustand
  • alle Clients erhalten denselben, konsistenten Update‑Stream

Dadurch entfallen:

  • konkurrierende Client‑States
  • komplexe Konfliktauflösungen
  • schwer testbare Synchronisationslogik

Das System verhält sich deterministisch, reproduzierbar und bleibt auch bei steigender Nutzerzahl beherrschbar.

CQRS: Trennung von Lese- und Schreiboperationen

Lese‑ und Schreibzugriffe haben unterschiedliche Anforderungen. Mit CQRS werden diese bewusst getrennt:

  • Commands (Writes): klassische HTTP‑Requests, die Aktionen auslösen
  • Queries (Reads): kontinuierlicher Stream (z. B. SSE), der Zustandsänderungen liefert

Diese Trennung ermöglicht:

  • gezielte Optimierung von Reads und Writes
  • klare, nachvollziehbare Datenflüsse
  • hohe Skalierbarkeit ohne zusätzliche architektonische Komplexität

Event Sourcing

Statt den aktuellen Zustand direkt zu persistieren, werden fachliche Ereignisse (Events) als primäre Quelle gespeichert.

Das Muster:

  • Aktionen erzeugen Events (z. B. UserRegistered, OrderConfirmed)
  • Der aktuelle Zustand wird aus der Event-Historie abgeleitet
  • Ein Event Bus (bspw. NATS) verteilt diese Ereignisse intern weiter (z. B. an Projektionen, SSE-Streams oder Nebenprozesse)

In Kombination mit serverseitiger Autorität ergeben sich klare Vorteile:

  • vollständige Nachvollziehbarkeit und Auditierbarkeit
  • deterministische Replays (State lässt sich jederzeit rekonstruieren)
  • natürliche Integration mit CQRS (Events → Read-Modelle)

Für Clients bedeutet das: Sie abonnieren Zustandsänderungen, nicht Implementierungsdetails. Echtzeit-Updates, Replays und neue Views lassen sich ohne invasive Änderungen ergänzen.

Wie diese Prinzipien in der Praxis aussehen, zeigen unsere Fallstudien, mit Quellcode und Rohdaten.

Einwände aus der Praxis

Diese Fragen und Bedenken hören wir häufig in technischen Reviews und Architekturgesprächen. Sie entstehen aus realen Projekterfahrungen, genauso wie unsere Antworten.

Das klingt technisch sinnvoll, aber was bringt mir das geschäftlich?

Der primäre Nutzen liegt nicht in der Technologie selbst, sondern in den wirtschaftlichen Effekten:

  • Tiefere Entwicklungskosten durch weniger Architektur-Overhead und kürzere Feedback-Zyklen
  • Planbare Performance dank Backpressure und serverseitiger Kontrolle
  • Reduzierte Betriebskosten, da keine komplexe Cloud- oder Microservice-Infrastruktur nötig ist
  • Schnellere Änderungen, weil Logik und State an einem Ort liegen

Kurz gesagt: Sie investieren weniger in Infrastruktur und Komplexität und mehr in tatsächlichen Geschäftsnutzen.

Dieser Ansatz ist besonders geeignet für Organisationen, die Wert auf Stabilität, Vorhersagbarkeit und langfristige Wartbarkeit legen.

Aber SPAs sind Industry-Standard

Single Page Applications sind weit verbreitet, aber Verbreitung ist nicht gleichbedeutend mit Eignung.

Der SPA-Ansatz hat sich vor allem aus historischen Gründen etabliert:

  • Limitierte Browser-APIs in der Vergangenheit
  • Wunsch nach App-ähnlicher UX zu Zeiten langsamer Server
  • Starke Tooling- und Framework-Ökosysteme

Heute haben sich die Rahmenbedingungen grundlegend verändert:

  • Server sind leistungsfähig und dauerhaft verbunden
  • Moderne Browser unterstützen Push, Streaming und Transitions nativ
  • Latenz und Komplexität sind oft die dominierenden Kostenfaktoren

«Industry-Standard» beschreibt daher eher einen Gewohnheitszustand als eine technische Notwendigkeit.

Wir wählen Architekturen nicht nach Marktanteil, sondern nach:

  • fachlicher Passung
  • Komplexitätsprofil
  • langfristigen Kosten und Risiken

In vielen realen Projekten führt ein serverautoritärer Ansatz zu einfacheren, stabileren und wirtschaftlicheren Systemen, auch wenn er weniger laut beworben wird.

Ohne SPA fühlt sich die Anwendung langsam an

Das wahrgenommene «App-Gefühl» entsteht nicht primär durch ein Client-Framework, sondern durch:

  • niedrige Latenz
  • gezielte Updates statt Full-Reloads
  • saubere Übergänge

Mit serverseitigem Rendering, SSE, Signals und View Transitions lassen sich sehr reaktive UIs realisieren, ohne komplexen Client-State und ressourcenschonender.

Das ist doch nicht «modern»

Die beschriebenen Konzepte basieren auf aktuellen Web-Standards und moderner Laufzeit-Architektur.

«Modern» bedeutet hier nicht maximale Abstraktion, sondern:

  • klare Verantwortlichkeiten
  • kontrollierbare Komplexität
  • langfristige Wartbarkeit

Und genau darauf ist der Ansatz ausgelegt.

Was ist mit Offline-First?

Offline-First ist ein legitimes Spezialanforderungsprofil, aber kein sinnvoller Default für die meisten Webanwendungen.

In vielen Geschäftsanwendungen gilt:

  • Nutzer arbeiten überwiegend online
  • Konsistenz ist wichtiger als lokale Autonomie
  • Konfliktauflösung und Merge-Logik sind fachlich komplex und teuer

Offline-First erfordert in der Regel optimistic Updates, also UI-Zustände, die eine erfolgreiche Aktion annehmen, bevor der Server sie bestätigt. Damit hält der Client temporär eine eigene Wahrheit, mit erheblichen Konsequenzen:

  • Rollbacks bei abgelehnten Aktionen (Validierung, Berechtigungen, Business-Regeln)
  • Konflikte bei parallelen Aktionen, mehreren Tabs oder kollaborativen Nutzern
  • schwer reproduzierbare Randfälle und deutlich höhere Testkomplexität

Diese Komplexität verlagert sich vollständig in den Client und skaliert schlecht mit wachsender fachlicher Logik.

Aus unserer Sicht ist echtes Offline-First primär ein Fall für native Applikationen.
Dort stehen Betriebssystem-APIs, persistente Storage-Modelle und klare Lebenszyklen zur Verfügung, um Offline-Zustände sauber und zuverlässig zu beherrschen.

Für Webanwendungen priorisieren wir daher Online-First mit robuster Degradierung:

  • Der Server bleibt autoritativ
  • Kurzzeitige Verbindungsunterbrüche werden abgefedert
  • Aktionen werden explizit bestätigt oder abgelehnt

Falls echte Offline-Fähigkeit fachlich zwingend erforderlich ist, empfehlen wir eine dedizierte native Lösung, statt die gesamte Webarchitektur auf einen Ausnahmefall auszurichten.

Skalierung funktioniert nur mit Microservices

Skalierung ist primär ein Last- und Datenflussproblem, kein Architektur-Label.

Wenn:

  • Zustandsberechnung entkoppelt ist
  • Reads und Writes getrennt sind (CQRS)
  • der Server Backpressure ausübt

lässt sich auch ein modularer Monolith sehr weit skalieren, effizienter und kostengünstiger als verteilte Systeme.

SQLite ist doch nicht ernst zu nehmen / nicht für produktive Systeme geeignet

SQLite wird häufig als «Embedded-Spielzeugdatenbank» wahrgenommen. Diese Einschätzung greift jedoch zu kurz.

Fakten:

  • SQLite ist transaktional, ACID-konform und extrem ausgereift
  • Es wird seit Jahren in sicherheits- und geschäftskritischen Systemen eingesetzt (Browser, Betriebssysteme, Mobile-Plattformen)
  • Die Performance ist nicht durch Netzwerk-Latenz begrenzt, sondern primär durch CPU und I/O

Entscheidend ist das Einsatzmuster:

  • SQLite eignet sich hervorragend für read-heavy Modelle, mit einem einzigen Writer auch für viele Writes
  • Writes werden bewusst kontrolliert (z. B. über CQRS, Event Sourcing oder zentrale Write-Stores)
  • Concurrency wird architektonisch gelöst, nicht der Datenbank überlassen

Wie viel ein einziger Writer schafft, haben wir an unserem Kanban-Board gemessen: Er fasst die wartenden Commands zu einer Transaktion pro Batch zusammen, jeden Command in einem eigenen Savepoint. So schafft er rund 13’800 Verschiebungen pro Sekunde, wenn 200 Leute gleichzeitig Karten verschieben, mit synchronous=FULL, also so dauerhaft wie PostgreSQL in den Standardeinstellungen.

In diesem Kontext ist SQLite kein Kompromiss, sondern ein strategisches Werkzeug:

  • extrem niedrige Latenz
  • minimale operative Komplexität
  • sehr hohe Vorhersagbarkeit

Kurz gesagt: SQLite ist kein Ersatz für jede Datenbank, aber in den richtigen Architekturen oft die effizienteste Wahl.

Aber es gibt doch schon React Server Components?

React Server Components verfolgen ein ähnliches Ziel wie unser Ansatz: weniger Client-State, mehr Verantwortung auf dem Server. Die Grundidee ist richtig und bestätigt eine Entwicklung, die wir seit Jahren beobachten, nämlich die Rückverlagerung von Logik, Datenzugriff und Rendering dorthin, wo sie besser kontrollierbar sind.

Der Unterschied liegt jedoch in der Umsetzung. React Server Components lösen dieses Problem innerhalb eines spezifischen Frameworks und eines komplexen Build- und Laufzeitmodells. Rendering, Datenfluss und Reaktivität bleiben an React, dessen Tooling und dessen Abstraktionen gebunden. Das funktioniert gut innerhalb dieses Ökosystems, bringt aber zwangsläufig Abhängigkeiten und konzeptionelle Kopplung mit sich.

Unser Ansatz ist bewusst framework-agnostisch. Statt komponentenbasiertem Server-Rendering setzen wir auf offene Web-Standards: HTTP, Streaming, Events und klar definierte Zustandsmodelle. Der Server ist autoritativ für State und Geschäftslogik, Änderungen werden als Ereignisse gestreamt, und die Darstellung folgt deterministisch dem aktuellen Zustand. Reaktivität im Frontend entsteht nicht durch erneutes Ausführen von Komponenten, sondern durch gezielte Updates auf Basis von Zustandsänderungen.

Gerade bei Echtzeit-, Multiplayer- oder kontinuierlich aktualisierten Anwendungen ist dieses zustandsgetriebene Push-Modell natürlicher als ein request-getriebenes Render-Modell. Es reduziert implizite Zustände, vermeidet Hydration- und Re-Render-Komplexität und bleibt auch operativ einfacher.

Kurz gesagt: React Server Components zeigen, dass die Richtung stimmt. Wir gehen diesen Weg jedoch konsequent weiter, mit weniger Abhängigkeiten, klareren Datenflüssen und einem Architekturmodell, das unabhängig von einem einzelnen Framework langfristig tragfähig bleibt.

Moderne Frontends brauchen ein grosses npm-Ökosystem

Ein umfangreiches Client-Ökosystem klingt produktiv, bringt in der Praxis jedoch oft erhebliche Nebenwirkungen mit sich:

  • Dependency Hell durch transitive Abhängigkeiten und inkompatible Major-Releases
  • node_modules als Black Hole: grosse Installationen, lange Build-Zeiten, schwer nachvollziehbare Abhängigkeiten
  • Security-Risiken durch eine hohe Anzahl externer Pakete mit variierender Pflegequalität
  • Upgrade-Zwang, der nicht fachlich getrieben ist, sondern durch Tooling-End-of-Life entsteht

Ein serverzentrierter Ansatz reduziert die Abhängigkeit von volatilen Client-Libraries drastisch. Weniger Abhängigkeiten bedeuten:

  • geringere Angriffsfläche
  • planbarere Wartung
  • längere Lebensdauer der Software

Der Fokus verschiebt sich von Tooling-Pflege hin zu stabiler, wertschöpfender Funktionalität.

Wenn Ihr Einwand hier fehlt, schreiben Sie uns.