Die meisten Event-Driven-Rewrites, die ich gesehen habe, sterben mit 80 % des Message-Buses gebaut, 20 % der Consumer als Stubs angelegt und null der harten Probleme gelöst. Ich war schon der Engineer bei zwei solcher Tode. Ich war auch der Engineer bei einem Event-Driven-Modernisierungsprojekt, das tatsächlich geshipt wurde — bei Bytro, über zwei Jahre. Dieser Post ist der Unterschied zwischen den beiden Outcomes — und er liegt nicht da, wo die Architekturdiagramme es sagen.
Die beruhigende Lüge über Event-Driven
Die Talks und Blog-Posts lassen Event-Driven wie ein Pattern klingen: nimm einen Message-Broker, definiere Events, emittiere sie von der Write-Seite, projiziere sie auf der Read-Seite, fertig. Tooling ist überall. Apache Kafka, NATS, RabbitMQ, SQS — du kannst die Infrastruktur an einem Wochenende aufsetzen.
Deshalb werden die Rewrites gestartet. Deshalb sterben sie auch.
Die beruhigende Lüge ist, dass die Architektur das Schwierige ist. Ist sie nicht. Das Schwierige ist alles, was die Architektur dich explizit machen zwingt. Das synchrone System hatte viele implizite Annahmen: Request-Ordering, transaktionale Grenzen, “dieser Write ist sofort für diesen Read sichtbar”, “wenn A passiert ist, ist B fertig”. In dem Moment, in dem du Write von Read trennst, trennst du auch all diese Annahmen — und wenn du sie nicht absichtlich auflöst, ist dein neues System eine schlechtere Version des alten, das auch noch einen Message-Bus zu betreiben hat.
Was bei Bytro funktioniert hat
Bei Bytro haben wir ein Echtzeit-Multiplayer-Game-Backend modernisiert, das über Jahre in ein verworrenes Legacy-System gewachsen war. Wir sind Event-Driven gegangen, und es hat geshipt, und es hat funktioniert. Warum:
1. Wir haben eine Domain gewählt, nicht das gesamte System
Die Versuchung ist zu sagen “wir gehen als Architekturprinzip Event-Driven.” Das haben wir nicht. Wir haben einen spezifischen Schmerzpunkt ausgewählt — Match-Join-Latency — und gesagt: “Dieser Flow wird der erste Event-Driven sein. Der Rest bleibt synchron, bis das Gegenteil bewiesen ist.”
Das bedeutete, dass wir den ersten echten Event-Driven-Pfad in Monaten statt Jahren shippen konnten. Es bedeutete auch, dass die Feature-Arbeit aller anderen Teams auf dem alten System weiter lief. Die Modernisierung war ein Service, keine Disruption.
2. Wir haben 11 Monate lang einen Shadow betrieben
Der neue Pfad war ab Woche vier in Production — bei 0 % Traffic, shadowend auf jeden Request des alten Pfads, produzierend Ergebnisse, die nicht an User ausgeliefert, sondern nächtlich gegen die alten Ergebnisse verglichen wurden.
Die Diskrepanzen waren die Spec. Jedes “das neue System produziert eine andere Antwort” war ein Bug im neuen System, im alten System oder in unserem Verständnis der Domain. Wir haben davon im Laufe des Jahres ungefähr 200 behoben. Jeder davon wäre ein Incident gewesen, wenn wir früher umgestiegen wären.
3. Wir haben transaktionale Grenzen ehrlich gehalten
Event-Driven bedeutet nicht “alles ist Eventually Consistent und User sollen damit klarkommen.” Manche Operationen müssen noch immer atomar sein. Wenn ein Spieler einem Match beitritt, müssen drei Dinge zusammen passieren: der State des Spielers wird aktualisiert, das Roster des Matches wird aktualisiert, der Billing-Hook feuert. Das können keine drei separaten Events mit Gebeten dazwischen sein.
Wir haben Transactional Outbox-Patterns dafür verwendet: Der Write passiert innerhalb einer DB-Transaktion, das Event wird auch (in eine Outbox-Tabelle) innerhalb derselben Transaktion geschrieben, und ein separater Prozess liefert das Event später an den Bus. Du bekommst Atomarität, wo du sie brauchst, async Delivery, wo du es nicht brauchst.
Jedes “wir brauchen keine Transaktionen, wir sind Event-Driven”-Gespräch ist eine Sechs-Monats-Katastrophe im Wartestand. Halte deine transaktionalen Grenzen explizit und klein.
4. Wir haben Events für Consumer designed, die wir noch nicht gebaut hatten
Die schwierigste Event-Driven-Entscheidung ist, was in ein Event gehört. Zu wenig, und jeder Consumer muss gegen die Write-Side-DB joinen, um den Payload anzureichern (was den Zweck verfehlt). Zu viel, und du bäckst das heutige Schema in jeden Consumer-Code ein und kannst dich nicht weiterentwickeln.
Die Regel, auf die wir uns geeinigt haben: Events tragen den minimalen Payload, der einem vernünftigen Consumer ermöglicht, das uservisible Outcome ohne Round- Trip zu rendern. Nicht “alle Felder, die die Write-Seite hat.” Nicht “nur die ID.” Irgendwo dazwischen, und die genaue Form war domain-spezifische Arbeit, die Monate brauchte, um richtig gemacht zu werden.
Events sind ein öffentlicher Contract. Behandle das Event-Schema mit demselben Ernst, mit dem du eine REST API behandeln würdest. Versioniere es. Dokumentiere es. Ändere es additiv.
5. Wir hatten bei jedem Schritt eine Rollback-Story
Woche vier: Feature Flag aus → zurück auf 100 % alten Pfad. Woche zwölf: Flag aus → zurück. Woche vierzig: Flag aus → zurück. Zu keinem Zeitpunkt war der Rollback mehr als eine Config-Änderung entfernt, und wir haben diesen Rollback monatlich in einem echten Drill getestet.
Am Tag, an dem wir ihn tatsächlich brauchten — Monat acht, ein Randfall beim Event-Ordering — dauerte der Rollback zwei Minuten. Weil wir gedrilled hatten.
Was die anderen zwei Rewrites getötet hat
Die zwei Event-Driven-Modernisierungen, die ich sterben sah, hatten die meiste Infrastruktur an Ort und Stelle. Broker deployed, Topics definiert, Serializer gewählt. Sie sind an den Dingen gestorben, die die Infrastruktur nicht löst:
- Kein Shadow. Sie sind mit einem kleinen Prozentsatz live gegangen und haben gehofft.
- Transaktionale Grenzen mit “Eventual” zugekleistert. Heißt: Operationen, die atomar hätten sein müssen, wurden zu Race Conditions in Production.
- Events per Komitee designed. Jedes Event hatte 40 Felder, weil jedes Team sein Ding drin haben wollte. Das Ergebnis war ein nicht parsebarer Firehose, den niemand subscriben konnte, ohne einen eigenen Service zum Vorverarbeiten.
- Kein Rollback. Das Cutover war “Flag umlegen.” Wenn es nicht funktionierte, funktionierte es nicht.
Alle vier Fehler sind organisatorische und architektonische, keine infrastrukturellen. Ein anderer Broker löst keinen davon.
Das 2026-Fußnoten
Der Großteil meiner aktuellen OSS-Arbeit (Fulcrum) ist selbst in einem spezifischen Sinne Event-Driven — Agent-Runs produzieren Events, die durch einen Memory / Task / Context-Bus fließen. Dieselben Regeln gelten im kleinen Maßstab wie im Bytro-Maßstab: wähle einen Flow, shadow ihn, halte Transaktionen ehrlich, designe Events für Consumer, drill den Rollback.
Das Pattern ist skalierungsinvariant. Die Disziplin ist das, was skaliert — nicht der Broker.