Blog > Unsere Migration von AngularJS zu …

Unsere Migration von AngularJS zu Hotwire und Tailwind

Ein Blick auf die reinen Zahlen eines unserer jüngsten Projekte erzählt bereits eine eindrucksvolle Geschichte: 3.000 geänderte Dateien, 100.000 hinzugefügte Zeilen Code und, was noch wichtiger ist, 250.000 entfernte Zeilen Code. Dies war kein gewöhnliches Refactoring. Es war eine bewusste architektonische Vereinfachung, ein Frontend-Rewrite, der unsere über Jahre gewachsene, geschäftskritische Ruby-on-Rails-Anwendung in eine neue technologische Ära führen sollte.

Einleitung: Die Anatomie eines Frontend-Rewrites

Die Ausgangslage war ein Szenario, das viele Entwicklerteams kennen: Ein robustes und zuverlässiges Backend, das über Jahre hinweg seine Dienste tat, gekoppelt mit einem Frontend, das auf den einstigen Vorreitern AngularJS 1.6 und Bootstrap 2.3 basierte. Diese Kombination, ein Aushängeschild alter Zeiten, hat sich über die Jahre zu einer erheblichen Quelle technischer Schulden entwickelt. Die Entscheidung zur Migration war daher keine Frage des Geschmacks, sondern eine unumgängliche Reaktion auf das technologische Altern unseres Produkts.

Die Entscheidung für den Rewrite wurde durch eine Kombination interner und externer Faktoren vorangetrieben. Intern war die Entwicklung in der veralteten AngularJS-Version zunehmend kompliziert und ineffizient geworden. Hinzu kam, dass die Anzahl der Teammitglieder, die mit den Feinheiten von AngularJS vertraut waren, begrenzt war, was die Wartung und Weiterentwicklung zusätzlich erschwerte. Diese internen Herausforderungen machten eine technologische Erneuerung notwendig. Das offizielle End-of-Life (EOL) von AngularJS am 31. Dezember 2021 war dann der externe Faktor, der die Dringlichkeit unterstrich. Ab diesem Datum stellte Google jeglichen Support ein, was in der Praxis bedeutet: keine Sicherheitspatches und keine Kompatibilitäts-Fixes für neue Browser-Versionen.

Parallel dazu war unser Styling-Framework, Bootstrap 2.3, ebenfalls ein Relikt aus einer anderen Zeit. Veröffentlicht im Februar 2013, endete der offizielle Support bereits im selben Jahr mit der Veröffentlichung von Bootstrap 3. Über ein Jahrzehnt der Weiterentwicklung moderner CSS-Praktiken war an unserem Frontend vorbeigegangen, was die Wartung erschwerte und zu einer veralteten User Experience führte.

Unsere neue Vision führte zu einer modernen Rails-Anwendung, die auf Hotwire und Tailwind CSS setzt. Das war eine bewusste, strategische Entscheidung. Mit diesem Schritt bekannten wir uns zur Philosophie des „HTML-over-the-wire“, mit dem Ziel, die Komplexität zu reduzieren, die Produktivität der Entwickler zu steigern und eine nachhaltig wartbare Codebasis für die kommenden Jahre zu schaffen. Die Webentwicklung der letzten Jahre zeigt eindeutig: Nutzer und Nutzerinnen erwarten keine statischen Websites mehr, sondern dynamisch funktionierende Inhalte, die eher einer klassischen Desktop- oder Mobilanwendung gleichen. Durch die Umstellung auf neueste Frameworks und Technologien können wir diesem Anspruch gerecht werden.

Teil 1: Die Strategie

Dieser Abschnitt beleuchtet die strategische Planung, die kritischen Lektionen aus frühen Fehlversuchen und die letztendlich erfolgreiche Rollout-Strategie, die uns zum Ziel führte.

1.1 Der Proof of Concept: Hypothesen validieren, Annahmen entkräften

Bevor wir uns auf einen vollständigen Rewrite festlegten, war ein fokussierter Proof of Concept (PoC) unerlässlich. Er diente dazu, unsere zentralen Annahmen über die Eignung des neuen Stacks für die spezifischen Anforderungen unserer Anwendung zu validieren.

Zwei Bereiche standen im Mittelpunkt unserer Untersuchung:

  • Formularlogik und Datenintegrität: Unser UI enthält viele Seiten, die aus mehreren Tabs bestehen, in denen unterschiedliche Inhalte und Einstellungen präsentiert werden, die sich hauptsächlich aus Formularen zusammensetzen. Wir testeten die Logik für komplexe Formulare mit mehreren Tabs. Hier trafen wir eine weitreichende architektonische Entscheidung: Jeder Tab wurde als eigene „Seite“ behandelt. Dieser Ansatz, ermöglicht durch die Turbo Frames von Hotwire, stellte sicher, dass beim Wechsel zwischen den Tabs stets die aktuellsten Daten angezeigt werden. Da nur die notwendigen Fragmente der Seite neu geladen werden, verbesserte sich das Ladeverhalten und die wahrgenommene Performance dramatisch, insbesondere bei datenintensiven Ansichten wie der Episodenliste. Hier testeten wir typische Kunden-Szenarien von 1 bis 45.000 Episoden.

Ladeverhalten und User Experience: Für Seiten, die aus sehr vielen Datensätzen bestehen, wie beispielsweise die Liste aller Podcasts, evaluierten wir zwei Ansätze: Infinite Scrolling und Lazy Loading. Die Entscheidung fiel auf Lazy Loading. Der ausschlaggebende Grund war ein subtiles, aber für unsere Power-User entscheidendes Detail: Lazy Loading bewahrt die native Browser-Funktionalität „Suchen auf der Seite“ (Strg/Cmd+F). Bei Infinite Scrolling geht diese verloren, da nicht geladene Inhalte nicht durchsucht werden können.

1.2 Die gescheiterte Koexistenz: Warum ein Parallelbetrieb von Alt und Neu scheiterte

Der naheliegendste und scheinbar risikoärmste Plan war, die alte und neue Version Seite an Seite innerhalb derselben Entwicklerinstanz zu betreiben und die Migration Sektion für Sektion durchzuführen. Diese Strategie wurde jedoch frühzeitig verworfen.

Die Gründe dafür lagen in der fundamentalen Inkompatibilität der zugrundeliegenden Paradigmen:

  • CSS-Konflikte: Der Versuch, Bootstrap 2.3 und Tailwind CSS parallel zu betreiben, führte zu einem unkontrollierbaren Konflikt. Bootstrap 2.3 verfolgt einen globalen, auf hohen Spezifitätswerten basierenden Ansatz, während Tailwind auf einem Utility-First-Prinzip aufbaut, bei dem Stile durch Komposition erzeugt werden. Das Ergebnis waren „CSS specificity wars“, bei denen sich Stile gegenseitig überschrieben, was zu inkonsistenten UIs und einem Debugging-Albtraum führte.

  • JavaScript-Paradoxon: Ähnlich verhielt es sich auf der JavaScript-Ebene. Der Lebenszyklus von AngularJS, der die gesamte Seite kontrolliert und auf Two-Way-Data-Binding und einen Digest-Cycle setzt, ist grundlegend unvereinbar mit dem Modell von Hotwire/Turbo. Hotwire ersetzt gezielt HTML-Fragmente, die vom Server gerendert werden. Der Aufwand, eine Koexistenz dieser beiden Welten zu ermöglichen, wurde als „zu aufwendig“ eingestuft und das Vorhaben abgebrochen.

Dieses Scheitern war keine Folge mangelhafter Ausführung, sondern basierte auf einer fehlerhaften Annahme. Wir gewannen aber eine wertvolle Erkenntnis: Bei einer Migration zwischen fundamental unterschiedlichen Architekturen ist ein „Clean Slate“-Ansatz für das Frontend oft nicht nur die bessere, sondern die einzig gangbare Option. Der gescheiterte Versuch validierte die Notwendigkeit unserer finalen, radikaleren Strategie.

1.3 Der Rollout-Plan: Eine Geschichte von zwei Stacks

Basierend auf den Erkenntnissen des Proof-of-Concepts und den ersten Entwickler-Versionen des neuen Frontends, war uns klar, dass das Risiko eines
“Big Bang” Launches zu groß war. Wir suchten also nach einem Weg, wie wir das bestehende Frontend für den Großteil unserer Kunden zugänglich halten konnten, während wir bestimmte Nutzergruppen mit dem neuen Frontend arbeiten lassen konnten.

Unsere Produktionsumgebung wurde wie folgt aufgebaut:

  • Dual-Stack-Deployment: Wir betrieben zwei vollständig getrennte Anwendungs-Stacks parallel. Der „Legacy Stack“ mit der alten Rails/AngularJS-App und der „Modern Stack“ mit der neuen Rails/Hotwire-App.

  • Der Cookie-gesteuerte Load Balancer: Ein Load Balancer fungierte als Weiche. Er inspizierte eingehende Anfragen auf ein bestimmtes Cookie. War das Cookie vorhanden, wurde der Nutzer zum Modern Stack geleitet. Fehlte es, landete er auf dem Legacy Stack.

  • Geteilte Backend-Logik: Der entscheidende Faktor, der diesen Ansatz ermöglichte, war, dass beide Stacks auf dasselbe Datenbankschema zugriffen und den Großteil der Backend-Business-Logik teilten. Dies garantierte Datenkonsistenz und erlaubte es uns sogar, kritische Bugfixes in beiden Anwendung durchzuführen (Backporting).

Diese Architektur bot immense Vorteile:

  • Risikominimierung: Wir konnten den Rollout schrittweise und kontrolliert durchführen, indem wir das neue UI für spezifische Nutzergruppen (interne Tester und ausgewählte Kundengruppen) freischalten, indem wir regelbasiert das entsprechende Cookie beim Login setzten.

  • Realitätsnahes Testen: Die Nutzer testeten das neue Frontend gegen die echte Produktionsdatenbank, was uns das bestmögliche Feedback lieferte.

  • Schneller Rollback: Bei kritischen Fehlern konnte die neue Oberfläche für eine Nutzergruppe sofort deaktiviert werden, indem die Cookie-Regel entfernt wurde – ein zuverlässiges Sicherheitsnetz.

Teil 2: Die Umsetzung – Einblicke aus dem Maschinenraum

Nach der strategischen Weichenstellung tauchen wir nun in die täglichen Realitäten der Entwicklung ein und lassen das Team mit seinen persönlichen Erfahrungen zu Wort kommen.

2.1 Das Fundament: Ein Design-System mit View Components und Tailwind CSS

Eine der wichtigsten strategischen Entscheidungen war der Aufbau eines robusten Design-Systems von Beginn an. Rails View Components erwiesen sich hierfür als das ideale Werkzeug. Sie ermöglichten die Erstellung wiederverwendbarer, isolierter und testbarer UI-Elemente. Die positiven Erfahrungen des Teams bestätigten diesen Ansatz. So fand ein Entwickler, der mitten im Rewrite zum Team stieß, eine bereits umfangreiche und sehr stabile UI-Komponentenbibliothek vor, die sofort einsatzbereit war. Ein anderer Entwickler schrieb auf die Frage, was besser funktioniert hat als erwartet: “Mostly everything - I initially thought that I would struggle much more with the new Frontend flow”. Es war also für unsere Programmierer wie für unsere Nutzer und Nutzerinnen heute: manchmal gehört eben die Bereitschaft dazu, sich auf etwas Neues einzulassen, bevor festgestellt werden kann, dass etwas besser funktioniert.

Diese anfängliche Investition in die Erstellung der Komponentenbibliothek und die Konfiguration von Tailwind CSS zahlte sich schnell aus. Sie führte zu einer nachhaltigen Beschleunigung der Entwicklungsgeschwindigkeit bei nachfolgenden Features und bestätigte unsere frühe Erkenntnis, dass nach dem initialen Bau von neuen Funktionen die Umsetzung weiterer Bereiche schneller von der Hand ging. Insbesondere das Schaffen von Komponenten, wie die Podcast-, Episoden- und Nutzerkarten, reduzierten die Komplexität sowie Code-Duplizierung und machten eine schnelle Entwicklung möglich.

2.2 Hotwire in der Praxis: Zwischen Magie und Tücke

Unser Erfahrungsbericht wäre unvollständig ohne eine ehrliche und nuancierte Betrachtung der Arbeit mit Hotwire. Oder wie ein Entwickler schrieb: “As ever, the devil is in the details.”

Die „Magie“ – Was besser funktionierte als erwartet:

Ein Entwickler, der nach einer mehrjährigen Pause zu Rails zurückkehrte, war positiv überrascht und hatte nicht erwartet, dass die Frontend-Konzepte und -Integrationen von Rails so ausgereift sein würden, wie sie sich herausstellten. Er hatte auch erwartet, weitaus mehr TypeScript schreiben zu müssen, als letztendlich notwendig war, was den Kernnutzen des Hotwire-Ansatzes unterstreicht: interaktive UIs mit minimalem clientseitigem Code. Ebenfalls fühlte sich die Inhaltsaktualisierung durch Morphing besser an als erwartet: bestimmte Seiten wurden schneller und selektiver aktualisiert als ein kompletter Reload der Seite.

Die „Tücke“ – Unerwartete Herausforderungen:

Die allgemeine Erkenntnis, dass verschachtelte Formulare in Rails hart sind, bewahrheitete sich. Die Komplexität kann sich noch erhöhen, wenn diese Formulare über Turbo Frames verwaltet werden. Das Morphing, also das intelligente Aktualisieren von DOM-Elementen, hielt einige unerwartete Überraschungen bereit. Konkrete Beispiele, die uns und unsere Nutzer vor Herausforderungen stellten, waren Textfelder, die nach dem Speichern ihre Größe auf den Originalzustand änderten, oder Kapitelmarken, die während der Bearbeitung „sprangen“. Viel Aufmerksamkeit richteten wir auch auf unseren Markdown-Editor, dessen Morph-Verhalten speziell angepasst werden musste, so dass die Cursor-Position während des Speicherns erhalten blieb. Die Lösungen, die wir letztendlich einsetzen mussten, halten sich nicht immer an die Konzepte der Hotwire/Stimulus-Welt. Ein weiteres subtiles, aber frustrierendes Problem war die Normalisierung von Formulareingaben, speziell der Unterschied zwischen einem leeren String und nil. Dies ist ein klassisches Integrationsproblem zwischen Frontend und Backend, das auch im neuen Stack sorgfältige Handhabung erfordert, aber durch die Verwendung von Rails Formularen stärker in den Vordergrund trat, als es bei dem AngularJS Frontend der Fall war.

2.3 Stimmen aus dem Team: Die menschliche Dimension der Migration

Die Migration war nicht nur eine technische, sondern auch eine menschliche Reise. Die Erfahrungen des Teams zeichnen ein Bild von anfänglicher Unsicherheit, wachsendem Mut und letztlich viel Stolz.

Anfängliche Bedenken und Lernkurve:

Der Einstieg war für einige eine Herausforderung. Für neue Entwickler im Team gab es die Schwierigkeit, die Konzepte von Hotwire/Stimulus parallel zur Einarbeitung in eine sich ständig ändernde Codebasis zu lernen. Auch wenn die anfängliche Phase, in der wir die Grundlagen für die Zukunft gelegt haben, Durchhaltevermögen erforderte, hat sie uns erst die Stabilität und Geschwindigkeit ermöglicht, die wir heute haben.

Mut und Stolz finden:

Dieser anfänglichen Phase folgte jedoch ein Wendepunkt. Nachdem zunächst viel Zeit in das Projekt floss, lieferten spätere Herausforderungen die Chance für notwendigen gesteigerten Entwicklergeist und daraus resultierenden Stolz. So entstanden viele neue Funktionen, die mittels Stimulus-Magie unseren neuen Bildeditor oder den komplett überarbeiteten Kommentarbereich und Theme-Editor ermöglichten.

Die Kraft von Team und Technologie:

Die wichtigsten Erkenntnisse waren oft menschlicher Natur. Das größte Learning für ein Teammitglied war, keine Angst vor großen Veränderungen zu haben, begründet durch ein hohes Maß an Unterstützung im Team. Ein anderer kam zu dem prägnanten Schluss, dass Ruby und Rails nicht tot sei und auch für moderne Anwendungen gut gerüstet ist. Dass Ruby on Rails funktioniert, belegt auch dieses Zitat aus dem Entwickler-Team: “Having paused Rails development for over 3 years before joining Podigee, I had not expected the Rails frontend concepts and integrations to be as mature as they turned out to be”. Diese Aussagen spiegeln das gewachsene Vertrauen des gesamten Teams wider, sowohl in die eigenen Fähigkeiten als auch in die gewählte Technologie.

Teil 3: Ergebnisse und gewonnene Erkenntnisse

Die letzte Phase des Projekts bestand darin, den Erfolg zu quantifizieren und unsere Erfahrungen in handfeste Ratschläge für andere Teams zu verwandeln.

3.1 Der Lohn der Mühe: Quantifizierbare Verbesserungen

Die beeindruckendste Metrik des Projekts ist die „Code-Diät“. Die Tatsache, dass wir 250.000 Zeilen Code löschen konnten und nur 100.000 hinzufügen mussten, bedeutet, dass für jede neue Zeile 2,5 alte Zeilen entfernt wurden - ohne dass wir Funktionen entfernt, sondern sogar neue hinzugefügt haben! Diese Zahl ist mehr als nur ein Fun-Fact; sie ist für uns ein Key Performance Indicator (KPI) für die Reduzierung von Komplexität und technischen Schulden. Die alte Codebasis war aufgebläht durch Boilerplate-Code, Workarounds und die komplexe clientseitige Logik, die dem AngularJS-Paradigma innewohnt. Der neue, schlankere Stack mit Hotwire und View Components ersetzt dieses JavaScript durch serverseitig gerendertes HTML und wiederverwendbare Komponenten. Diese Vereinfachung führt direkt zu besserer Wartbarkeit, schnellerem Onboarding für neue Entwickler und einer höheren Entwicklungsgeschwindigkeit in der Zukunft.

Die Verbesserungen waren auch für die Nutzer direkt spürbar. Das neue Frontend ist reaktionsfreudiger und schneller als das Alte. Zitat eines Entwicklers: “Also I’m proud of how it works for huge podcasts: way faster and more responsive compared to the old UI of course. I think that makes a huge positive impact.”

Über unser Monitoring sammelten wir wichtige, objektive Daten zur Veranschaulichung.

Performance-Vergleich: Legacy vs. Modern

User-Aktion

Alte App (AngularJS 1.6) - P95 Ladezeit (ms)

Neue App (Hotwire) - P95 Ladezeit (ms)

Steigerung (%)

Laden eines Podcasts

757

344

~120%

Laden des “Episoden”-Tabs eines Podcasts

757

435

~74%

Laden einer Episode

518

402

~28%

Laden der “Podcasts” Liste

1420

401

~254%

Speichern von Episoden-Änderungen

360

82

~339%

Anmerkung:

Die Ladezeiten wurden serverseitig gemessen und repräsentieren die 95. Perzentile der Antwortzeiten über einen Zeitraum von 7 Tagen.

3.2 Unsere wichtigsten Lektionen

Unsere Erfahrungen lassen sich in einige Lektionen zusammenfassen, die wir anderen Teams auf einer ähnlichen Reise mit auf den Weg geben möchten:

  1. Ein Rewrite dauert immer länger als erwartet: Eine alte Weisheit, die sich immer wieder bewahrheitet. Dies von Anfang an zu akzeptieren, hilft, realistische Erwartungen zu setzen.

  2. Friere das Design ein: Eine unserer schmerzhaftesten Lektionen war die Änderung des Farbschemas und des Design-Guides mitten im Rewrite. Dies führte zu erheblichem Mehraufwand. Die klare Lehre daraus: Design-Entscheidungen müssen vor Beginn der Implementierung finalisiert werden.

  3. Die Macht der Gewohnheit: basierend auf Nutzerfeedback, welches wir während des Rollouts sammelten, lernten wir, niemals die Macht der Gewohnheiten zu unterschätzen, die Nutzer entwickelt haben. Selbst kleine Änderungen können zu viel Verwirrung und manchmal auch Ärger führen. Ein echtes Kundenfeedback zur neuen Benutzeroberfläche: "Das (alte UI) kenne ich besser und beim Veröffentlichen sind mir zu viele Auswahlmöglichkeiten", wobei hier anzumerken ist, dass keine Erweiterung der Optionen stattfand.

  4. Die einstimmige Entscheidung: Das vielleicht stärkste Zeugnis für den Erfolg des Projekts war der einstimmige Konsens der Entwickler. Ein klares Zeichen für den Projekterfolg war die einhellige Rückmeldung des Entwicklerteams. Auf die Frage, welche Aspekte der alten Anwendung sie bevorzugen würden, antworteten alle mit „keine“. Dasselbe galt für die Frage, welche Teile man besser nicht migriert hätte.

Fazit: Hat sich der Aufwand gelohnt?

Die Antwort ist ein uneingeschränktes „Ja“. Das Projekt hat nicht nur die Code-Komplexität drastisch reduziert (250.000 gelöschte Zeilen), die Anwendungsperformance signifikant verbessert und eine moderne, wartbare Codebasis geschaffen, sondern auch potenzielle Sicherheitsrisiken durch veraltete Software eliminiert.

Doch über die technischen Errungenschaften hinaus hat das Projekt ein fähigeres und selbstbewusstes Entwicklerteam geformt, das mit dem Go-Live des neuen Frontend einen riesigen Meilenstein erreicht hat und zu Recht stolz auf seine Arbeit sein kann.

“Ich habe den Hotwire-Stack bereits für private Projekte genutzt und war extrem glücklich, als wir uns ebenfalls für diesen Stack für unser Rewrite entschieden haben. Von vornherein war absehbar, dass es herausfordernd wird und ich bin stolz auf die Leistung des Teams, welches diese Herausforderung angenommen und in mustergültiger Zusammenarbeit gemeistert hat!” (Dirk, Senior Backend Entwickler)

Die neue Plattform stellt nicht das Ende der Entwicklung dar, sondern ein Fundament für eine schnellere und bessere Produktentwicklung. Der Enthusiasmus des Teams, nun auch verbleibende Legacy-Teile (wie einige Vue-Komponenten) auf den neuen, einheitlichen Hotwire-Stack zu migrieren, signalisiert eine vielversprechende Zukunft für die Architektur unserer Anwendung.