Handling Feature Flags in a Java EE Application using Togglz

June 26th, 2013 by

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

 

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:

Togglz Configuration Panel

Togglz Configuration Panel

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
Activating a feature

Activating a feature

This is what the administration console looks like with all features enabled .. greeen ….

All features activated

All features activated

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.

Togglz JSF/Facelet Integration

Togglz JSF/Facelet Integration

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:

Feature flag check in a servlet

Feature flag check in a servlet

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: , , , , , ,

One Response to “Handling Feature Flags in a Java EE Application using Togglz”

  1. Gonzalo Says:

    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

Leave a Reply

Please note, that no personal information like your IP address is stored and you're not required to enter you real name.

Comments must be approved before they are published, so please be patient after having posted a comment - it might take a short while.

Please leave these two fields as-is:
Search
Categories