Continuous Delivery mit Feature Toggles

In vielen Projekten erfolgt die Entwicklung von neuen Features in dem einen oder anderen Branching Modell wie zum Beispiel Git Flow: Features werden zunächst auf einem separaten Branch entwickelt und erst nach „Fertigstellung“ des Features wieder integriert. Beim Mergen der Änderungen kommt es dann gelegentlich zu Konflikten und wenn man Pech hat, landet man in der „Merging Hell“. Moderne VCS wie GIT machen Branching + Merging zwar deutlich einfacher, ändern aber auch nichts daran, dass es gelegentlich schwierig ist, konkurrierende Änderungen zu einem lauffähigen Deployment zu integrieren.

Das Hauptproblem mit Feature Branches ist aber ein anderes: Wann ist ein Feature „fertig“, kann also integriert werden? Erfolgt die Abnahme einer Story auf Basis des Feature Branches, muss nach der Integration eine weitere Qualitätssicherung erfolgen – denn sonst könnten sich Fehler in der Kombination mit parallel entwickelten Features einschleichen. Erfolgt die Qualitätssicherung erst nach der Integration auf einem Release Branch, könnte sich herausstellen, dass das Feature eben doch nicht fertig ist, weil die eine oder andere Anforderung nicht erfüllt ist.

Continuous Integration verfolgt daher einen anderen Weg: die Entwicklung erfolgt auf dem HEAD und jeder Commit wird direkt automatisiert integriert. Jenkins, TeamCity oder andere Tools helfen dabei, regelmässig alle paar Minuten einen aktuellen Build zu erstellen und auf einen CI Server zu deployen. Die Abnahme der Feature erfolgt entweder auf dem CI Server oder einer separaten Stage der Build Pipeline.

Eine sehr gute automatisierte Testabdeckung ist die Voraussetzung dafür, dass die Software jederzeit lauffähig ist und alle Anforderungen erfüllt bleiben.
Feature Toggle stellen sicher, dass ein Feature erst dann aktiviert wird, wenn es fertig und von der Qualitätssicherung abgenommen ist. Dazu werden die neuen Funktionen der Software über einfache if-Statements geschaltet:
if (Features.NEW_FANCY_FEATURE.isActive()) {
        useMyFancyNewFeature();
} else {
        doTheOldBoringStuff()
}
Mit der Verwendung von Feature Toggles erhalten wir ganz neue Möglichkeiten, Funktionen live zu stellen. Features lassen sich nämlich auch über „Ventile“ (Valves, Activation Strategies) graduell aktivieren:
  • Zunächst nur für einzelne User, beispielsweise Tester, Product Owner oder „Friendly Customers“.
  • Für ein Prozent der User, später 50% mit einem A/B Test, der den Erfolg des Features sicherstellt, irgendwann dann für alle User.
  • Zeitlich gesteuert, um bspw. zu einem Release Date scharf zu schalten.
  • Feature lassen sich auch auf nur einem Server des Clusters aktivieren.
  • …und natürlich umgebungsspezifisch für ein oder mehrere Stages der Deployment Pipeline.
Die letzte Entscheidung, ob eine neue Funktion Online geht kann also in der Live Umgebung erfolgen, was dabei hilft, regelmässig und kurz getaktet zu deployen. Im Team „Entdecken“ setzen wir Feature Toggles bereits seit einiger Zeit ein. Aktuell gehen wir alleine in unserem Team (das System besteht aus mehreren lose gekoppelten Anwendungen) mehrmals die Woche, teilweise auch mehrmals am Tag live.

Die Integration von Feature Toggles in die Software ist einfach. Eine rudimentäre Lösung ist schnell selbst entwickelt, es gibt aber auch fertige Lösungen. Wir haben vor einiger Zeit von einer Eigenentwicklung auf die Togglz Library umgestellt: eine schicke, schlanke Library, die sich sehr leicht in alle möglichen Arten von Java-Anwendungen integrieren lässt und auch eine Console zur Verwaltung der Toggle mitbringt. Die oben genannten „Activation Strategies“ werden (unter anderem) unterstützt, es lässt sich leicht eine Rechte-Verwaltung anbinden und auch die Persistenz der Toggle-Einstellungen ist leicht möglich.

Togglz Administration Console
Togglz Administration Console
Toggles verwenden wir dabei in verschiedenen Situationen:
  • Neu entwickelte Features werden zunächst deaktiviert. In den Tests und Test-Umgebungen werden neue Feature aktiv geschaltet.
  • Alle Caches werden über (per default aktivierte) Toggles geschaltet. Sollte es zu Inkonsistenzen der gecachten Daten kommen, lässt sich das Caching deaktivieren.
  • Bestimmte Funktionen werden dauerhaft über Toggles abgesichert: beispielsweise können wir auf diese Weise den Zugriff auf unkritische externe Systeme ausschalten oder gleich die gesamte Personalisierung ausknipsen, so dass nur noch cachebare Seiten ausgeliefert werden.

Die Togglz Library unterstützt derartige Gruppierungen über die FeatureGroup Annotation. Wir haben uns für die verschiedenen Anwendungsfälle eigene Annotationen geschrieben. Features, die noch in der Entwicklung sind, werden beispielsweise über die InDevelopment geschaltet:

@FeatureGroup
@Label("Features in development")
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InDevelopment {
}

public enum Features implements Feature {
    @Label("Only load product recommendations asynchronously using AJAX")
    @InDevelopment
    USE_AJAX_FOR_PRODUCT_RECOMMENDATIONS,
    ...
In der Togglz Console werden die FeatureGroups als Tabs dargestellt, in denen die zur Gruppe gehörenden Features automatisch einsortiert werden.
An die Entwicklung mit Feature Toggles mussten wir uns erst einmal gewöhnen: Bei zu wenig Toggles können existierende Funktionen durch Seiteneffekte beeinträchtigt werden. Wenn wir es aber übertreiben, leidet die Lesbarkeit und Testbarkeit des Codes. Vor allem aber ist es wichtig, nicht mehr benötigte Toggles auch wieder aus dem Code zu entfernen.
Die Vorteile überwiegen diese anfänglichen Schwierigkeiten jedoch bei weitem. Neben den neuen Möglichkeiten der Livestellung können wir mit Hilfe der Toggles kontinuierlich integrieren und häufig in Produktion gehen. Die Feedback-Zyklen werden auf diese Weise sehr kurz und wir können direkt auf dem HEAD häufige kleine Commits pushen, die aufwändige Merges vermeiden: Ein neuer Test, eine kleine Änderung, ein Commit.
In der Kombination mit BDD, TDD, Build Pipelines, unterbrechungsfreien Deployments und einer rollbackfähigen Anwendung sind wir mit Feature Toggles auf dem besten Weg in Richtung Continuous Delivery.