Vorwort: Warum Features Releases zerstören?
\nJeder Entwickler war schon einmal in der Situation, dass eine neue, glänzende Funktionalität fertig ist, aber noch nicht ausgerollt werden kann. Vielleicht hat sie die vollständige Qualitätssicherung nicht bestanden, das Marketing hat noch keine Banner vorbereitet, oder es ist einfach beängstigend, am Freitagabend auf den "Deploy"-Button zu drücken. In diesem Moment beginnen wir normalerweise, if-Abfragen zu schreiben, BuildConfig umzuschalten oder, noch schlimmer, Code auszukommentieren.
Kommt Ihnen das bekannt vor? Willkommen in der Welt der Feature Flags (oder Feature Toggles). Dies ist nicht nur "ein weiteres architektonisches Ding". Es ist Ihr Rettungsanker, der es Ihnen ermöglicht, Code in die Produktion auszurollen, ihn aber nur für ausgewählte Personen zu aktivieren. In diesem Artikel werden wir uns ansehen, wie man Feature Flags in Kotlin-Projekten implementiert – von einfachen Lösungen bis hin zu fortschrittlichen Systemen. Und ja, es wird viel Code geben.
\n\nWas ist ein Feature Flag und warum brauchen Sie es?
\nEin Feature Flag (auch Feature Toggle genannt) ist ein programmgesteuerter Schalter, der es ermöglicht, Funktionalitäten ein- oder auszuschalten, ohne den Code zu ändern oder erneut bereitzustellen. Stellen Sie sich vor, Sie könnten einen neuen Empfehlungsalgorithmus in die Produktion einspielen, ihn aber nur für 5% der Benutzer aktivieren. Oder ihn nur für Mitarbeiter des Unternehmens zum internen Testen einschalten. Ziemlich cool, oder?
\nHier sind einige Szenarien, in denen Feature Flags unverzichtbar sind:
\n- \n
- Canary Releases: Rollen Sie ein neues Feature auf 1% der Server aus, überwachen Sie Fehler und erhöhen Sie den Prozentsatz schrittweise. \n
- A/B-Testing: Testen Sie, welche UI-Version besser konvertiert, ohne das gesamte Projekt zu kopieren. \n
- Kill Switch: Wenn ein neues Feature Speicher "frisst" oder einen Bug verursacht, schalten Sie es einfach mit einer Bewegung (oder einem API-Aufruf) aus. \n
- Trennung von Release und Deployment: Der Code ist eingespielt, aber das Feature "schläft" bis zum Datum X. \n
In Kotlin, das sowohl für das Backend (Ktor, Spring Boot) als auch für mobile Anwendungen (Android) aktiv genutzt wird, werden Feature Flags aufgrund der strengen Typisierung und der Anforderungen an sauberen Code besonders relevant.
\n\nArchitektur: Wie man Flags nicht in Spaghetti verwandelt
\nDer größte Fehler von Anfängern ist es, if (flag.isEnabled()) { ... } else { ... } im gesamten Projekt zu verstreuen. Dies führt dazu, dass Sie nach einem Monat nicht mehr verstehen, welche Codezweige lebendig und welche tot sind. Wir brauchen Feature-Management, keine chaotischen Schalter.
Eine gute Architektur basiert auf dem Prinzip der Abhängigkeitsinversion. Ihr Geschäftscode sollte nicht wissen, woher der Wert des Flags stammt (aus einer Datei, einer Datenbank oder einem externen Dienst). Er fragt einfach: "Hey, FeatureFlagService, ist das Feature new_checkout aktiviert?"
Betrachten wir zwei Ansätze: einen einfachen (für kleine Projekte) und einen fortgeschrittenen (für Microservices und Kubernetes).
\n\n1. Einfacher Ansatz: Konfiguration und Enum
\nWenn Sie schnell starten müssen und kein System mit einer Million Anfragen pro Sekunde bauen, können Sie mit einer einfachen enum class und einer Konfigurationsdatei (z. B. application.conf für Ktor oder application.yml für Spring) auskommen.
Erstellen wir ein Enum aller Features:
\nenum class Feature(val key: String) {\n NEW_PAYMENT_FLOW("feature.new-payment-flow"),\n DARK_MODE("feature.dark-mode"),\n SMART_SEARCH("feature.smart-search")\n}\nJetzt der Service, der die Konfiguration liest:
\nclass FeatureFlagService(private val config: Map<String, Boolean>) {\n\n fun isEnabled(feature: Feature): Boolean {\n return config.getOrDefault(feature.key, false)\n }\n\n // Für A/B-Tests kann eine Randomisierung hinzugefügt werden\n fun isEnabledForUser(feature: Feature, userId: String): Boolean {\n if (!isEnabled(feature)) return false\n // Einfachster Hash zur Aufteilung in Gruppen\n val group = userId.hashCode() % 100\n return group < 50 // 50% der Benutzer\n