Handling Feature Flags in a Java EE Application using Togglz
June 26th, 2013 by Micha KopsFeature flags are a common technique, often combined with continuous deployment and delivery and they allow us to rollback a specific feature, to create A/B tests or to rollout a specific feature for a specific test group, a specific amount of users or dedicated systems.
In the following short examples I’d like you to demonstrate how easy it is to implement feature flags with the Togglz framework with a few steps in a Java EE environment.
Contents
Project Dependencies
Togglz has a rich set of libraries for different integration points slf4j, cdi, spring, seam, servlet, shiro and others.
The main dependencies from my pom.xml are togglz-core, togglz-console, togglz-jsf and togglz-cdi are used in the following tutorial to add the administration console, facelet and CDI integration:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <togglz.version>2.0.0.RC1</togglz.version> </properties> <dependencies> <dependency> <groupId>org.togglz</groupId> <artifactId>togglz-core</artifactId> <version>${togglz.version}</version> </dependency> <dependency> <groupId>org.togglz</groupId> <artifactId>togglz-console</artifactId> <version>${togglz.version}</version> </dependency> <dependency> <groupId>org.togglz</groupId> <artifactId>togglz-jsf</artifactId> <version>${togglz.version}</version> </dependency> <dependency> <groupId>org.togglz</groupId> <artifactId>togglz-cdi</artifactId> <version>${togglz.version}</version> </dependency> <dependency> <groupId>org.glassfish.main.extras</groupId> <artifactId>glassfish-embedded-all</artifactId> <version>3.1.2.2</version> <scope>provided</scope> </dependency> </dependencies>
My full pom has some additional settings to integrate the embedded GlassFish Plugin, set the source/target level to Java 7 and add directives for the Maven War Plugin – please feel free to have a look at the source, fork, clone .. see below in the section “Tutorial Sources“..
Creating Feature Flags
A feature flag is implemented as an enum that implements org.togglz.core.Feature.
We may add a description for each feature using @Label, and adding a method isActive allows us to use this enum everywhere in our code.
This makes it easy to remove a feature switch from our code later because we only need to search for references to the specific enum.
package com.hascode.tutorial.fflag.feature; import org.togglz.core.Feature; import org.togglz.core.annotation.Label; import org.togglz.core.context.FeatureContext; public enum UserFeatures implements Feature { @Label("Displays basic information for a given user") DISPLAY_SIMPLE_USER_PROFILE, @Label("Displays extended information for a given user") DISPLAY_EXTENDED_USER_PROFILE; public boolean isActive() { return FeatureContext.getFeatureManager().isActive(this); } }
Feature Flag Configuration
Since we’ve added CDI integration, we’re able to instanciate our feature-flag configuration by creating an @ApplicationScoped class that implements org.togglz.core.manager.TogglzConfig.
Togglz offers different implementations for StateRepository – you may store your state in a database using JDBCStateRepository in a file using FileStateRepository – or for the purpose of a quick tutorial, using an in-memory repository, InMemoryStateRepository.
We’re using the ServletUserProvider here, initialized with the feature-admin role needed to access the administration area for the feature configuration.
package com.hascode.tutorial.fflag.config; import javax.enterprise.context.ApplicationScoped; import org.togglz.core.Feature; import org.togglz.core.manager.TogglzConfig; import org.togglz.core.repository.StateRepository; import org.togglz.core.repository.mem.InMemoryStateRepository; import org.togglz.core.user.UserProvider; import org.togglz.servlet.user.ServletUserProvider; import com.hascode.tutorial.fflag.feature.UserFeatures; @ApplicationScoped public class FeatureFlagConfiguration implements TogglzConfig { private static final String featureAdminRole = "feature-admin"; @Override public Class<? extends Feature> getFeatureClass() { return UserFeatures.class; } @Override public StateRepository getStateRepository() { return new InMemoryStateRepository(); } @Override public UserProvider getUserProvider() { return new ServletUserProvider(featureAdminRole); } }
If you want to take a quick look without adding authentication and roles to your application, just the following workaround, but please don’t forget to remove it for a production system!
@ApplicationScoped public class FeatureFlagConfiguration implements TogglzConfig { @Override public UserProvider getUserProvider() { return new UserProvider() { @Override public FeatureUser getCurrentUser() { return new SimpleFeatureUser("admin", true); } }; } }
To make CDI work, we need to add an empty file named beans.xml in src/main/webapp/WEB-INF!
Managing Feature Flags / Admin Console
When starting the application – in my project you simply need to run mvn to boot an embedded GlassFish with the application deployed – you’re able to load the administration console at the URL /contextPath/togglz/index.
Activation/Deactivation
In my case that means that the console is accessible at http://localhost:8080/togglz-feature-flag-tutorial/togglz/index
The console displays available features and their current state as seen in the following screenshot:
Activation Strategies
The following dialog allows you to activate a feature and chose a specific activation strategy:
- none: The feature is simply activated
- Client IP: Activated for clients with a specific IP
- Gradual rollout: You may select the percentage of users e.g. selecting 25% activates the feature for every fourth user
- Release date: You may specify the release date in the format yyyy-MM-dd
- ScriptEngine: You may execute a script language e.g. ECMAScript – I must admit that I’ve never tried this one..
- Server IP: Restrict the feature to a specific server IP .. e.g. activate for 2 of 6 servers by adding their IP address
- Username: Restrict to specific users .. you could – for example activate a specific feature for the users test-user and test-admin or something similar
This is what the administration console looks like with all features enabled .. greeen ….
Java Server Faces / Facelet Integration
Now we want to take a look at Togglz’ JSF Integration.. first of all we’re adding configuration for the JSF Servlet and add *.xhtml as the file pattern to activate it.
This is our web.xml in src/main/webapp/WEB-INF:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> </web-app>
And this is a simple facelet, index.xhtml added to the directory src/main/webapp.
As you can see, we’re able to test if a feature is enabled using #{features['FEATURE_NAME']} that returns a boolean.
In the following example, I’m displaying/hiding a panelGroup depending an the feature state:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head> <title>hasCode.com - Togglz Feature Flag Tutorial</title> </h:head> <h:body> <h1>Features enabled</h1> <h:panelGroup rendered="#{features['DISPLAY_SIMPLE_USER_PROFILE']}"> Display simple user profile <hr/> </h:panelGroup> <h:panelGroup rendered="#{features['DISPLAY_EXTENDED_USER_PROFILE']}"> Display extended user profile </h:panelGroup> </h:body> </html>
When you now start the application using mvn, the facelet is accessible at http://localhost:8080/togglz-feature-flag-tutorial/index.xhtml.
Programmatic Feature Flag Check
This is the way that we’ll be testing a flag most times – by simply using the enum and the isActive method.
In the following example, I’m adding a feature flag check to a servlet that prints a string depending on the features enabled:
package com.hascode.tutorial.fflag.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.hascode.tutorial.fflag.feature.UserFeatures; @WebServlet(urlPatterns = "/fftest") public class FeatureFlagTestServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { StringBuilder sb = new StringBuilder(); sb.append("Available features are:\n"); if (UserFeatures.DISPLAY_SIMPLE_USER_PROFILE.isActive()) { sb.append("\t- Display simple user profiles is enabled\n"); } if (UserFeatures.DISPLAY_EXTENDED_USER_PROFILE.isActive()) { sb.append("\t- Display extended user profiles is enabled\n"); } resp.getWriter().append(sb.toString()); } }
Finally the servlet output, accessible at http://localhost:8080/togglz-feature-flag-tutorial/fftest should output something like this if both features are enabled:
Tutorial Sources
Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/jee-feature-flags.git
Resources
Tags: cdi, feature flag, java ee, java server faces, jsf, servlet, togglz
January 1st, 2016 at 4:03 pm
Hi there. I downloaded your git repo and when I run I see an infinite loop:
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:840) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:622) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:560) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:488) [jbossweb-7.0.13.Final.jar:]
at com.sun.faces.context.ExternalContextImpl.dispatch(ExternalContextImpl.java:546) [jsf-impl-2.1.7-jbossorg-2.jar:]
at com.sun.faces.application.view.JspViewHandlingStrategy.executePageToBuildView(JspViewHandlingStrategy.java:364) [jsf-impl-2.1.7-jbossorg-2.jar:]
at com.sun.faces.application.view.JspViewHandlingStrategy.buildView(JspViewHandlingStrategy.java:154) [jsf-impl-2.1.7-jbossorg-2.jar:]
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:100) [jsf-impl-2.1.7-jbossorg-2.jar:]
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101) [jsf-impl-2.1.7-jbossorg-2.jar:]
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139) [jsf-impl-2.1.7-jbossorg-2.jar:]
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:594) [jboss-jsf-api_2.1_spec-2.0.1.Final.jar:2.0.1.Final]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:329) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:248) [jbossweb-7.0.13.Final.jar:]
Can you help me to debug it? Thanks. P/D: I use JBoss AS 7.1.1