
Import ff4j-spring-boot-starter dependency in your microservices to get the web console and rest api working immediately. (To be used for the backend app. Now compliant with Spring Boot 2x: 👉 SAMPLES
1.a - Using DOCKER
If you already have Docker install on your machine you can simply run the demo with the command below, it should start at http://localhost:8080. 🤘
docker run -p 8080:8080 ff4j/ff4j-sample-springboot2x:1.8.5
git clone https://github.com/ff4j/ff4j-samples.git cd spring-boot-2x/ff4j-sample-springboot2x mvn spring-boot:run
As FF4j provides REST-API and a web console we may want to create a web application to get the most of this tutorial. We will use Spring-Boot but the same steps could be adapted for application.
You can create your project in multiple ways using your IDE or a maven archetype. For this tutorial we will use the website https://start.spring.io/ entering some package name, project name and adding the Spring WEB module and then click GENERATE to download the app.
<dependency>
<groupId>org.ff4j</groupId>
<artifactId>ff4j-spring-boot-starter</artifactId>
<version>${ff4j.version}</version>
</dependency>
<dependency>
<groupId>org.ff4j</groupId>
<artifactId>ff4j-web</artifactId>
<scope>test</scope>
<version>${ff4j.version}</version>
</dependency>
<properties> <ff4j.version>...</ff4j.version> </properties>
2.c - Define FF4j Object
With the Spring framework we use the annotations @Configuration and @Bean to define our FF4j object. FeatureStore, PropertyStore and EventRepository are all initialized with in-memory implementations by default.At the FF4j level exist some flags to enable features like audit or feature autocreation (default behaviour is throwing FeatureNotFoundException)
import org.ff4j.FF4j;
import org.ff4j.audit.repository.InMemoryEventRepository;
import org.ff4j.property.store.InMemoryPropertyStore;
import org.ff4j.store.InMemoryFeatureStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FF4jConfig {
@Bean
public FF4j getFF4j() {
FF4j ff4j = new FF4j();
/*
* Implementation of each store. Here this is boiler plate as if nothing
* is specified the inmemory is used. Those are really the one that will
* change depending on your technology.
*/
ff4j.setFeatureStore(new InMemoryFeatureStore());
ff4j.setPropertiesStore(new InMemoryPropertyStore());
ff4j.setEventRepository(new InMemoryEventRepository());
// Enabling audit and monitoring, default value is false
ff4j.audit(true);
// When evaluting not existing features, ff4j will create then but disabled
ff4j.autoCreate(true);
// To define RBAC access, the application must have a logged user
//ff4j.setAuthManager(...);
// To define a cacher layer to relax the DB, multiple implementations
//ff4j.cache([a cache Manager]);
return ff4j;
}
}
2.d - Expose the Web Console
Exposing the web console is only relevant for backend applications (probably centralized) and not in each component using ff4j. Even you secure with user/password, there is not point to open that breach. That been said, let's do it.
@Configuration
// Enable the REST API documentation
@EnableFF4jSwagger
// The class should be on classpath : FF4jDispatcherServlet
@ConditionalOnClass({FF4jDispatcherServlet.class})
// Setup FF4j first, not is required
@AutoConfigureAfter(FF4jConfig.class)
public class FF4jWebConsoleConfiguration extends SpringBootServletInitializer {
@Bean
@ConditionalOnMissingBean
public FF4jDispatcherServlet defineFF4jServlet(FF4j ff4j) {
FF4jDispatcherServlet ff4jConsoleServlet = new FF4jDispatcherServlet();
ff4jConsoleServlet.setFf4j(ff4j);
return ff4jConsoleServlet;
}
@Bean
@SuppressWarnings({"rawtypes","unchecked"})
public ServletRegistrationBean registerFF4jServlet(FF4jDispatcherServlet ff4jDispatcherServlet) {
return new ServletRegistrationBean(ff4jDispatcherServlet, "/ff4j-web-console/*");
}
}
2.e - Define a sample controller
With the previous steps you do have a working sample but no features or properties to play with. In the demo we added a rest controller mapped to root path /. In the one we define 3 features (showWebConsoleURL, showRestApiURL, showUserName) and 1 property (username).
@RestController
@RequestMapping("/")
public class HomeController {
private static final Logger LOGGER = LoggerFactory.getLogger(HomeController.class);
private static final String FEATURE_SHOW_WEBCONSOLE = "showWebConsoleURL";
private static final String FEATURE_SHOW_REST_API = "showRestApiURL";
private static final String FEATURE_SHOW_USERNAME = "showUserName";
private static final String PROPERTY_USERNAME = "username";
@Autowired
public FF4j ff4j;
@PostConstruct
public void populateDummyFeatureForMySample() {
if (!ff4j.exist(FEATURE_SHOW_WEBCONSOLE)) {
ff4j.createFeature(new Feature(FEATURE_SHOW_WEBCONSOLE, true));
}
if (!ff4j.exist(FEATURE_SHOW_REST_API)) {
ff4j.createFeature(new Feature(FEATURE_SHOW_REST_API, true));
}
if (!ff4j.exist(FEATURE_SHOW_USERNAME)) {
ff4j.createFeature(new Feature(FEATURE_SHOW_USERNAME, true));
}
if (!ff4j.getPropertiesStore().existProperty(PROPERTY_USERNAME)) {
ff4j.createProperty(new PropertyString(PROPERTY_USERNAME, "cedrick"));
}
LOGGER.info(" + Features and properties have been created for the sample.");
}
@RequestMapping(value = "/", method = RequestMethod.GET, produces = "text/html")
public String get() {
LOGGER.info(" + Rendering home page...");
StringBuilder htmlPage = new StringBuilder("<html><body><ul>");
htmlPage.append("<h2>This is home page.</h2>");
htmlPage.append("<p>Displaying the links below is driven by features in FF4j."
+ "If you disable the feature "
+ "the link will disapear (but the servlet will still response, "
+ "this is just a trick to illustrate "
+ "response in the UI)</p>");
htmlPage.append("<p><b>List of resources for you :"
+ "</b><br/><ul>");
if (ff4j.check(FEATURE_SHOW_WEBCONSOLE)) {
htmlPage.append("<li> To access the To access the <b>REST API</b> "
+ "please go to <a href=\"./api/ff4j\" target=\"_blank\">ff4j-rest-api </a>");
}
if (ff4j.check(FEATURE_SHOW_USERNAME)) {
if (ff4j.getPropertiesStore().existProperty(PROPERTY_USERNAME)) {
htmlPage.append("<li> " + PROPERTY_USERNAME + " value is ");
htmlPage.append("<span style=\"color:blue;font-weight:bold\">");
htmlPage.append(ff4j.getPropertiesStore().readProperty(PROPERTY_USERNAME).asString());
htmlPage.append("</span>");
} else {
htmlPage.append("<li> " + PROPERTY_USERNAME + " does not exist.");
}
}
htmlPage.append("</ul></body></html>");
return htmlPage.toString();
}
Feature branches lead to conflict during merges. Use trunk-based-developpement to toggle-off unfinished code when develop continously.
Avoid clusters nodes inconsistency during deployment and deliver new features desactivated. Toggle "ON"" when all nodes are up-to-date and ready.
Do not create dedicated infrastructure to qualify new features. Open them for subset of beta-testers and directly into production environments.
Measure performance impacts of new features. Activate them dynamically on a defined ratio of incoming requests and observe system responses.
Tune and protect your system of heavy loads: focus on high business value requests and discard others dynamically (clients vs prospects, carts contents..).
Avoid annoying frequent deployments and downloads of mobile applications by providing empty shelf: request expected active features to YOUR servers.
Toggling is not only technical. Define your own rules and evaluate features based on business requirements like office hours, user profiles...
Split A and B populations using a business toggle. Measure business impacts not only with CRM but also hitcounts of very same framework.
Implement the circuit breaker pattern with a dedicated strategy and custom rules allowing to toggle off proactively not available features.