Features

Feature Toggle

Also called features flag, it allows to enable/disable features at runtime without deployment. During developments you implement multiple behaviours in your code. At runtime, the executed one is selected by evaluating conditions.

Zoom

Role-based Toggling

Grant features based on roles and groups. Sometimes called Canary Release it allows to tests new features without involving everyone. Different security frameworks can be used like Apache Chiro or Spring Security.

Zoom

Strategy-based Toggling

Implement your custom predicates to evaluate if a feature is enabled. There are some provided out of the box like White lists, Black List, Office Hours, Expression based. A bridge is also provided to rely on real Rules Engine like Drools.

Zoom

AOP-driven Toggling

Avoid nested if statements in your source code, instead decorate your code with annotations. A dynamic proxy will pick up correct implementation at runtime based on feature status. It improves readability and maintainability.

Zoom

Features Monitoring

As a dispatcher for features we are able to record all features usage (hits) and compute metrics based on different KPI like users, sources or hosts. Dashboards allow to search and filter in history.

Zoom

Features Auditing

Save operations performed on both features and properties (CRUD) in order to troubleshoot and provide auditability. With permissions management enabled it's possible to identify each user operation.

Zoom

Web Console

Perform administrations operations through nice i18n web console: features, caching, properties but also audit or settings. Packaged as a servlet it meant to be embedded in your applications.

Zoom

Support many databases

Persist Features, Properties or Audit events in a dozen of databases technologies. From RDBMS, to caches (redis, jsr107, hazelcast...) with main NoSQL players (Cassandra, Mongo, Elastic, Neo4j)

Zoom

Command Line Interface

Not all applications provides web interfaces. In production, you will probably work with SSH. Command line is very convenient for scripting and automation. Because 60% of devops guys are ops here is the CLI.

Zoom

JMX

Treatments like batches cannot use web UI nor CLI. MBeans are provided to operate through JMX. Tools like Java Visual VM to operate. It is also the opportunity to plug supervision tools like Nagios.

Zoom

Configuration Management

DB used for features states are good candidate to externalize configuration and store any properties. Inject properties and edit values at runtime. Integrated with Spring, Archaius and commons-config or Consul.

Zoom

Caching

To resolve feature state you have to hit DB but some storage are slow. Leverage on caching (near or distributed) to speed up applications. Most Cache solutions implemented like EhCache, Hazelcat, Redis or Ignite.

Zoom

Spring Boot Starter

Resolve all dependencies by using provided spring-boot-starter library. Very useful in microservices as handle distributed configuration, service registry (consul) and feature toggle of course.

Zoom

Feature toggle is meant to be implemented in any application layer including UI. Use FF4j with any technologies by integrated the provided rest API. As for web console it's a single servlet to declare.

Zoom

Getting Started


Hello World !

In this section we will create a sample from scratch, skip first steps if you already have a working project. First, create a simple web project with dedicated maven archetype. We could have chosen a basic archetype but we could like to show you the web console.

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp \
	               -Dversion=1.0 -DartifactId=hello-ff4j -DgroupId=org.ff4j.hello


Import the project in your favorite IDE, open your pom.xml file and add the following dependencies. JUNIT should already be provided but with a quite old version.

<dependency>
 <groupId>org.ff4j</groupId>
 <artifactId>ff4j-core</artifactId>
 <version>1.3.0</version>
</dependency>

<dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <scope>test</scope>
 <version>4.11</version>
</dependency>	  


If required, the maven project folders (src/main/java, src/test/java....). In the folder src/test/resources, create the following ff4j.xml file. We should have put you a single feature but let's illustrate immediately some great capabilities.

<?xml version="1.0" encoding="UTF-8" ?>
<features xmlns="http://ff4j.org/schema" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://ff4j.org/schema http://ff4j.org/schema/ff4j-1.2.0.xsd">

  <!-- Will be "ON" if enable is set as true -->
  <feature uid="hello" enable="true"  description="This is my firts feature" />
 
  <!-- Will be "ON" only if :
   (1) Enable is set as true
   (2) A security provider is defined  
   (3) The current logged user has the correct permissions. -->
  <feature uid="mySecuredFeature" enable="true" >
    <security>
	<role name="USER" />
	<role name="ADMIN" />
    </security>
  </feature>
  
  <!-- Will be "ON" only if 
   (1) Enable is set as true
   (2) Strategy predicate is true (here current > releaseDate) -->
  <feature uid="myFutureFeature" enable="true">
    <flipstrategy class="org.ff4j.strategy.ReleaseDateFlipStrategy" >
      <param name="releaseDate" value="2020-07-14-14:00" />
    </flipstrategy>
  </feature>
  
  <!-- Features can be grouped to be toggled in the same time -->
  <feature-group name="sprint_3">
    <feature uid="userStory3_1" enable="false" />
    <feature uid="userStory3_2" enable="false" />
  </feature-group>
 
</features>    


In the src/test/java, create the package org.ff4j.hello with the following JUNIT class. We put several use cases to give you an overview. As I use to say, a piece of code is much clear than a long paragraph.

package org.ff4j.hello;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.ff4j.FF4j;
import org.ff4j.exception.FeatureNotFoundException;
import org.junit.Test;

public class HelloTest {
    
    @Test
    public void my_first_test() {
        // Given: file exist
        assertNotNull(getClass().getClassLoader().getResourceAsStream("ff4j.xml"));
        // When: init
        FF4j ff4j = new FF4j("ff4j.xml");
        // Then
        assertEquals(5, ff4j.getFeatures().size());
        assertTrue(ff4j.exist("hello"));
        assertTrue(ff4j.check("hello"));
        
        // Usage
        if (ff4j.check("hello")) {
            // hello is toggle on
            System.out.println("Hello World !!");
        }
        
        // When: Toggle Off
        ff4j.disable("hello");
        // Then: expected to be off
        assertFalse(ff4j.check("hello"));
    }
    
    @Test
    public void how_does_AutoCreate_Works() {
        // Given: feature not exist
        FF4j ff4j = new FF4j("ff4j.xml");
        assertFalse(ff4j.exist("does_not_exist"));
        
        // When: use it
        try {
           if (ff4j.check("does_not_exist")) fail();
        } catch (FeatureNotFoundException fnfe) {
            // Then: exception
            System.out.println("By default, feature not found throw exception");
        }
        
        // When: using autocreate
        ff4j.setAutocreate(true);
        // Then: no more exceptions
        if (!ff4j.check("does_not_exist")) {
            System.out.println("Auto created and set as false");
        }
    }
    
    @Test
    public void how_groups_works() {
        // Given: 2 features 'off' within existing group
        FF4j ff4j = new FF4j("ff4j.xml");
        assertTrue(ff4j.exist("userStory3_1"));
        assertTrue(ff4j.exist("userStory3_2"));
        assertTrue(ff4j.getStore().readAllGroups().contains("sprint_3"));
        assertEquals("sprint_3", ff4j.getFeature("userStory3_1").getGroup());
        assertEquals("sprint_3", ff4j.getFeature("userStory3_2").getGroup());
        assertFalse(ff4j.check("userStory3_1"));
        assertFalse(ff4j.check("userStory3_2"));
        
        // When: toggle group on
        ff4j.getStore().enableGroup("sprint_3");
        
        // Then: all features on
        assertTrue(ff4j.check("userStory3_1"));
        assertTrue(ff4j.check("userStory3_2"));
    }
}

Web Console

In production you probably won't toggle Off your features programmatically (maybe to implement a circuit breaker), instead you will toggle features through web console.

FF4J provided 2 consoles : one is embedded as a servlet, the other is a full-stack application (operating with remote components through REST API). Here we will demonstrate the usage of the embedded. Add the following dependencies to your pom.xml file.

<dependency>
 <groupId>org.ff4j</groupId>
 <artifactId>ff4j-web</artifactId>
 <version>1.3.0</version>
</dependency>

The, add the following statement within <build> tag. The purpose here is to execute our web application on jetty container using the proper maven plugin. We also add src/test/resource to jetty classpath, where we created the ff4j.xml file.

<plugins>

 <plugin>
  <groupId>org.mortbay.jetty</groupId>
  <artifactId>maven-jetty-plugin</artifactId>
  <version>6.1.26</version>
  <configuration>
   <useTestClasspath>true</useTestClasspath>
   <contextPath>/hello</contextPath>
   <scanIntervalSeconds>5</scanIntervalSeconds>
   <stopPort>8065</stopPort>
   <connectors>
    <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
     <port>8080</port>
     <maxIdleTime>60000</maxIdleTime>
    </connector>
   </connectors>
  </configuration>
 </plugin>
 
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
   <configuration>
    <source>1.7</source>
    <target>1.7</target>
    <showWarnings>true</showWarnings>
   </configuration>
 </plugin>
 
</plugins>

You should be able to start your application with mvn jetty:run and access URL http://localhost:8080/hello/

The administration console needs to find the instance of FF4j to operate features. You have to define a class implementing org.ff4j.web.api.FF4JProvider and reference it when declaring the servlet.


Create a class MyFF4JProvider in src/main/java in the package of your choice with the following source code. Note that the single expected method is getFF4j()

package org.ff4j.hello;

import org.ff4j.FF4j;
import org.ff4j.web.api.FF4JProvider;

public class MyFF4jProvider implements FF4JProvider {

    private final FF4j ff4j;
   
    public MyFF4jProvider() {
        ff4j = new FF4j("ff4j.xml");
    }
  
    /** {@inheritDoc} */
    public FF4j getFF4j() { return ff4j; }
}
Note : If your are working with any dependency injection framework like Spring Framework you will probably inject ff4j as a bean it, but, here, we want to avoid any dependency.

Update your web.xml file and declare the servlet like the following :
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
    version="3.0">

 <display-name>HelloWorld ff4j app</display-name>
	
 <servlet>
  <servlet-name>ff4j-console</servlet-name>
  <servlet-class>org.ff4j.web.embedded.ConsoleServlet</servlet-class>
  <init-param>
   <param-name>ff4jProvider</param-name>
   <param-value>org.ff4j.hello.MyFF4jProvider</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <servlet-mapping>
   <servlet-name>ff4j-console</servlet-name>
   <url-pattern>/ff4j-console</url-pattern>
  </servlet-mapping>
  
  <welcome-file-list>
   <welcome-file>index.jsp </welcome-file>
  </welcome-file-list>

 </web-app>

You should be able access administration console http://localhost:8080/hello/ff4j-console


Every operation performed through this console are performed in-memory. At next restart, the state will be lost. To avoid this behavior you will need to change the FeatureStore and choose among JDBC, MongoDB or Redis.

The code of this sample (and many others) is available on github HERE

What's NEXT ?


This following introduction is the simplest code ever. A real getting started guide is available on github and deals with integration with Spring, AOP and externalization to JDBC. The reference guide contains all informations related to the framework. Finally there are a lot of samples available on github. At end, don't hesitate to raise your question to support google group

Some Use Cases

Avoid Feature Branching

Feature branches lead to conflict during merges. Use trunk-based-developpement to toggle-off unfinished code when develop continously.

Blue/Green Deployments

Avoid clusters nodes inconsistency during deployment and deliver new features desactivated. Toggle "ON"" when all nodes are up-to-date and ready.

Canary Release

Do not create dedicated infrastructure to qualify new features. Open them for subset of beta-testers and directly into production environments.

Dark Launch

Measure performance impacts of new features. Activate them dynamically on a defined ratio of incoming requests and observe system responses.

Graceful degradation

Tune and protect your system of heavy loads: focus on high business value requests and discard others dynamically (clients vs prospects, carts contents..).

Thin client application

Avoid annoying frequent deployments and downloads of mobile applications by providing empty shelf: request expected active features to YOUR servers.

Business Toggle

Toggling is not only technical. Define your own rules and evaluate features based on business requirements like office hours, user profiles...

A/B Testing

Split A and B populations using a business toggle. Measure business impacts not only with CRM but also hitcounts of very same framework.

Circuit Breaker

Implement the circuit breaker pattern with a dedicated strategy and custom rules allowing to toggle off proactively not available features.

Fork me on GitHub