komoot ist eine mobile App und Website für Navigation und Routenplanung für Outdoor-Aktivitäten. Sie wurde 2010 gegründet und hat, Stand März 2025, über 7 Millionen monatlich aktive Nutzer (45 Millionen gesamt) und ist die führende Outdoor-App in Deutschland und anderen europäischen Ländern.
Ich kam im Oktober 2021 als Senior Backend Engineer zu komoot und begann im Monetization-Squad, der alle zahlungsbezogenen Bereiche sowie die meisten Premium-Funktionen verwaltete. Ende 2024 wechselte ich zum "Developer Productivity"-Squad, der aus internem Bedarf des Backend-Teams gegründet wurde.
Im März 2025 wurde komoot von der italienischen Software Firma Bending Spoons übernommen. Anfang April 2025 wurde ein großer Teil der Mitarbeiter entlassen, mich eingeschlossen.
Der Tech-Stack von komoot hatte keine willkürlichen Einschränkungen; wir verwendeten im Allgemeinen das, was für die jeweilige Aufgabe am besten geeignet war. Aus Gründen der Wartbarkeit verwendeten wir jedoch in der Regel die folgenden Komponenten:
- AWS als Cloud-Plattform
- Datenbanken: MySQL (aus historischen Gründen), PostgreSQL (für alles GIS-bezogene) und DynamoDB
- Sprachen: Kotlin oder Python
- Frameworks: Spring (Boot) und ktor
- Laufzeitumgebung: AWS Fargate oder AWS Lambda-Funktionen
- CI/CD: CircleCI oder GitHub Actions
Arbeit im Monetization-Squad
Migration der Datenbank für Willkommensangebot E-Mails
komoot versendete Willkommensangebot E-Mails an neu registrierte Nutzer. Dieses Angebot bestand aus einem Rabatt von ca. 60 % auf das Produkt "Weltpaket", mit dem Nutzer offline navigieren und Kartendaten der ganzen Welt herunterladen können. Die ursprüngliche Implementierung hierfür war etwa 5 Jahre alt und sehr unflexibel. Da der Monetization-Squad das Angebot auf das abonnementbasierte "komoot Premium" ausweiten wollte, war einige Arbeit erforderlich, um dies zu ermöglichen. Dies war auch mein Onboarding-Projekt bei komoot, um mich mit der Hauptcodebasis und dem zentralen, monolithischen Kern vertraut zu machen.
Im Rahmen dieser Änderung migrierte ich die Live-Daten von der bestehenden MySQL Datenbank zu DynamoDB. Dies beinhaltete eine fortlaufende Datenreplikation mit einer Dual-Write-Strategie, gepaart mit einem Hintergrundjob, der die bestehenden Daten verschob. Außerdem implementierte ich das Versenden verschiedener E-Mails je nach Nutzertyp und Feature-Flags.
Massenmail-Versand
Für den allgemeinen Newsletter-Versand entwickelte ich ein neues Massenmail-System. Das bestehende System war sehr einfach und basierte auf dem Laden aller erforderlichen Daten in den Arbeitsspeicher, was aufgrund der schieren Größe der Nutzerbasis nicht mehr möglich war. Es gab auch andere technische Probleme, sodass wir beschlossen, es nicht zu reparieren, sondern zu ersetzen. Da personalisierter E-Mail-Versand und Templating bereits als interne Lösung implementiert waren, war es einfacher, einfach Massenmail-Versand hinzuzufügen, anstatt zu einem externen Anbieter zu wechseln.
Das System funktionierte, indem es Daten aus einem Athena-Export der relevanten Nutzerdaten lud und dann eine SQS-Queue verwendete, um eine Lambda-Funktion aufzurufen, die die E-Mails versendete. Alles wurde durch eine AWS Step Function für die Verwaltung zusammengehalten und mit einer einfachen Benutzeroberfläche verbunden, sodass Mitarbeiter des Brand Squads Newsletter als Self-Service versenden konnten.
Dynamic Promotions
In der Vergangenheit hatte komoot immer Probleme mit Verkaufskampagnen. Normalerweise enthielten diese Kampagnen verschiedene Angebote für neue Nutzer, Nutzer, die bereits ein einmaliges Produkt gekauft hatten, und Nutzer mit abgelaufenen Abonnements. Das Hauptproblem war, dass es mehrere Stellen gab, an denen die Logik implementiert war, welches Angebot ein bestimmter Nutzer erhielt: das Backend, der Versand von Kampagnen E-Mails und auch in jedem der Clients. Manchmal stimmten diese Kriterien bei Randfällen nicht ganz überein, und es gab im Laufe der Zeit Änderungen (z. B. lief ein Abonnement kurz vor/nach dem Versenden einer Angebots-E-Mail ab). Außerdem war der Großteil der Logik fest codiert und musste auch von Kampagne zu Kampagne manuell geändert werden, da wir in der Regel auch Änderungen vornahmen und A/B-Tests durchführten.
Da wir in der Regel zwischen 12 und 20 Millionen E-Mails pro Kampagne versendeten, führte selbst ein sehr kleiner Teil dieser Probleme zu Hunderten von Fragen an unseren Support bezüglich der Preise, die nicht dem entsprachen, was wir in einer bestimmten E-Mail versprochen hatten, was dann auch manuelle Arbeit erforderte, um die versprochenen Preise für Nutzer, die wirklich darauf bestanden, zu realisieren.
Auch hatten wir zwei völlig unterschiedliche Konzepte von Werbeaktionen, die den Nutzern Angeboten wurden. Das eine waren die bereits erwähnten Verkaufskampagnen, die nach einem von uns festgelegten Zeitplan stattfanden. Nutzer erhielten aber auch individuelle Angebote für Upselling oder als Win-Back-Angebote bei Abonnementkündigung.
"Dynamic Promotions" wurde entwickelt, um diese Probleme ein für alle Mal zu lösen. Die Kernidee war, eine explizite Konfiguration (anstatt Code) für jede Werbeaktion bereitzustellen, die wir durchführen wollten. Diese wurde dann einmal für jeden Nutzer ausgewertet, und alle Entscheidungen wurden getroffen, persistent gespeichert und dann als "source of truth" für jede andere zu treffende Entscheidung angesehen. Es funktionierte wie folgt:
- Bereitstellung einer JSON-basierten Konfiguration, die die gesamte Kampagne oder Werbeaktion detailliert beschreibt
- Erfassung aller erforderlichen Nutzerdetails, um eine Entscheidung zu treffen
- Speicherung dieser Entscheidung
- Generierung und Planung aller zukünftigen Kommunikation (z. B. E-Mails oder Push-Benachrichtigungen)
Dieser Vorgang wurde entweder als Batch-Job für alle Nutzer ausgeführt oder durch Ereignisse ausgelöst (z. B. "Wenn der Nutzer ein Kartenpaket kauft, biete ihm ein vergünstigtes Premium-Abonnement für 2 Wochen an und versende zwei E-Mails"). Die Werbeaktionen enthielten auch Details zur In-App-Kommunikation, die als API für alle Clients bereitgestellt wurde, damit diese entsprechende Nachrichten anzeigen konnten.
Das gesamte System wurde um einige einfache Lambda-Funktionen, DynamoDB für die Speicherung und eine ziemlich große AWS Step Function für die gemeinsame Verarbeitung aller Nutzer und Ereignisse aufgebaut. Wir haben auch verschiedene Testfunktionen integriert, wie einen Dry-Run-Modus zur Validierung ganzer Kampagnen oder eine Test-CLI für unser QA-Team, um einzelne Werbeaktionen auch ohne die Ausführung der erforderlichen Nutzeraktionen zu testen.
Das System funktionierte bemerkenswert gut und wurde im Laufe der Zeit an weitere Anwendungsfälle angepasst und erweitert und ist immer noch der Haupttreiber für Werbeaktionen bei komoot.
Übergabe an mich
Einige Monate nach meinem Eintritt musste der andere Backend-Entwickler im Squad das Unternehmen aus persönlichen Gründen verlassen. Obwohl dies mich unglücklicherweise als einzigen Backend-Entwickler im Squad zurücklassen würde, haben wir schnell einen Plan für eine gute Übergabe ausgearbeitet. Dazu haben wir uns zusammengesetzt und eine Liste aller Repositories, Funktionen und Dokumentationen zusammengestellt, die mit dem Verantwortungsbereich des Monetization-Squads zusammenhängen. Über einige Monate hinweg habe ich dann mehrere Stunden pro Woche damit verbracht, den Code und die Dokumente im Detail zu studieren. Jede Woche kamen wir zusammen, und ich ging alle offenen Fragen durch. Infolgedessen habe ich dann den Code oder die Dokumente in Frage verbessert, indem ich sie refaktoriert, weitere Code-Kommentare hinzugefügt oder die Dokumentation (neu) geschrieben habe.
Dieser Prozess erwies sich als sehr effektiv, sodass ich die alleinige Entwicklerrolle im Squad übernehmen konnte, ohne das Gefühl zu haben, große Wissenslücken zu haben. Die verbesserte Dokumentation bewies auch später ihren Wert, als ein neuer Entwickler zum Monetization-Squad hinzustieß.
A/B-Tests
Im Monetization-Squad hatten wir natürlich das Ziel, den Umsatz zu steigern. Aus diesem Grund haben wir im Laufe der Jahre viele A/B-Tests durchgeführt, von denen die meisten eine Art Unterstützung vom Backend erforderten. Einige Tests, die wir durchgeführt haben, sind:
- Entfernung der Versicherung: Wir haben experimentiert, wie wir die Fahrradversicherung aus dem komoot Premium-Abonnementangebot entfernen können, bevor wir dies tatsächlich umsetzten.
- Unterschiedliche Shop-Anzeige: Je nach verschiedenen Faktoren haben wir experimentiert, dem Nutzer verschiedene Produkte als erstes, großes Element auf der "Shop"-Seite anzuzeigen. Entweder wurde dem Nutzer zuerst das Premium-Produkt oder die Kartenprodukte angezeigt.
- Preistests: Wir haben verschiedene Preise für das Abonnementprodukt getestet.
- Verkaufskampagnen: Bei komoot haben wir im Laufe des Jahres verschiedene Verkaufskampagnen durchgeführt. Normalerweise machten wir eine kurze zum neuen Jahr, eine im Sommer während der Hauptsaison und dann eine weitere im frühen Herbst. Die Kampagnen beinhalteten immer verschiedene Preisniveaus, Nutzerauswahlen und Kommunikationsstile. Bevor wir das System "Dynamic Promotions" einführten, mussten wir oft die Backend-Logik ändern oder anpassen, um die verschiedenen Ideen zu unterstützen, die sich der PM und der Designer ausgedacht hatten.
Entfernung der Versicherung
Das Premium-Angebot von komoot beinhaltete eine Fahrradversicherung (unterstützt vom deutschen Versicherer AXA) ohne zusätzliche Kosten. Obwohl das Angebot immer in jedem Abonnement enthalten war, mussten Nutzer einige manuelle Schritte unternehmen, um es zu aktivieren, vor allem ihre persönliche Adresse angeben, da dies von AXA benötigt wurde. Damit dies reibungslos funktionierte, gab es eine relativ komplizierte Zwei-Wege-Synchronisierung zwischen unseren Anwendungen und einer AXA-API.
Im Jahr 2022 wurde beschlossen, das Angebot aus dem Premium-Abonnement zu entfernen. Dies war relativ kompliziert, da wir sicherstellen mussten, dass das Angebot nicht mehr in neuen und verlängerten Abonnements enthalten war, und gleichzeitig dafür Sorge tragen mussten, dass es für alle bestehenden Abonnements, die vor einem bestimmten Stichtag erstellt wurden, weiterhin funktionierte. Aus diesem Grund konnte die endgültige Entfernung des Codes erst ein volles Jahr nach der Einstellung des Angebots erfolgen.
Unterstützung neuer Länder und Preise
komoot klassifizierte die Länder, in denen wir tätig waren, in verschiedene Stufen. Länder der Stufe 1 hatten eine vollständige Lokalisierung in die Landessprache, und auch die Preise wurden in die jeweiligen Landeswährungen lokalisiert. Im Jahr 2022 fügte komoot Polen, Brasilien, Japan und Südkorea zur Liste der Länder der Stufe 1 hinzu, sodass die unser Code diese alle unterstützen mussten. Dies war ein interessantes Unterfangen, da komoot zwar nie den Fehler gemacht hat, Geldwerte als Gleitkommazahlen zu speichern, aber der gesamte Code annahm, dass alle Geldwerte in der kleinsten Währungseinheiten (z. B. Cent) gespeichert wird und dass jede Währung 100 kleine Währungseinheiten für jede große Währungseinheit hat. Eine Annahme, die für den südkoreanischen Won und den japanischen Yen nicht zutrifft. Dies erforderte eine Reihe von Refactorings, um alles ordnungsgemäß zum Funktionieren zu bringen.
"Free experience" und Verschieben von Funktionen hinter eine Paywall
Im Jahr 2024 wollten wir bestimmte Funktionen für neue Nutzer kostenpflichtig machen. komoot versuchte im Allgemeinen zu vermeiden, Dinge bereits bestehenden Nutzern wegzunehmen. Daher beinhaltete dieses Projekt, herauszufinden, wo die verschiedenen Prüfungen durchgeführt wurden, und sie so anzupassen, dass sie für bestehende Nutzer weiterhin funktionierten, für neue Nutzer jedoch ein gültiges Abonnement erforderlich war.
Damit einhergehend haben wir auch die Möglichkeit einer "free experience" für diese Funktionen hinzugefügt, bei denen neue Nutzer sie eine Weile kostenlos nutzen konnten, dann aber mit einer Paywall und einem zeitlich begrenzten Angebot für ein Abonnement konfrontiert wurden.
Kleinere Projekte
Abgesehen von den bereits erwähnten Projekten im Monetization-Squad war ich auch an verschiedenen kleineren Projekten beteiligt:
- Verbesserung von Geschäftsereignissen für das Analyseteam, um weitere Informationen hinzuzufügen.
- Viele, viele Fehlerbehebungen.
- Ermöglichung für komoot-Teammitglieder, Premium-Gutscheine zu einem reduzierten Preis für Familie und Freunde zu kaufen.
- Mehrwertsteuer- und preisbezogene Änderungen.
- Automatisierter Rückerstattungsprozess für Abonnements in unserem Kundensupport-Backend mit einem Klick auf eine Schaltfläche.
Arbeit im Backend-Team
Während meine Hauptrolle im Monetization-Squad lag, hatten wir auch Zeit, um im Backend-Team an nicht produktbezogenen Dingen zu arbeiten. Dies geschah in der Regel entweder am "Quality Monday" oder im Rahmen der vierteljährlichen Team-OKR-Wochen, in denen wir uns mit technischen Themen befassten, die nicht in die Mission der Squads passten. Einige bemerkenswerte Beispiele hierfür sind:
Hiring
Im Jahr 2023 trat ich auch der Hiring Gruppe für das Backend-Team bei. Unser Einstellungsprozess beinhaltete eine Programmieraufgabe, die an jeden Bewerber gesendet wurde, und dann 2 separate Interviews mit jeweils 2 Personen. Das erste,Interview konzentrierte sich auf die technischen Fähigkeiten, während das zweite mehr auf Produkt- und Funktionsentwicklung ausgerichtet war. Ich war an der Überprüfung von Aufgabeneinsendungen von Bewerbern beteiligt und bewertete sie anhand eines festgelegten Katalogs von Kriterien und Fragen. Ich war auch Teil der Gruppe von Personen, die das erste, technische Interview führten.
Nach jeder Interviewrunde kamen wir vier Personen zusammen und trafen gemeinsam eine Entscheidung darüber, ob wir dem Kandidaten ein Angebot machen würden oder nicht.
Cookiecutter
Um die Zeit zu verkürzen, die benötigt wird, um einen neuen Microservice oder eine neue Lambda-Funktion innerhalb des Backend-Teams zu starten, habe ich ein Cookiecutter Repository für beide Anwendungsfälle implementiert. Dies basierte auf einem bestehenden, einfachen template Repository, das nur wenige Platzhalter enthielt, aber bis zur ersten Bereitstellung des neuen Stacks viel manuelle Arbeit erforderte.
Ich habe zwei Variationen implementiert, die auch einen gemeinsamen Teil verwendeten, um einen Generator zu haben, der zwei Arten von häufig verwendeten Repositories vollständig erstellen konnte:
- Eine Lambda-Funktion mit Optionsunterstützung für SQS und DynamoDB
- Ein ECS Fargate-basierter Dienst, der ktor verwendet
Beide Vorlagen waren nach der Generierung einsatzbereit, sodass sie direkt bereitgestellt werden konnten (wenn auch ohne Funktionalität). Ich habe dem Repository auch automatisierte Integrationstests hinzugefügt, die sicherstellten, dass alle möglichen Variationen ein gültiges Projekt generieren würden. Der Integrationstest hat es zur Überprüfung auch tatsächlich in ein dediziertes AWS-Konto bereitgestellt und den resultierenden Lambda-/ECS-Dienst aufgerufen.
Fargate-Migration des Monolithen
Der zentrale Monolith von komoot lief lange Zeit auf einfachen EC2-Instanzen. Die Skalierung und Bereitstellung wurde über boxfuse verwaltet. Wir beschlossen, ihn zu einem ECS Fargate Service zu migrieren, um mehr, kleinere Instanzen ausführen und schneller hoch- und herunterskalieren zu können sowie die Betriebskosten leicht zu senken.
Dieses Projekt beinhaltete die Einrichtung eines neuen Fargate-Dienstes für den Monolithen, die Implementierung der automatischen Skalierung, die Migration aller Metriken und Alarme und wurde innerhalb eines Monats ohne Ausfallzeiten oder Auswirkungen auf die Nutzer abgeschlossen.
On-Call-Prozesse
Ich habe die Prozesse rund um unsere On-Call-Planung auf verschiedene Weise verbessert. Ich habe ein Utility in Rust geschrieben, das automatisch bevorstehende Konflikte zwischen der On-Call-Rotation und den in unserer Mitarbeiterverwaltungssoftware Bob eingegebenen Urlaubszeiten erkennen würde. Diese würden dann automatisch an die Nutzer gesendet, die den Konflikt verursacht haben, damit sie sich darum kümmern konnten, jemanden zum Tauschen zu finden.
Ende 2024 haben wir auch ein Ausgleich für die On-Call-Bereitschaft eingeführt. Für jeden Tag On-Call-Bereitschaft wurde Freizeit gutgeschrieben. Diese Tage konnten entweder als bezahlte Freizeit genommen werden oder, wenn sie nicht genommen wurden, nach 3 Monaten automatisch ausgezahlt werden. Ich habe einige Python-basierte Codes darum herum implementiert, um den Prozess der Verfolgung der verdienten Zeit pro Nutzer vollständig zu automatisieren und sie auch zur Auszahlung an die Gehaltsabrechnung zu senden.
"Backend-Hub"-Dokumentation
Das Backend-Team hatte verschiedene Dokumente über den gesamten Ort verstreut. Von Onboarding- und allgemeinen Einrichtungsanleitungen über On-Call-Handbücher bis hin zu anderer nicht systemspezifischer Dokumentation. Der Großteil davon lag in Form von Google Docs-Dateien vor, aber es gab auch Präsentationen, README-Dateien und verschiedene andere Ressourcen.
Ich habe mir die Zeit genommen, einen dedizierten Hub für die gesamte Backend-bezogene Dokumentation in einem dedizierten GitHub-Repository einzurichten, das eine Intranet-zugängliche, MkDocs-basierte Reihe von Seiten generiert, die leicht navigiert und durchsucht werden können. Im Laufe der Zeit habe ich zusammen mit dem Team die gesamte kritische Dokumentation migriert.
API-Testmigrationen und -Verbesserungen
komoot verwendete ursprünglich das Robot Framework für API-basierte Integrationstests. Im Laufe der Zeit wurden diese schwer zu skalieren, sodass Jahre vor meinem Eintritt beschlossen wurde, dass neue Tests als einfache Python-basierte Tests geschrieben werden sollten. Ich habe die Initiative ergriffen, die meisten der noch vorhandenen Robot-Tests zu den Python-Tests zu migrieren, damit wir von einer besseren Parallelisierung profitieren und auch den zusätzlichen Schritt in unserer CI-Pipeline einsparen konnten. Der Großteil der Arbeit wurde in meinem ersten Monat erledigt, teilweise um mich mit der vorhandenen API und den Funktionalitäten vertraut zu machen, während der Rest im Laufe der Jahre aufgeräumt wurde.
Ich habe auch immer wieder Zeit damit verbracht, die vorhandenen API-Tests zu stabilisieren, indem ich Wiederholungslogik hinzugefügt, Race Conditions behoben und einige seltsame Fehler behoben habe, die immer vorhanden waren, aber nur zu manchmal fehlerhaften Tests führten.
Quality Monday
Bei komoot war jeder Montag der Behebung nicht kritischer Fehler und kleinerer Verbesserungen im Backend-Code gewidmet. Jeden Montag kamen wir in einem Zoom-Anruf zusammen, um uns Fehlerberichte (die bereits von unserem QA-Team vorbereitet wurden) und metrikbezogene Warnungen der Vorwoche anzusehen und zu versuchen, sie zu lösen (oder sie in die Produkt-Squads zu verschieben). Die Zuweisung von Fehlern erfolgte durch uns selbst, sodass ich im Laufe der Jahre auf die eine oder andere Weise an fast allen Teilen von komoot gearbeitet habe.
Arbeit im "Developer Productivity"-Squad
Der "Developer Productivity"-Squad wurde Ende 2024 als Initiative aus dem Backend-Team heraus gegründet. Wir alle wollten mehr Zeit für technische Aufgaben aufwenden, von denen alle profitieren würden. Um diesem Bedarf gerecht zu werden, haben wir diesen neuen Squad mit zwei Entwicklern als mittelfristiges Experiment gegründet, um zu sehen, wie nützlich diese Idee ist.
Erhöhung der Pipeline-Geschwindigkeit
Die erste Aufgabe, die wir in Angriff nahmen, war eine Anstrengung zur Pipeline-Geschwindigkeit unseres Kernmonolithen zu steigern. Es handelte sich um eine ziemlich große Anwendung, daher gab es eine riesige Anzahl von Unit-, Integrations- und API-Tests. Das Ausführen dieser Pipeline für eine neue Pull-Request dauerte über 45 Minuten, während eine einfache production deployment fast 30 Minuten dauerte. Da viel Zeit von mehreren Entwicklern für die Arbeit an dieser Codebasis aufgewendet wurde, würde jede Minute, die im CI-Schritt reduziert wird, die allgemeine Iterationsgeschwindigkeit erheblich verbessern.
Wir haben einige Zeit damit verbracht, den allgemeinen Build zu beschleunigen, indem wir unnötige Schritte entfernten, mehr Parallelität eingeführt und einzelne Schritte im Allgemeinen beschleunigt haben.
Ein weiterer Teil der Zeit wurde damit verbracht, die Bereitstellung von Branch-Stacks pro Branch zu beschleunigen. Ursprünglich wurden Datenbank-Snapshots verwendet, um die beiden Hauptdatenbanken für die Anwendung zu erstellen, was lange dauerte, da die Snapshots ziemlich groß waren. Wir haben dies gelöst, indem wir stattdessen leere Datenbanken verwendet und dann Test-Fixtures eingeführt haben, um das Datenbankschema und den Inhalt einzurichten.
Insgesamt haben unsere Bemühungen die Produktionsbereitstellung auf 18 Minuten und die Branch-Stack-Erstellung auf etwa 22 Minuten reduziert.
Feature-Flagging
Meine letzte Aufgabe bei komoot war die Implementierung einer Feature-Flagging- und Experimentierplattform für alle Anwendungen und Clients. Zuvor hatten wir nur eine interne Experimentierplattform für A/B-Tests, und das Backend hatte ein Remote-Feature-Flagging. Wir wollten das Feature-Flagging und die Experimente auf einer Plattform vereinheitlichen und auch eine gemeinsame, Remote-Feature-Flagging-Lösung für alle Backend-Dienste, das Web-Frontend und mobile Apps bereitstellen.
Dazu haben wir GrowthBook in unserem AWS-Konto bereitgestellt und es in unser Data Warehouse und unsere Ereignispipeline integriert. Ich habe auch die Integration in alle relevanten Backend-Dienste hinzugefügt und Unterstützung und Dokumentation für die clientseitige Integration bereitgestellt.
Leider wurde dieses Projekt nie vollständig abgeschlossen, da komoot im März 2025 von Bending Spoons übernommen wurde, wodurch alle aktuellen Projekte gestoppt und viele Entwickler entlassen wurden.