Echtzeit-Zusammenarbeit sieht mühelos aus, bis man hinter die Kulissen blickt. Eine Person tippt. Eine andere löscht eine Zeile drei Absätze weiter oben. Eine dritte Person fügt ein Snippet von Stack Overflow ein. Irgendwie pendelt sich das Dokument in einem einzigen, kohärenten Zustand ein. Diese Geschmeidigkeit von Grund auf neu aufzubauen, ohne vorherige Erfahrung mit WebSockets oder verteiltem Zustand, klingt leichtsinnig. Es klingt aber auch nach dem richtigen Weg, um wirklich etwas zu lernen.

Dieses Projekt beginnt bei Null. Kein geliehener Boilerplate-Code. Keine polierten YouTube-Walkthroughs, bei denen die schwierigen Teile in einer dreißigsekündigen Montage übersprungen werden. Das Ziel ist ein kollaborativer Code-Editor, in dem mehrere Benutzer gleichzeitig dieselbe Datei bearbeiten können und dabei die Änderungen der anderen – sowie deren Cursor – in Echtzeit sehen. Um dorthin zu gelangen, müssen Transportschichten, Konsistenzmodelle und das knifflige Problem des Zusammenführens gleichzeitiger Bearbeitungen gelöst werden, ohne das Dokument zu beschädigen.

Was „Echtzeit“ eigentlich bedeutet

Die meisten Webanwendungen fühlen sich mit Request-Response-Zyklen wohl. Man sendet ein Formular ab, der Server speichert, man aktualisiert die Seite. Echtzeit-Zusammenarbeit bricht diesen Vertrag komplett. Jeder Tastendruck ist ein Ereignis, das an jeden anderen verbundenen Client propagiert werden muss – meist in Millisekunden – und in einer Reihenfolge ankommen muss, die den Sinn bewahrt.

WebSockets sind hier die offensichtliche Wahl für den Transport, da sie eine persistente Full-Duplex-Verbindung zwischen Client und Server aufrechterhalten. Im Gegensatz zum HTTP-Polling, das Bandbreite verschwendet, indem es alle paar Sekunden fragt: „Gibt es etwas Neues?“, bleibt ein WebSocket offen. Wenn Benutzer A ein Semikolon tippt, wird dieses Zeichen zu einer Nachricht, die über den Socket an einen zentralen Server gesendet wird und sich dann an die Benutzer B und C verteilt. Dieser Teil ist relativ unkompliziert.

Der schwierige Teil ist das, was passiert, wenn B und C im exakt selben Moment tippen. Wenn beide Änderungen fast gleichzeitig beim Server eintreffen, welche gewinnt? Wenn man Nachrichten einfach in der Reihenfolge ihres Eintreffens überträgt, riskiert man verlorene Zeichen oder durcheinandergeratenen Text. Naive „Last-Write-Wins“-Strategien scheitern, weil sie die Absicht ignorieren. Wenn ich „hello“ am Anfang von Zeile eins tippe, während du „world“ am Anfang von Zeile eins tippst, sollte das Ergebnis keine Kollision sein, bei der einer von uns gelöscht wird. Es sollte „helloworld“ oder „worldhello“ sein, deterministisch gewählt. Um das zu erreichen, ist eine Synchronisationsstrategie erforderlich, die die Struktur des Dokuments versteht.

Warum es wichtig ist, bei Null anzufangen

Es gibt hervorragende Frameworks, die diese Komplexität verbergen. Yjs, Automerge und Socket.IO können den Schmerz abstrahieren und bereits an einem Nachmittag einen funktionierenden Prototyp liefern. Aber sie zu verwenden, ohne die zugrunde liegenden Primitiven zu verstehen, ist wie ein Flugzeug im Autopiloten zu fliegen, ohne zu wissen, wie man die Instrumente liest. Wenn Turbulenzen auftreten – und in verteilten Systemen treten sie immer auf –, muss man wissen, ob das Problem in der Netzwerkschicht, der Konfliktlösung oder im Datenmodell liegt.

Die Verpflichtung hier besteht darin, die Konzepte zu lernen, bevor man sich auf die Bibliotheken verlässt. Das bedeutet, man muss analytisch durchspielen, was passiert, wenn:

  • Ein Client mitten im Tastendruck die Verbindung verliert und sich zehn Sekunden später wieder verbindet
  • Zwei Benutzer gleichzeitig Text an derselben Cursorposition einfügen
  • Ein Benutzer einen Block löscht, den ein anderer Benutzer gerade aktiv bearbeitet
  • Der Server abstürzt und ein neuer Knoten den Dokumentzustand von Grund auf rekonstruieren muss

Operational Transformation (OT) und Conflict-free Replicated Data Types (CRDTs) sind die zwei dominierenden Lösungsansätze für diese Probleme. Google Docs baute seine frühe Architektur bekanntlich auf OT auf, was einen zentralen Server erfordert, um Operationen gegeneinander zu transformieren, bevor sie angewendet werden. CRDTs hingegen sind so konzipiert, dass gleichzeitige Aktualisierungen lokal ohne Koordination zusammengeführt werden können, was sie für Peer-to-Peer- oder Edge-basierte Setups attraktiv macht. Die Wahl zwischen ihnen – oder hybriden Ansätzen – erfordert ein Verständnis ihrer Abwägungen in Bezug auf Speicherverbrauch, Konvergenzgarantien und Implementierungskomplexität. Über diese Abwägungen zu lesen, reicht nicht aus; der Plan ist, sowohl naive als auch verfeinerte Versionen zu implementieren, um zu sehen, wo sie scheitern.

Die Neukonstruktionen, Fehler und Sackgassen

Erwartungen werden ehrlich kalibriert. Es wird Phasen geben, in denen nichts funktioniert. Ein erster Versuch könnte einfache JSON-Patches verwenden, um Textänderungen darzustellen, nur um festzustellen, dass JSON kein Konzept von „Index 5 in einem Absatz“ hat, sodass zwei gleichzeitige Einfügevorgänge am selben Index einander überschreiben, anstatt zu verschmelzen. Ein zweiter Versuch könnte ein benutzerdefiniertes lineares History-Log erstellen, nur um zu merken, dass das Abspielen dieses Logs ein Big-O-Albtraum ist, wenn das Dokument wächst. Ein dritter Versuch könnte WebSockets lokal zum Laufen bringen, nur um dann in einem echten Netzwerk zusammenzubrechen, in dem Paketverlust und variable Latenz die Regeln neu schreiben.

Genau dieser Reibungspunkt ist das Ziel. Das Kopieren eines funktionierenden Repositories würde die Untersuchung darüber überspringen, warum die Queue in dieser speziellen Reihenfolge geleert wird oder warum der Server einen Version-Vector pflegt. Dieselbe Komponente dreimal neu aufzubauen ist langsam, aber es erzwingt ein Verständnis für die Grenze zwischen dem, was das Framework tut, und dem, was die eigene Logik handhaben muss.

Die Dokumentation dieses Prozesses wird kein Best-of sein. Sie wird auch die Fehlentscheidungen enthalten. Zum Beispiel scheint die Implementierung von Presence Awareness – zu wissen, wer online ist und wo sich der Cursor befindet – eine rein kosmetische Funktion zu sein, bis man erkennt, dass sie vom selben Konsistenzmodell abhängt wie der Text selbst. Wenn Benutzer A den Cursor von Benutzer B in Spalte 10 sieht und Benutzer B dann vier Zeichen einfügt, wohin bewegt sich dieser Cursor? Ohne ein gemeinsames Verständnis der Dokumententopologie driften die Presence-Daten von der Realität ab. Die Lösung erfordert, die Cursorposition an die Identität der zugrunde liegenden Datenstruktur zu koppeln, nicht nur an deren numerischen Index. Dies sind die Arten von Details, die in Tutorials oft nur oberflächlich behandelt werden, weil sie mühsam sind, nicht weil sie unwichtig sind.

Was als Nächstes kommt

Die unmittelbare Roadmap ist absichtlich spärlich gehalten. Die ersten Meilensteine werden sein:

  • Ein roher WebSocket-Server, der Zeichen-Events spiegelt, um Latenz und den Lebenszyklus der Verbindung aus erster Hand zu erleben
  • Ein einfacher String-Buffer auf dem Client, um zu verstehen, warum eine naive Einfügereihenfolge bei Nebenläufigkeit scheitert
  • Ein von Grund auf neu entwickelter CRDT für geordnete Sequenzen, so ineffizient er auch sein mag, um die kommutative Eigenschaft in Aktion zu sehen
  • Die schrittweise Integration in eine echte Code-Editor-Oberfläche, wahrscheinlich etwas wie CodeMirror oder Monaco, um sich mit der Diskrepanz zwischen der imperativen API des Editors und der funktionalen Natur der Operations-Historie auseinanderzusetzen

Jeder Schritt wird mit einer schriftlichen Begründung versehen. Warum dieser Ansatz und nicht jener? Welche Annahmen wurden widerlegt? Welche Abstraktion ist durchgesickert?

Ein echtes Fazit

Ein Projekt wie dieses ohne Erfahrung mit WebSockets oder CRDTs zu starten, ist einschüchternd, aber Fachwissen ist oft nur wiederholte Verwirrung mit besseren Bezeichnungen. Das Ziel ist kein schneller Abschluss. Es ist ein System, dessen Verhalten vorhersehbar ist, weil jede Ebene mit Absicht gebaut und nicht in der Hoffnung importiert wurde.

Wenn Sie schon einmal kollaborative Software entwickelt haben – sei es ein Texteditor, ein Design-Tool oder eine Engine zur Synchronisierung des Spielzustands – teilen Sie die Fehlermodi, die Sie unvorbereitet getroffen haben. Wenn Sie diese Systeme ebenfalls gerade lernen, machen Sie mit. Der Code wird langsam erscheinen und oft umgeschrieben werden. Tag 0 beginnt jetzt.