Continuous Delivery mit dem FeatureToggle Pattern

Seite 2: Beispiel mit Togglz

Inhaltsverzeichnis

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 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.

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 nachlesen.