Skip to content

Getting Started

Neoh59 edited this page Nov 14, 2017 · 24 revisions

Welcome to this getting started ! We want you to be aware of what can do FF4J as fast as possible. No chitchat promise.

Live Demo

You can assess a live DEMO here. Web Console and REST API can be reached through the buttons on top right of the web page. Element of the page are features to be enabled or disabled.

Out of the box working samples

FF4J provides working samples with different configurations in the Github Repository Samples

Link Description
Simple JDBC Starter project for Jdbc store with REST API and servlet
Spring Boot Starter Easy integration with SpringBoot using a starter
Spring Boot Same as before but manually define the servlet and the REST API
Spring Boot JDBC The starter with a MySQL SpringJdbc store
Web Define the Servlet manually
Web API Define the REST API manually with Jersey2x
5 min tutorial To be updated (still 1.3)

5 minutes tutorial

to be updated to 1.6

We will create a working project from scratch, download the result [here].

  • Create a simple web project with maven web archetype. (skip this first step if you already have a java project). Web project is not required but it is a way to illustrate the web console.
mvn archetype:generate -Dversion=1.0 \
                       -DarchetypeArtifactId=maven-archetype-webapp \
	                   -DartifactId=hello-ff4j -DgroupId=org.ff4j.hello
  • Import the project in your favorite IDE, open the pom.xml file and add the following dependencies.
<dependency>
 <groupId>org.ff4j</groupId>
 <artifactId>ff4j-core</artifactId>
 <version>1.5</version>
</dependency>
 
<dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <scope>test</scope>
 <version>4.11</version>
</dependency>	  
  • Create the following ff4j.xml file.
<?xml version="1.0" encoding="UTF-8" ?>
<ff4j xmlns="http://www.ff4j.org/schema/ff4j" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.ff4j.org/schema/ff4j http://ff4j.org/schema/ff4j-1.4.0.xsd">
	
 <features>
  	
  <!-- Will be "ON" if enable is set as true -->
  <feature uid="hello" enable="true"  description="This is my first 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.time.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>    
</ff4j>
  • In the folder src/test/java, create the package org.ff4j.hello with the following Junit class. As a piece of code is clearer than a long paragraph, check the tests and understand the behaviour.
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 features are not toggled programmatically (except to implement the circuit breaker pattern), instead, you will use the web console.

  • Add the ff4j-web dependencies in your pom.xml file
<dependency>
 <groupId>org.ff4j</groupId>
 <artifactId>ff4j-web</artifactId>
 <version>1.5</version>
</dependency>
  • Again in pom.xml file, add the following within <build> tag to define and setup the maven jetty plugin. It adds the src/test/resource to jetty classpath in which 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>
  • Start the application with mvn jetty:run and access URL http://localhost:8080/hello/, now stop the jetty server

  • The console needs to find an instance of FF4j class to administrate features. We will implement the FF4JProvider interface in a dedicated class and reference it as init param of the servlet. Create the following class src/main/java.

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 you are working with any dependency injection framework like Spring Framework you will probably inject ff4j as a bean, but, here, we want to avoid any dependency.

  • Now 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>

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

You can’t perform that action at this time.