Feature-Toggles mit umgebungsbasierter Konfiguration
Der einfachste Weg, ein halbfertiges Feature in Produktion zu bringen, ohne es offenzulegen, ist zugleich der am häufigsten schlecht neu erfundene: ein Boolean, aus der Konfiguration gelesen, in einem if geprüft. Bevor man zu einem Feature-Flag-Dienst mit Dashboard und SDK greift, lohnt es sich zu wissen, wie weit ein schlichter umgebungsbasierter Toggle tatsächlich trägt – und das ist weiter, als die meisten Teams annehmen.
Die Kernidee
Ein Feature-Toggle ist eine Laufzeitprüfung, die entscheidet, ob ein Codepfad ausgeführt wird. Der Wert des Flags kommt von außerhalb des Codes – Umgebungsvariablen, eine Konfigurationsdatei, eine Datenbankzeile –, sodass sich das Verhalten ohne neues Deployment ändern lässt.
const features = {
newCheckoutFlow: process.env.FEATURE_NEW_CHECKOUT === 'true',
betaExportApi: process.env.FEATURE_EXPORT_API === 'true',
};
function handleCheckout(req, res) {
if (features.newCheckoutFlow) {
return handleCheckoutV2(req, res);
}
return handleCheckoutV1(req, res);
}
Das ist der gesamte Mechanismus. Alles andere – prozentuale Rollouts, Zielgruppen pro Nutzer, Notausschalter – ist Verfeinerung auf Basis von „einen Wert lesen und darauf verzweigen".
Warum der Aufwand, statt das Feature einfach zu deployen
- Deploy von Release entkoppeln. Den Code hinter einem Flag auf
falsemergen und deployen, und dann – ohne Deploy – separat einschalten, wenn man wirklich bereit ist. So wird das Freigeben zu einer Konfigurationsänderung statt einer Codeänderung, die sich schneller durchführen und schneller zurücknehmen lässt. - Notausschalter. Verhält sich ein neuer Codepfad in Produktion falsch, ist das Abschalten eines Flags sofort wirksam. Ein Deployment zurückzurollen dauert länger und macht alles in diesem Deployment rückgängig, nicht nur das kaputte Feature.
- Umgebungsspezifisches Verhalten. Ein Feature wochenlang vor Produktion in Staging aktivieren, ohne einen separaten Branch pflegen zu müssen.
Eine Konfigurationsschicht, die über eine Handvoll Booleans hinaus skaliert
process.env.FEATURE_X === 'true'-Prüfungen über die Codebasis zu streuen funktioniert bei zwei Flags und wird bei zehn unwartbar. Zentralisiere es:
// config/features.js
function bool(name, fallback = false) {
const value = process.env[name];
if (value === undefined) return fallback;
return value === 'true' || value === '1';
}
module.exports = {
newCheckoutFlow: bool('FEATURE_NEW_CHECKOUT'),
betaExportApi: bool('FEATURE_EXPORT_API'),
strictRateLimit: bool('FEATURE_STRICT_RATE_LIMIT', true), // defaults on
};
Jetzt importiert jede Aufrufstelle aus einer einzigen Stelle, jedes Flag hat eine Zeile, die den Standardwert dokumentiert, und ein neues Flag hinzuzufügen ist eine Ein-Zeilen-Änderung.
const features = require('./config/features');
if (features.betaExportApi) {
app.use('/api/export', exportRouter);
}
Flags behandeln, die Zielgruppen pro Nutzer brauchen
Umgebungsvariablen sind global – jede Anfrage in diesem Prozess sieht denselben Wert. Das ist in Ordnung für „ist dieses Feature in Produktion überhaupt live", aber nicht für „zeig das 5 % der Nutzer" oder „zeig das nur internen Konten". Dafür braucht die Flag-Prüfung ein Stück Request-Kontext:
function isEnabledForUser(flagName, user) {
if (!features[flagName]) return false;
// internal accounts always see flags that are otherwise off for everyone
if (user?.isInternal) return true;
// deterministic percentage rollout: same user always gets the same bucket
const bucket = hashToPercent(`${flagName}:${user.id}`);
return bucket < rolloutPercent(flagName);
}
function hashToPercent(input) {
let hash = 0;
for (let i = 0; i < input.length; i++) {
hash = (hash * 31 + input.charCodeAt(i)) >>> 0;
}
return hash % 100;
}
Das wichtige Detail ist, dass hashToPercent deterministisch pro Nutzer ist, nicht zufällig pro Anfrage – ein Nutzer in den 5 % des Rollouts sollte bei jeder Anfrage darin bleiben, sonst entsteht eine inkonsistente Erfahrung (die neue UI bei einem Seitenaufruf, die alte beim nächsten), die für Nutzer verwirrend und aus einem Fehlerbericht heraus unmöglich zu debuggen ist.
Wo Flags sterben
Der Fehlerfall von Feature-Flags ist nicht, sie hinzuzufügen – sondern sie nie zu entfernen. Ein Flag, das seit sechs Monaten in jeder Umgebung true ist, ist toter Ballast: ein Zweig, dessen false-Seite niemand mehr testet, eine Bedingung, die jeder künftige Leser im Kopf auswerten muss, und eine Quelle für „Moment, ist dieser Code überhaupt erreichbar?" beim Debuggen.
Behandle ein Flag als temporäres Konstrukt mit erwarteter Lebensdauer:
- Benenne es nach dem Feature, nicht nach der Ticketnummer (
newCheckoutFlow, nichtPROJ-4821) – es sollte ein Jahr später selbsterklärend sein. - Wenn ein Flag eine Weile ohne Zwischenfälle bei 100 % in Produktion steht, ist das ein Ticket, um das Flag und den alten Codepfad zu löschen, kein Dauerzustand.
- Flags, die wirklich dauerhaft gedacht sind (ein Ops-Notausschalter, ein Toggle pro Umgebung), sind eine andere Kategorie – benenne und dokumentiere sie anders, damit niemand versucht, sie „aufzuräumen".
Wenn Umgebungsvariablen nicht mehr reichen
Schlichte umgebungsbasierte Flags stoßen an eine echte Grenze: Eines zu ändern erfordert weiterhin, die Deploy-Umgebung anzufassen (ein Redeployment oder mindestens einen Prozessneustart, um den neuen Wert aufzunehmen), und es gibt kein Audit-Log darüber, wer was wann umgelegt hat. Das ist der Punkt, an dem sich ein dedizierter Flag-Dienst (LaunchDarkly, Unleash oder eine einfache Flags-Tabelle in der eigenen Datenbank mit Admin-Oberfläche) seine Komplexität verdient – sofortiges Umschalten ohne Deploy, Regeln pro Nutzer ohne selbstgebautes Hashing und eine Änderungshistorie.
Greife aber nicht am ersten Tag danach. Ein Dutzend Umgebungsvariablen, gelesen über ein kleines Konfigurationsmodul, trägt ein Projekt bequem lange, und das Konzept eines Flags (ein Name, ein Boolean, Aufrufstellen, die es prüfen) später auf einen echten Flag-Dienst zu migrieren, ist eine mechanische Änderung, kein Redesign.
Fazit
Ein Feature-Flag ist nur ein if, dessen Bedingung außerhalb des Codes lebt. Zentralisiere die Prüfungen, mache prozentuale Rollouts deterministisch pro Nutzer und – der Teil, den alle überspringen – lösche Flags tatsächlich, sobald sie sich bei 100 % eingependelt haben. Die Infrastruktur, um Flags im großen Maßstab zu verwalten, ist optional; die Disziplin, sie zu entfernen, nicht.