zurück zum Artikel

Continuous Delivery mit dem FeatureToggle Pattern

Niko Köbler

Wer Continuous Delivery umsetzen und betreiben will, hat mit Features zu kĂ€mpfen, deren Entwicklung noch nicht abgeschlossen ist und die noch nicht oder nur fĂŒr eine bestimmte Benutzergruppe in Produktion gehen sollen, aber bereits entwickelt sind. Mit dem FeatureToggle Pattern lassen sich die daraus resultierenden Probleme im Handumdrehen beseitigen.

Continuous Delivery mit dem FeatureToggle Pattern

Wer Continuous Delivery umsetzen und betreiben will, hat mit Features zu kĂ€mpfen, deren Entwicklung noch nicht abgeschlossen ist und die noch nicht oder nur fĂŒr eine bestimmte Benutzergruppe in Produktion gehen sollen, aber bereits entwickelt sind. Dass kein Feature-, sondern nur der Master-Branch existiert, aus dem auch regelmĂ€ĂŸig verteilt wird, könnte zur Herausforderung werden. Mit dem FeatureToggle Pattern ist es einfach, all das im Handumdrehen zu realisieren.

FĂŒr die sich in Produktion befindliche Software soll ein neues Feature entwickelt werden, bestenfalls nur fĂŒr Kunde A. Kunde B dagegen nutzt ein anderes Modul der Software und hat einen nun zu behebenden Bug gefunden. Die Entwicklung des Features fĂŒr Kunde A dauert aber lĂ€nger als bis zum nĂ€chsten geplanten Releasezyklus, und die Korrektur des Fehlers ist als Hotfix an Kunde B auszuliefern. Also erstellen die Entwickler vom Master-Branch einen neuen Feature-Branch [1] und zusĂ€tzlich noch einen fĂŒr den Hotfix. Die Software im Hauptzweig des Repository wird derweil planmĂ€ĂŸig weiterentwickelt. Nach einiger Zeit sind die so entstandenen, gewachsenen und gewucherten Branches, nicht selten deutlich mehr als die angesprochenen zwei, zusammenzufĂŒhren, da irgendwann die Software wieder alle Features und Bugfixes im Master-Branch vereinen soll. Wer möchte jetzt gerne seine Freizeit opfern und die Merges manuell durchfĂŒhren, da parallel an gleichen Codefragmenten in den Branches gearbeitet wurde? Hier gilt es, neben den strukturellen die semantischen Konflikte aufzulösen und die Applikation nach dem ZusammenfĂŒhren weiterhin lauffĂ€hig zu halten. Wohl dem, der auf ausreichende und funktionsfĂ€hige Unit-Tests zurĂŒckgreifen kann.

Einer der GrundsĂ€tze des Continuous Integration besagt, dass es lediglich einen Master-Branch und keine weiteren Feature-Branches geben soll, in den der Code eingecheckt und damit eben diese Merge-Hölle vermieden wird. Weiterhin soll sich fĂŒr Continuous Delivery aus dem Master-Branch jederzeit ein neues lauffĂ€higes Release erzeugen lassen. Wie schaffen es Softwareprojekte nun, die genannten Anforderungen unter einen Hut zu bringen? Jederzeit ein lauffĂ€higes Release erzeugen zu können, keine Feature-Branches zu pflegen und dennoch neue Features zu implementieren. Diesen Code oft (und gegebenenfalls noch nicht fertig entwickelt) ins Repository zu "committen", ohne dass dieser die allgemeine Entwicklung, womöglich an identischen Modulen beziehungsweise Codefragmenten, stört.

Im Grunde ist die Lösung recht einfach mit if-then-else-Statements zu implementieren. Die Bedingung fĂŒr den if-Zweig sollte allerdings nicht hart im Code implementiert sein, sondern sich von außen in irgendeiner Art und Weise beeinflussen oder Ă€ndern lassen. Der bekannte Softwarearchitekt Martin Fowler spricht hier vom FeatureToggle Pattern [2]. In einer Konfigurationsdatei wird eine Menge an Schaltern (Toggle) fĂŒr verschiedene Features definiert. Die Software nutzt dann die Schalter, um das Feature anzuzeigen und zu verarbeiten, oder auch nicht:

if (schalter.ist_an)
zeige_neues_Feature_an
end

Somit hat ein Schalter grundsĂ€tzlich zwei ZustĂ€nde: AN und AUS. Solange sich das Feature noch in der Entwicklung befindet, ist der Schalter in allen Umgebungen deaktiviert: Lediglich der Entwickler, der damit arbeitet (oder die Gruppe derer) schaltet das Feature aktiv und kann somit darĂŒber verfĂŒgen, die Entwicklung vorantreiben und testen. Alle anderen Umgebungen (oder Entwickler) beachten das neue Feature einfach noch nicht. Nach Finalisierung der neuen Funktionen lĂ€sst es sich in allen Umgebungen aktivieren und damit in der Software zur VerfĂŒgung stellen.

Features nicht nur nach ihrem Fertigstellungsgrad, sondern auch benutzer- und/oder umgebungsabhĂ€ngig zu verwenden und anzuzeigen ist ein weiteres Anwendungsbeispiel fĂŒr die Verwendung von Schaltern. Zum Beispiel lĂ€sst sich eine neue Funktion vorerst nur fĂŒr eine Gruppe von Power-Usern anzeigen und spĂ€ter, nach erfolgreicher Erprobung, fĂŒr alle Benutzer freischalten ("Dark Testing"). Oder das neue Feature wird zunĂ€chst nur fĂŒr Anwender auf einem bestimmten Server bereitgestellt. LĂ€uft es dort fehlerfrei, lassen sich die Schalter fĂŒr alle weiteren Server umlegen. Im Fehlerfall wird der Schalter einfach wieder zurĂŒckgelegt, und die Software lĂ€uft wieder ohne die neue Funktion weiter, als wenn sie gar nicht da wĂ€re.

Facebook wendet ein Ă€hnliches Verfahren an, um neue Features erst einer kleinen Nutzergruppe vorzustellen, bevor diese alle Anwender nutzen können. Außerdem verteilt das soziale Netz tĂ€glich mehrmals direkt aus dem Master-Branch, wĂ€hrend viele Features und Bugfixes sich noch in Entwicklung befinden. Somit wird der Code zwar mit ausgeliefert, aber aufgrund der entsprechenden Schalterstellungen nicht verwendet und fĂŒhrt so nicht zu Fehlern. Auch Flickr ist ein prominentes Beispiel dafĂŒr, wie mit Feature Flags und Flippers [3] (wie diese dort genannt werden) mehrmals am Tag aus nur einem Branch in die Produktion hinein verteilt und die Verwendung von Features gesteuert wird.

HĂ€ufig kommen FeatureToggles im Frontend beziehungsweise User Interface vor. Neue OberflĂ€chenteile sollen in der in Produktion befindlichen Software noch nicht sichtbar sein. Um nun hĂ€ssliche if-Statements in den UI-Templates zu vermeiden, ist es oftmals lohnenswert, eigene Tags dafĂŒr zu erstellen, die auf die Schalter reagieren. In einer JSF-Umgebung (JavaServer Faces), in der ein eigener toggle-Tag eingefĂŒhrt wurde, sĂ€he das wie folgt aus. Korrekt implementiert, kommt der Teil, der zwischen den Tags steht, gar nicht bis in den Komponentenbaum und damit ins Frontend, falls das so gewĂŒnscht ist:

<toggle name="meinNeuesFeature">
<p>Hier ist der <a href="newFeature">Link</a> des neuen Features</p>
</toggle>

Wird ein UI-Element einfach durch den Feature-Schalter verborgen, ist meist keine weitere TÀtigkeit notwendig. Was nicht vorhanden ist, kann der Anwender nicht verwenden. Im Backend kann die Implementierung des Feature-Schalters die einfache Unterscheidung zwischen der Nutzung verschiedener Algorithmen und Methodenaufrufe bedeuten. Allerdings hat das wieder unschöne if-Statements zur Folge. Eine elegantere Art ist es, in AbhÀngigkeit des Schalters einfach eine andere Implementierung eines Interfaces in die aufrufende Klasse per Dependency Injection einzuschleusen.

Überwiegend kommen Feature-Schalter zur Laufzeit der Applikation zur Auswertung und zur Anwendung. Vereinzelt werden sie aber auch zum Build-Zeitpunkt verwendet, um einzelne Codebestandteile gar nicht erst in die auszuliefernde Software zu paketieren und neue Features statt altem Code auszuliefern. Das hat allerdings den Nachteil, dass sich nicht auf die alte Version zurĂŒckschalten lĂ€sst, wenn ein neu ausgeliefertes Feature zur Laufzeit einen Fehler verursacht oder sich doch nicht wie erwartet verhĂ€lt. Einer Schalterauswertung zur Laufzeit der Software sollte deshalb also der Vorzug gegenĂŒber dem Build-Zeitpunkt gegeben werden.

Das Testen neuer Features mit Unit-Tests darf natĂŒrlich nicht vergessen werden. Die Verwendung von FeatureToggles und damit die von "neuem" und "altem" Code parallel im gleichen Entwicklungszweig bringt aber keine erhöhte KomplexitĂ€t hinsichtlich der Tests mit sich. Es gibt hier keine kombinatorische Explosion von TestfĂ€llen, die nun zu schreiben und auszufĂŒhren sind. Genauso wie in einer Mehr-Branch-Umgebung ist fĂŒr die neuen Features dieselbe Anzahl an TestfĂ€llen zu implementieren. Ein weiterer Vorteil ist, dass sogar ein Testen beider Schalter-ZustĂ€nde möglich wird, wenn sich die Schalter durch Test(basis)klassen steuern lassen.

Nachdem die Features nicht mehr neu und in den regulĂ€ren Bestand und Funktionsumfang der Software eingeflossen sind, sollte man die Feature-Schalter möglichst wieder ausbauen und aufrĂ€umen. Dadurch bleibt der Code ĂŒbersichtlich und ist nicht mit ungenutzten Fragmenten ĂŒberfrachtet. Kein Entwickler wird sich nach einiger Zeit noch daran erinnern, warum ein altes Fragment noch im Code enthalten ist und es nicht mehr verwendet wird. Auch sorgt das Entfernen der alten Tests fĂŒr eine weitere Übersichtlichkeit und nötigt einen nicht dazu, auf diese in der weiteren Entwicklung RĂŒcksicht zu nehmen, obwohl sie ja gar nicht mehr gelten. ZusĂ€tzlich bewahrt das Ausbauen der Schalter vor dem ungewollten Deaktivieren eines Features, das zu unangenehmen Nebenwirkungen und Fehlverhalten der Software fĂŒhren kann.

Da es sich bei den FeatureToggles ja "nur" um if-then-else Statements handelt, lassen sie sich selbst mit allen benötigten Funktionen im Code implementieren. Auf die Idee sind auch schon andere gekommen, und es gibt einfach zu bedienende Frameworks dafĂŒr, die einem die Programmierarbeit abnehmen. Im Java-Umfeld gibt es mit Togglz [4] eine Bibliothek, die die genannten Funktionen hinsichtlich der Feature-Schalter implementiert und zur VerfĂŒgung stellt. Aber auch das .NET-Umfeld stellt diverse Bibliotheken fĂŒr FeatureToggles bereit, die Google-Suche hilft hier fĂŒr einen Überblick weiter. Im Grunde ist aber die Funktionsweise auf allen Programmierplattformen grĂ¶ĂŸtenteils identisch.

Das eigene Projekt ist schnell fĂŒr die Verwendung von Togglz konfiguriert. Lediglich die gewĂŒnschten und benötigten AbhĂ€ngigkeiten sind hinzuzufĂŒgen. Nutzt man Maven, ist es besonders einfach, da sich alle Ressourcen ĂŒber das zentrale Repository beziehen lassen. Togglz ist in Module aufgeteilt, die je nach Anwendungsfall die benötigten Ressourcen enthalten. In folgenden Listing sind beispielsweise die benötigten AbhĂ€ngigkeiten fĂŒr eine JSF-Webanwendung konfiguriert.

<!-- Togglz fĂŒr Webanwendungen -->
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-servlet</artifactId>
<version>1.1.0.Final</version>
</dependency>

<!-- CDI-Integration (optional) -->
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-cdi</artifactId>
<version>1.1.0.Final</version>
</dependency>

<!-- JSF-Integration (optional) -->
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-jsf</artifactId>
<version>1.1.0.Final</version>
</dependency>

Außer den Servlet-, CDI- und JSF-Integrationen stehen Module fĂŒr das Spring Framework und Authentifizierungen mit Spring Security, Seam Security, Apache DeltaSpike und Apache Shiro bereit. ZusĂ€tzlich gibt es noch ein Modul mit einer Webkonsole, die die Administration ĂŒber eine in die eigene Webanwendung integrierte OberflĂ€che anbietet.

Die weitere Konfiguration geschieht nicht per XML, sondern komplett typsicher in Java. Die Definition der benötigten Features erfolgt in einer Feature-Enum-Klasse, die das Interface Feature implementiert. Hierbei wird jedes Feature als Konstante in der Enum-Klasse reprÀsentiert:

public enum MyFeatures implements Feature {

@EnabledByDefault
@Label("Smart First Feature")
FIRST_FEATURE,

@Label("Cool Second Feature")
SECOND_FEATURE;

@Override
public boolean isActive() {
return FeatureContext.getFeatureManager().isActive(this);
}
}

Das Verwenden einer Enumeration als Feature-Definition hat unter anderem den Vorteil, dass die Funktionen im Code typsicher zu referenzieren sind, sich AutovervollstÀndigung in der IDE der Wahl verwenden lÀsst und Tippfehler vermieden werden. Beim Ausbauen der Features wird somit durch entsprechende Compiler-Fehler schnell ersichtlich, wo der zu entfernende Schalter zum Einsatz kommt.

Im Normalfall ist ein definierter Schalter erst mal inaktiv und schĂŒtzt so automatisch davor, dass der Code innerhalb des Feature-Schalters ausgefĂŒhrt wird. Der Mechanismus kann so manches unerwĂŒnschte Fehlverhalten vermeiden. Soll der entsprechende Code ausgefĂŒhrt werden, ist der Schalter manuell zu aktivieren. Im seltenen Fall, dass er initial aktiviert sein soll, lĂ€sst sich die Annotation @EnabledByDefault verwenden. Die Nutzung weiterer Annotationen zur Anreicherung der Features mit Metadaten ist ebenfalls möglich, Details hierzu findet man auf der Togglz-Website [5].

Nach der Feature-Definition ist Togglz noch zu konfigurieren. Das geschieht ebenfalls ĂŒber die Implementierung eines Interface, in diesem Fall TogglzConfig:

public class MyTogglzConfiguration implements TogglzConfig {

@Override
public Class<? extends Feature> getFeatureClass() {
return MyFeatures.class;
}

@Override
public StateRepository getStateRepository() {
return new FileBasedStateRepository(
new File("/path/to/features.properties")
);
}

@Override
public UserProvider getUserProvider() {
return new NoOpUserProvider();
}
}

In der neuen Klasse sind nun drei Methoden enthalten, jede fĂŒr eine einzelne Aufgabe der Konfiguration. Eine der wichtigsten Methoden ist getFeatureClass(), in der einfach nur anzugeben beziehungsweise zurĂŒckzugeben ist, welche Feature Enum das soeben definierte Feature enthĂ€lt.

In der Methode getStateRepository() konfiguriert man, aus welcher Art von Repository die Feature-ZustĂ€nde gelesen beziehungsweise in welches Repository diese zu persistieren sind. Das einfachste ist sicherlich InMemoryStateRepository, das die SchalterzustĂ€nde lediglich im Hauptspeicher hĂ€lt. Bei Beenden der Anwendung gehen auch die ZustĂ€nde verloren. Möchte man das nicht, sollte man auf das FileBaseStateRepository oder das JDBCStateRepository zurĂŒckgreifen, das die ZustĂ€nde im ersten Fall in einer einfachen Property-Datei und im zweiten Fall per JDBC in einer Datenbank speichert. Eigene StateRepositories lassen sich je nach Bedarf und Belieben erstellen und verwenden.

Die dritte und letzte Methode getUserProvider() der TogglzConfig-Implementierung definiert den zu verwendenden UserProvider, falls Features nur fĂŒr einzelne Benutzer und Gruppen von Benutzern gelten sollen. Hier lassen sich die genannten Module fĂŒr verschiedene Security-Implementierungen verwenden, um sich in deren Authentifizierungsmechanismus einzuhĂ€ngen. Werden andere UserProvider als die mitgelieferten benötigt, lassen sie sich wie die StateRepositories erstellen.

Zu guter Letzt ist der Applikation noch mitzuteilen, dass es Togglz gibt und welche Klasse zur Konfiguration verwendet werden soll. Im Falle einer Enterprise-Java-Anwendung mit CDI (Context and Dependency Injection) ist nichts weiter zu tun. Togglz ist so programmiert, dass es sich im CDI-Container selbst registriert. Im Spring-Kontext ist es Àhnlich einfach, hier sind lediglich, je nach Konfiguration, die TogglzConfig Bean in der Spring-XML zu deklarieren beziehungsweise die Klasse mit einer @Component-Annotation zu versehen.

Jetzt lassen sich Togglz und die definierten Feature-Schalter im Code verwenden. Das geschieht mit einer einfachen if-Abfrage:

if (MyFeatures.FIRST_FEATURE.isActive()) {
// ...
}

Dieses Snippet lĂ€sst sich an jeder Stelle im Quellcode verwenden, egal welcher Art von Ressource beziehungsweise Klasse. Möchte man im UI Elemente verbergen, wie eingangs im Artikel erwĂ€hnt, stellt Togglz fĂŒr JSF-Anwendungen eine einfache Möglichkeit bereit. Hier wird ĂŒber das rendered-Attribut einer Komponente gesteuert, ob sie angezeigt werden soll oder nicht:

<h:panelGroup rendered="#{features['FIRST_FEATURE']}">
<!-- Ein neues Bedienelement -->
</h:panelGroup>

Eine Bean mit Namen feature stellt das JSF-Modul von Togglz automatisch zur VerfĂŒgung.

Togglz bietet noch eine große FĂŒlle weiterer Möglichkeiten, auf die der Artikel nicht ausfĂŒhrlich eingehen kann. Wie oben angesprochen zĂ€hlt eine komplette AdministrationsoberflĂ€che zum optionalen Ausstattungspaket mit. Sie hĂ€ngt sich unterhalb der eigenen URL mit dem Pfad /<context-path>/togglz in das Projekt, je nach Servlet-Version automatisch (Version 3.0) oder manuell ĂŒber die web.xml (bis Version 2.5). Über diese OberflĂ€che lassen sich die einzelnen SchalterzustĂ€nde einfach pflegen, ohne dass ein manuelles und fehleranfĂ€lliges Bearbeiten der Property-Dateien und Datenbank-EintrĂ€ge notwendig wird.

Mit der FeatureProxyFactoryBean kann man in einem Spring-Kontext eine komplett andere Implementierung einer Klasse in AbhÀngigkeit eines Feature-Schalters verwenden:

<bean id="fancyService" 
class="com.example.myapp.services.NewFancyService" />

<bean id="boringService"
class="com.example.myapp.services.BoringService" />

<bean id="myService" class="org.togglz.spring.proxy.FeatureProxyFactoryBean">
<property name="feature" value="FANCY_FEATURE" />
<property name="active" ref="fancyService" />
<property name="inactive" ref="boringService" />
</bean>

Je nach Schalterzustand (aktiv oder inaktiv) stellt die Proxy-Klasse die alte Implementierung oder ihr neues Pendant bereit. Weitere Optionen des Togglz-Frameworks lassen sich auf der Projekt-Website [6] nachlesen.

Continuous Delivery ist bis zum ersten Release gut mit nur einem Branch in der Versionsverwaltung umzusetzen. Sollen danach jedoch weitere Features entwickelt und zeitnah eingecheckt werden, sind Vorkehrungen zu treffen, damit unfertige Features nicht in der Produktionsumgebung sichtbar beziehungsweise bereits fertig entwickelte Funktionen gegebenenfalls erst zu einem bestimmten Zeitpunkt nur fĂŒr gewisse Benutzer oder Serverumgebungen aktiv werden. Das FeatureToggle Pattern kann hier helfen, auch weiterhin Continuous Delivery nur mit einem Code-Branch durchzufĂŒhren und so Schalter fĂŒr die Features zu setzen.

Mit Togglz steht eine schlanke und leistungsfĂ€hige Java-Bibliothek zur VerfĂŒgung, die das Pattern mit weiteren Funktionen implementiert und sich in jede beliebige Java-Anwendung integrieren lĂ€sst. FĂŒr das .NET-Umfeld stehen ebenfalls Implementierungen bereit. Nun sollte es fĂŒr niemanden mehr ein Problem sein, Continuous Delivery in seinem Projekt umzusetzen.

Niko Köbler
ist freiberuflicher Berater und Softwarearchitekt fĂŒr Enterprise Java, Integrationen und Webservices. Er hĂ€lt Workshops und Trainings und fĂŒhrt Architektur-Reviews durch. Zudem ist er Co-Organisator der Java User Group Darmstadt (JUG DA) und regelmĂ€ĂŸig als Sprecher auf Fachkonferenzen anzutreffen.
(ane [7])


URL dieses Artikels:
https://www.heise.de/-1825477

Links in diesem Artikel:
[1] http://martinfowler.com/bliki/FeatureBranch.html
[2] http://martinfowler.com/bliki/FeatureToggle.html
[3] http://code.flickr.net/2009/12/02/flipping-out
[4] http://togglz.org
[5] http://togglz.org
[6] http://togglz.org
[7] mailto:ane@heise.de