Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Cedrick Lunven edited this page Dec 19, 2022 · 20 revisions

InMemory

The implementation of this FeatureStore parses an XML document (file or stream) at startup and store the features as a ConcurrentHashMap in memory. It's the default implementation of FF4J. It does not required any external librairies and is available in the module ff4j-core. The XML schema of the file (or XSD) can be find there. http://ff4j.org/schema/ff4j.xsd

You can declared the schema in the XML file with the following schema :

<?xml version="1.0" encoding="UTF-8" ?>
<features 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.xsd">
  <!-- Here your declarations -- >
</features>

There are a lot of samples in this reference guide. Yet, let's summarize everything in a quite complete one

<?xml version="1.0" encoding="UTF-8" ?>
<features 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.xsd">

 <!-- Simplest -->
 <feature uid="A" enable="true" />

 <!-- Add description -->
 <feature uid="B" description="Expect to say good bye..." enable="false" />

 <!-- Security stuff -->
 <feature uid="C" enable="false">
  <security>
   <role name="USER"  />
   <role name="ADMIN" />
  </security>
 </feature>

 <!-- Some strategies and a group -->
 <feature-group name="strategies">
  
  <feature uid="S1" enable="true">
   <flipstrategy class="org.ff4j.strategy.el.ExpressionFlipStrategy"> 
    <param name="expression" value="A | B" />
   </flipstrategy>
  </feature>
  
  <feature uid="S2" enable="true">
   <flipstrategy class="org.ff4j.strategy.ReleaseDateFlipStrategy">
    <param name="releaseDate" value="2013-07-14-14:00" />
   </flipstrategy>
  </feature>

  <feature uid="S3" description="null" enable="true">
    <flipstrategy class="org.ff4j.strategy.PonderationStrategy">
     <param name="weight" value="0.5" />
    </flipstrategy>
  </feature>
  
  <feature uid="S4" description="z" enable="true">
    <flipstrategy class="org.ff4j.strategy.ClientFilterStrategy"> 
     <param name="grantedClients" value="c1,c2" />
    </flipstrategy>
  </feature>

  <feature uid="S5" description="null" enable="true"> 
   <flipstrategy class="org.ff4j.strategy.ServerFilterStrategy">
    <param name="grantedServers" value="s1,s2" />
   </flipstrategy>
  </feature>

 </feature-group>
</features>

The XML format can be generated whatever the FeatureStore configured. It should be use to export configuration from an environment and insert into another. As detailed further, this operation is available in the web console. If you would like to do it in your own code, here is the way to do it.

public class ImportExportXmlTest {

 @Test
 public void testImport() throws FileNotFoundException {
  // Given
  FF4j ff4j = new FF4j();
  
  // When
  FileInputStream fis = new FileInputStream(new File("src/test/resources/ff4j.xml")); 
  Map<String, Feature> mapsOfFeat = new FeatureXmlParser().parseConfigurationFile(fis); 
  for (Entry<String, Feature> feature : mapsOfFeat.entrySet()) {
    if (ff4j.exist(feature.getKey())) { 
      ff4j.getStore().update(feature.getValue());
    } else { 
      ff4j.getStore().create(feature.getValue());
    }
  }

  // Then
  assertEquals(2, ff4j.getFeatures().size());
}

 @Test
 public void testExport() throws IOException { 
  FF4j ff4j = new FF4j("ff4j.xml");
  InputStream in = ff4j.exportFeatures();
  // Write into console
  byte[] bbuf = new byte[4096]; int length = 0;
  while ((in != null) && (length != -1)) { 
   length = in.read(bbuf);
  }
  System.out.print(new String(bbuf));
 }
}

Jdbc

JdbcFeatureStore, JdbcPropertyStore, JdbcEventRepository of the module ff4j-core store informations in any RDBMS database through JDBC communications. The connectivity to DB rely on a simple Jdbc DataSource. To use this implementation, only your target driver is required, there is no extra dependency.

Data Model

-- Main Table to store Features
CREATE TABLE FF4J_FEATURES (
  "FEAT_UID"     	VARCHAR(100),
  "ENABLE"  		INTEGER NOT NULL,
  "DESCRIPTION" 	VARCHAR(1000),
  "STRATEGY"		VARCHAR(1000),
  "EXPRESSION"	    VARCHAR(255),
  "GROUPNAME"		VARCHAR(100),
  PRIMARY KEY("FEAT_UID")
);

-- Roles to store ACL, FK to main table
CREATE TABLE FF4J_ROLES (
  "FEAT_UID"     VARCHAR(100) REFERENCES FF4J_FEATURES("FEAT_UID"),
  "ROLE_NAME"    VARCHAR(100),
  PRIMARY KEY("FEAT_UID", "ROLE_NAME")
);

-- Feature Internal Custom Properties
CREATE TABLE FF4J_CUSTOM_PROPERTIES (
  "PROPERTY_ID"  VARCHAR(100) NOT NULL,
  "CLAZZ" 		 VARCHAR(255) NOT NULL,
  "CURRENTVALUE" VARCHAR(255),
  "FIXEDVALUES"	 VARCHAR(1000),
  "DESCRIPTION"	 VARCHAR(1000),
  "FEAT_UID"     VARCHAR(100) REFERENCES FF4J_FEATURES("FEAT_UID"),
  PRIMARY KEY("PROPERTY_ID", "FEAT_UID")
);

-- @PropertyStore (edit general properties)
CREATE TABLE FF4J_PROPERTIES (
  "PROPERTY_ID"  VARCHAR(100) NOT NULL,
  "CLAZZ" 		 VARCHAR(255) NOT NULL,
  "CURRENTVALUE" VARCHAR(255),
  "FIXEDVALUES"	 VARCHAR(1000),
  "DESCRIPTION"	 VARCHAR(1000),
  PRIMARY KEY("PROPERTY_ID")
);

-- @see JdbcEventRepository (audit event)
CREATE TABLE FF4J_AUDIT (
  "EVT_UUID" 	 VARCHAR(40)  NOT NULL,
  "EVT_TIME" 	 TIMESTAMP 	  NOT NULL,
  "EVT_TYPE" 	 VARCHAR(30)  NOT NULL,
  "EVT_NAME" 	 VARCHAR(30)  NOT NULL,
  "EVT_ACTION" 	 VARCHAR(30)  NOT NULL,
  "EVT_HOSTNAME" VARCHAR(100)  NOT NULL,
  "EVT_SOURCE" 	 VARCHAR(30)  NOT NULL,
  "EVT_DURATION" INTEGER,
  "EVT_USER" 	 VARCHAR(30),
  "EVT_VALUE" 	 VARCHAR(100),
  "EVT_KEYS" 	 VARCHAR(255),
  PRIMARY KEY("EVT_UUID", "EVT_TIME")
);

The sql file can be found HERE and will be up-to-date as used in all unit tests.

Sample Code

To initialize ff4j with those stores please use the following and adapt with your needs.

// Initialization of your DataSource
DataSource ds = ...

FF4j ff4j = new FF4j();
ff4j.setFeatureStore(new JdbcFeatureStore(ds));
ff4j.setPropertiesStore(new JdbcPropertyStore(ds));
ff4j.setEventRepository(new JdbcEventRepository(ds));

Consul IO

Consul is a service discovery software. It allows to give the endpoints list for a service a runtime. It also provide a store. This is it, perfect location for features states and properties.

// Given a consul connection
Consul c = Consul.builder().withUrl("http://localhost:8800").build();
ConsulConnection connection = new ConsulConnection(c);
// Get the feature Store
FeatureStoreConsul consultFeatureStore = new FeatureStoreConsul(connection);
PropertyStoreConsul consultPropertyStore = new PropertyStoreConsul(connection);

Spring JDBC

Rational

Spring, the well known framework, provides a lot of element to ease (and secure) the usage of JDBC : Transactions, Connection closes. FF4j provides an implementation of stores to work with spring-jdbc module.

Sample Code

// Initialization of your DataSource
DataSource ds = ...

// Init the framework full in memory
FF4j ff4j = new FF4j();

// Feature States in a RDBMS
FeatureStoreSpringJdbc featureStore= new FeatureStoreSpringJdbc();
featureStore.setDataSource(ds);
ff4j.setFeatureStore(featureStore);

// Properties in RDBMS
PropertyStoreSpringJdbc propertyStore= new PropertyStoreSpringJdbc();
jdbcStore.setDataSource(ds);
ff4j.setPropertiesStore(propertyStore);

// Audit in RDBMS
// So far the implementation with SpringJDBC is not there, leverage on default JDBC
EventRepository auditStore = new JdbcEventRepository(ds);
ff4j.setEventRepository(eventRepository);

ff4j.audit(true);

JCache

JSR107...

Redis

FeatureStoreRedis, PropertyStoreRedis, EventRepositoryRedis and FeatureCacheProviderRedis of the module ff4j-store-redis store informations in the (awesome) noSql Key-value database REDIS. The connectivity to Redis rely on the Jedis library. You can implement a direct connection (not threadsafe) or use pooled connections. It's possible to connect to a Redis 'Sentinel' to handle clustering.

Keys are prefixed with FF4J_ to avoid any collision with existing data. There is no TTL for stores, keys remain forever.

  • Define the RedisConnection depending of the topology of your redis server :
//  Will use default value for REDIS (localhost/6379 @{@link `redis.clients.jedis.Protocol`})
new RedisConnection();
        
// enforce host and port
new RedisConnection("localhost", 6379);
        
// if password is enabled in redis
new RedisConnection("localhost", 6379, "requiredPassword");
        
// Defined your own pool with all capabilities of {@link redis.clients.jedis.JedisPool}
new RedisConnection(new JedisPool("localhost", 6379));
        
// Use the sentinel through specialized JedisPool {@link redis.clients.jedis.JedisSentinelPool}
new RedisConnection(new JedisSentinelPool("localhost", Util.set("master", "slave1")));
  • Initialize the store you will need (features, properties or event) :
// Initialization of FF4J
FF4j ff4j = new FF4j();
ff4j.setFeatureStore(new FeatureStoreRedis(redisConnection));
ff4j.setPropertiesStore(new PropertyStoreRedis(redisConnection));
ff4j.setEventRepository(new EventRepositoryRedis(redisConnection));

// Empty Store
ff4j.getFeatureStore().clear();
ff4j.getPropertiesStore().clear();

// Work a bit with CRUD
Feature f1 = new Feature("f1", true, "My firts feature", "Group1");
ff4j.getFeatureStore().create(f1);
        
PropertyString p1 = new PropertyString("p1", "v1");
ff4j.getPropertiesStore().createProperty(p1);
        
ff4j.check("f1");
  • To illustrate the structure in Redis, here are some redis-cli commands:
m127.0.0.1:6379> keys FF4J*
 1) "FF4J_PROPERTY_p1"
 2) "FF4J_FEATURE_f1"
 3) "FF4J_EVENT_-1467803617889-9ac83532-2480-4609-9a76-5793cdb21e1a"
 4) "FF4J_EVENT_-1467803617833-a856c1b9-cedc-4164-815b-55bf9dc3adef"
 5) "FF4J_EVENT_-1467803654139-03a9cb1e-b5b3-4d9f-91dd-0ca5b2cc5a01"
 6) "FF4J_EVENT_-1467803654144-cc591275-a98b-4efe-ab39-4bfd0839aa1e"
 7) "FF4J_EVENT_-1467803617829-4487077f-680a-44ba-b0e2-43e6685dc044"
 8) "FF4J_EVENT_-1467803654599-83af1ce2-05f6-4264-8b53-d1541e8e036c"

127.0.0.1:6379> get FF4J_FEATURE_f1
"{\"uid\":\"f1\",\"enable\":true,\"description\":\"My firts feature\",\"group\":\"Group1\",\"permissions\":[],\"flippingStrategy\":null,\"customProperties\":{}}"

127.0.0.1:6379> get FF4J_PROPERTY_p1
"{\"name\":\"p1\",\"description\":null,\"type\":\"org.ff4j.property.PropertyString\",\"value\":\"v1\",\"fixedValues\":null}"

127.0.0.1:6379> get FF4J_EVENT_-1467803654599-83af1ce2-05f6-4264-8b53-d1541e8e036c
"{\"id\": \"83af1ce2-05f6-4264-8b53-d1541e8e036c\", \"timestamp\":1467803654599, \"hostName\": \"mbp\", \"source\": \"JAVA_API\", \"name\": \"f1\", \"type\": \"feature\", \"action\": \"checkOn\", \"duration\":0}"

127.0.0.1:6379>

Because Redis is very fast it can also be used as cache for other stores slower. For instance, if you need to request features through http (client http store) it would be useful to use a cache to limit overheader. This is a simple way of doing it :

// Definition of slow stores (http, db, ...)
FeatureStore  fStore;
PropertyStore pStore;
        
FeatureCacheProviderRedis fcr = new FeatureCacheProviderRedis(redisConnection);
fcr.setTimeToLive(3600 * 2); // 2 hours
FF4jCacheProxy redisCacheProxy = new FF4jCacheProxy(fStore, pStore, fcr);
ff4j.setFeatureStore(redisCacheProxy);
ff4j.setPropertiesStore(redisCacheProxy);

@since 1.6.4 Due to the slowness of KEYS clause, a new dictionary key FF4J_FEATURE_MAP is provided to speed up things. To migrate to 1.6.4 you have to execute the following:

Migration for Redis key scan from 1.3 to 1.6.4

  1. First place the lua script in the same directory with the name: redis-migration.lua
  2. Place the redis-migration.sh shell script in the same directory.
  3. Execute the script with the parameters {host} {port} for the master of Redis instance/Sentinel. NOTE: This operation is idempotent, since underlying structure is SET and the script ignores self map from the list

redis-migration.lua

local keys = {};
local featureNames = "";
local feature = "FF4J_FEATURE_"
local property = "FF4J_PROPERTY_"
local cursor = "0"
local done = false;

local searchPattern = feature

if ARGV[1] == property then
	searchPattern = property
end

repeat
    local result = redis.call("SCAN", 0, "match", searchPattern .. "*", "count", 200000)
    cursor = result[1];
    keys = result[2];
    for i, key in ipairs(keys) do
    	local featureName = key:gsub(searchPattern, "");
		-- if this script is double run, ignore MAP!
		if featureName ~= "MAP" then
	        if i > 1 then 
				featureNames = featureNames .. ",";
			end
	 		featureNames = featureNames .. featureName;
	    end
	end

    if cursor == "0" then
        done = true;
    end
until done

return featureNames

AWS DynamoDB

FeatureStoreDynamoDB and PropertyStoreDynamoDB in the module ff4j-store-aws-dynamodb will store your features and properties in Amazon DynamoDB.

Table creation

The connectivity to DynamoDB relies on AWS SDK, more especially on com.amazonaws:aws-java-sdk-dynamodb maven dependency.

In order to use this store, you will need DynamoDB tables (one for features, one for properties). Two options are possible:

  • Either you let FF4J create it for you. To do so, you must create a ff4j-dynamodb.properties file with the following content and put it in your classpath:
ff4j.store.dynamodb.feature.table.name=<your feature table name>
ff4j.store.dynamodb.feature.table.billing=[PROVISIONED|PAY_PER_REQUEST]
ff4j.store.dynamodb.feature.table.billing.rcu=<int value>
ff4j.store.dynamodb.feature.table.billing.wcu=<int value>

ff4j.store.dynamodb.property.table.name=<your property table name>
ff4j.store.dynamodb.property.table.billing=[PROVISIONED|PAY_PER_REQUEST]
ff4j.store.dynamodb.property.table.billing.rcu=<int value>
ff4j.store.dynamodb.property.table.billing.wcu=<int value>

(rcu and wcu are only needed when billing=PROVISIONED: See documentation for more information related to DynamoDB billing mode)

  • Or you create it on your own. You must keep the same attribute names (see DynamoDBConstants for the list of attributes), but you can change the table name, index name, billing mode and throughput.

Sample for feature:

aws dynamodb create-table --cli-input-json file://create-feature-dynamodb-table.json
{
    "TableName": "ff4jfeatures",
    "AttributeDefinitions": [
        {
            "AttributeName": "featureUid",
            "AttributeType": "S"
        },
        {
            "AttributeName": "groupName",
            "AttributeType": "S"
        }
    ],
    "KeySchema": [
        {
            "AttributeName": "featureUid",
            "KeyType": "HASH"
        }
    ],
    "GlobalSecondaryIndexes": [
        {
            "IndexName": "ff4jfeaturesgroup",
            "KeySchema": [
                {
                    "AttributeName": "groupName",
                    "KeyType": "HASH"
                }
            ],
            "Projection": {
                "ProjectionType": "ALL"
            },
            "ProvisionedThroughput": {
                "ReadCapacityUnits": 42,
                "WriteCapacityUnits": 42
            }
        }
    ],
    "BillingMode": "PROVISIONED",
    "ProvisionedThroughput": {
        "ReadCapacityUnits": 42,
        "WriteCapacityUnits": 42
    }
}

FF4J DynamoDB setup

To setup FF4j DynamoDB Store, simply use the appropriate constructor:

// Default initialization of FF4J
FF4j ff4j = new FF4j();
ff4j.setFeatureStore(new FeatureStoreDynamoDB());
ff4j.setPropertiesStore(new PropertyStoreDynamoDB());

DEPRECATED use ff4j-dynamodb.properties file to customize table name:

// Custom table name (DEPRECATED)
ff4j.setFeatureStore(new FeatureStoreDynamoDB("MySuperFeatureTable"));
ff4j.setPropertiesStore(new PropertyStoreDynamoDB("MySuperPropertyTable"));
// Custom Amazon DynamoDB Client
AmazonDynamoDB dynamoDB = AmazonDynamoDBClientBuilder.standard()
                .withRegion("eu-central-1")
                .withCredentials(new InstanceProfileCredentialsProvider(false))
                .build();
ff4j.setFeatureStore(new FeatureStoreDynamoDB(dynamoDB));
ff4j.setPropertiesStore(new PropertyStoreDynamoDB(dynamoDB));

AWS SSM Parameter Store

AWS System Manager Parameter Store provide a way to store configuration securely and easily. FF4J leverage this service to store properties. This module only enables property storage, not features, using PropertyStoreAwsSSM.java.

You need to provide a "path" in the form of /path/to/my/properties in order to store your properties in a specific area.

Two constructors are available:

FF4j ff4j = new FF4j();
ff4j.setPropertiesStore(new PropertyStoreAwsSSM("/ff4j/properties"));
// Custom Amazon SSM Client
AWSSimpleSystemsManagement ssmClient = AWSSimpleSystemsManagementClientBuilder.standard()
                .withRegion("eu-central-1")
                .withCredentials(new InstanceProfileCredentialsProvider(false))
                .build();

FF4j ff4j = new FF4j();
ff4j.setPropertiesStore(new PropertyStoreAwsSSM(ssmClient, "/ff4j/properties"));

MongoDB

Mongo v2 & v3

Neo4j

The graph database

EhCache

3.0

HttpClient

Using http client

CommonsConfiguration

The Commons Configuration software library provides a generic configuration interface which enables a Java application to read configuration data from a variety of sources. Commons Configuration provides typed access to single, and multi-valued configuration parameters as demonstrated by the following code.

Double double = config.getDouble("number");
Integer integer = config.getInteger("number");

FF4J proposes several use cases to work with commons configuration.

Use a FF4J PropertyStore as a source for Commons-configuration

You already has set up your application to use commons configurations but feel limited as the existing Configuration are limited to properties and xml files. Use the FF4jConfiguration to inject properties coming from other sources.

// sample object implementing the Configuration of commons-conf
FF4jConfiguration ff4jConf = new FF4jConfiguration(new InMemoryPropertyStore("ff4j-properties.xml"));

// Inject in existing with, for example a composite configuration
CompositeConfiguration config = new CompositeConfiguration();
config.setThrowExceptionOnMissing(true);
config.addConfiguration(ff4jConf);

Use Commons Configuration as input for FF4J property store

If properties are defined in a ff4j PropertyStore, they will be available in the web UI and CLI. AS a consequence you can perform CRUD operations through UI (caution some stores are read-only).

// Use the commons-conf syntax to init properties
Configuration conf = new PropertiesConfiguration("application.properties");

// Init property store
PropertyStore ff4jPropertyStore = new PropertyStoreCommonsConfig(conf);
ff4j.setPropertiesStore(ff4jPropertyStore );
// 

Archaius

Archaius is a framework proposed by Netflix to handle Properties Management Configuration. It leverages on commons-configurations (for best and worst). The integration with ff4j works in 2 ways.

Use the FF4J properties as a source for Archaius

Sample principle as before. Archaius leverage on commons configuration and provides new sources for properties. You can use the FF4JConfiguration and add FF4J properties to the dynamic configuration. Archiaus also introduce the PolledSourceConfiguration mechanism to polled target properties provider and update configuration accordingly. The ff4j-archaius module propose and implementation of PolledSourceConfiguration to work with FF4J. sample code

Use Archiaus for FF4J property store

Sample principle as before with Archaius, define a PropertyStore to work with a dynamic configuration, you will get all properties values in the UI. sample code

// Initialize configuration with the FF4J configuration implementation
FF4jConfiguration ff4jConfig = new FF4jConfiguration(new InMemoryPropertyStore("ff4j-properties.xml"));
// Define the property Store leveraging on archiaus
PropertyStore archaiusStore = new PropertyStoreArchaius(ff4jConfig);
// Sample FF4J Store
PropertyStore ff4jStore = new InMemoryPropertyStore("ff4j-properties.xml");

// FF4Store as polling Source for Archiaus
PolledConfigurationSource ff4jSource = new FF4jPolledConfigurationSource(ff4jStore);

// Working Thread (polling from ff4j => Archaius)
AbstractPollingScheduler scheduler = new FixedDelayPollingScheduler(0, 1000, true);

// Define configuration with polling and source
DynamicConfiguration dynConfig = new DynamicConfiguration(ff4jSource,scheduler);

// You Can now use the 'dynConfig' in pure Archaius
ConfigurationManager.install(configuration);
  • You Can now use any 'DynamicConfiguration ' as input for FF4J as well
PropertyStore archaiusStore2 = new PropertyStoreArchaius(dynConfig);

elastic ElasticSearch

Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents. Elasticsearch is developed in Java. Following an open-core business model, parts of the software are licensed under various open-source licenses (mostly the Apache License),[2] while other parts fall under the proprietary (source-available) Elastic License. Official clients are available in Java, .NET (C#), PHP, Python, Apache Groovy, Ruby and many other languages. According to the DB-Engines ranking, Elasticsearch is the most popular enterprise search engine followed by Apache Solr, also based on Lucene. source wikipedia

For this database, the 3 stores have been implemented FeatureStore, PropertyStore and EventRepository where the most useful should be probably the last one.

  • From FF4j 1.8.5 the store has been rewritten to support Elastic 6+.

1. Import relevant dependency

<dependency>
 <groupId>org.ff4j</groupId>
 <artifactId>ff4j-store-elastic</artifactId>
 <version>${ff4j.version}</version>
 </dependency>

The connectivity to Elastic leverage on the JESTClient library by searchbox-io. It will connect to the database using some HTTP connectivity. You need to define a JestClient object or bean using either your own logic (recommended to properly setup the connectivity) or use the utility method we provide ElasticQueryHelper.createDefaultJestClient.

2. Define JestClient object

@Bean
public JestClient jestClient() {
 try {
   return ElasticQueryHelper.createDefaultJestClient(new URL(elasticUrl));
 } catch (MalformedURLException e) { ... }
}

3. Define FF4j object

In Elastic Events, Properties and Features are stored in 3 different indices. The default names are (they will be created if not exist) ff4j_features, ff4j_events and ff4j_properties but you can override the value in the constructor.

@Bean
public FF4j getFF4j(JestClient jestClient) {
 FF4j ff4j = new FF4j();
 ff4j.setFeatureStore(new FeatureStoreElastic(jestClient));
 ff4j.setPropertiesStore(new PropertyStoreElastic(jestClient));
 ff4j.setEventRepository(new EventRepositoryElastic(jestClient));
 // Changing name of Feature index using the constructor
 // ff4j.setFeatureStore(new FeatureStoreElastic(jestClient), "my_custom_index");
 return ff4j;
}

4. Schema

As stated before Events, Properties and Features are stored in 3 different indices. The default names are (they will be created if not exist) ff4j_features, ff4j_events and ff4j_properties but you can override the value in the constructor.

The objects Feature, Properties and Events are simply serialized in JSON. We don't define the _uid we are using a custom proper id. As such the objects in the DB are

Feature:

{
  "_index": "ff4j_features",
  "_type": "feature",
  "_id": "i6kk0XEBKI8Aw8mO_-sY",
  "_version": 1,
  "_score": 0,
  "_source": {
    "uid": "first",
    "enable": true,
    "description": "description",
    "permissions": [
      "USER"
    ],
    "customProperties": {
      "myLogLevel": {
        "name": "myLogLevel",
        "type": "org.ff4j.property.PropertyLogLevel",
        "value": "DEBUG",
        "fixedValues": [
          "TRACE",
          "ERROR",
          "INFO",
          "FATAL",
          "DEBUG",
          "WARN"
        ]
      },
      "digitValue": {
        "name": "digitValue",
        "type": "org.ff4j.property.PropertyInt",
        "value": "1",
        "fixedValues": [
          "0",
          "1",
          "2",
          "3"
        ]
      },
      "ppint": {
        "name": "ppint",
        "type": "org.ff4j.property.PropertyInt",
        "value": "12"
      },
      "ppstring": {
        "name": "ppstring",
        "type": "org.ff4j.property.PropertyString",
        "value": "hello"
      },
      "ppdouble": {
        "name": "ppdouble",
        "type": "org.ff4j.property.PropertyDouble",
        "value": "12.5"
      },
      "ppListInt": {
        "name": "ppListInt",
        "type": "org.ff4j.property.PropertyString",
        "value": "12,13,14"
      },
      "ppboolean": {
        "name": "ppboolean",
        "type": "org.ff4j.property.PropertyBoolean",
        "value": "true",
        "fixedValues": [
          "false",
          "true"
        ]
      },
      "regionIdentifier": {
        "name": "regionIdentifier",
        "type": "org.ff4j.property.PropertyString",
        "value": "AMER",
        "fixedValues": [
          "SSSS",
          "AMER",
          "EAST"
        ]
      }
    }
  }
}

Property:

{
  "_index": "ff4j_properties",
  "_type": "property",
  "_id": "makl0XEBKI8Aw8mOBuue",
  "_version": 1,
  "_score": 0,
  "_source": {
    "readOnly": false,
    "name": "k",
    "type": "org.ff4j.property.PropertyBigDecimal",
    "value": 200000000.1234567
  }
}

Event:

{
  "_index": "ff4j_events",
  "_type": "event",
  "_id": "boKDpnEBNOvrkWJic7HX",
  "_version": 1,
  "_score": 0,
  "_source": {
    "uuid": "5da2cd94-9b32-4410-9ff2-38d22d36f839",
    "timestamp": 1587636564918,
    "duration": 0,
    "hostName": "clunhost",
    "source": "EMBEDDED_SERVLET",
    "name": "f2",
    "type": "feature",
    "action": "checkOn",
    "customKeys": {}
  }
}

5. Samples Codes and resources

Here are the resources to work with Elastic

Resources URL
Working Spring Boot 1x ff4j-springboot1x-elastic
Working Spring Boot 2x [ff4j-spring-boot-elastic]https://github.com/ff4j/ff4j-samples/tree/master/spring-boot-2x/ff4j-sample-springboot2x-elastic)
A Kibana Dashboard sample dashboard

The webui with 3 stores initialized: pic

The provided Kibana Dashboard: pic

Clone this wiki locally