ff4j 一些高级概念

feature groups

通过feature groups 我们可以将同一个release 实例的feature 聚合起来,灵活管理

  • 参考配置
<?xml version="1.0" encoding="UTF-8" ?>
<features>
 <!-- Sample Feature Group -->
 <feature-group name="release-2.3">
    <feature uid="users-story1" enable="false" /> 
    <feature uid="users-story2" enable="false" />
 </feature-group>
 <feature uid="featA" enable="true" /> 
 <feature uid="featB" enable="false" />
</features>
  • 代码访问
@Test
public void myGroupTest() {
 FF4j ff4j = new FF4j("ff4j-groups.xml");
 // Check features loaded
 assertEquals(4, ff4j.getFeatures().size());
 assertTrue(ff4j.exist("featA")); 
 assertTrue(ff4j.exist("users-story1"));   
 assertTrue(ff4j.getStore().existGroup("release-2.3")); 
 System.out.println("Features loaded OK");
 // Given 
 assertFalse(ff4j.check("users-story1")); 
 assertFalse(ff4j.check("users-story2"));
 // When 
 ff4j.enableGroup("release-2.3");
 // Then 
 assertTrue(ff4j.check("users-story1")); 
 assertTrue(ff4j.check("users-story2"));
}
  • 通过FeatureStore操作
@Test
public void workWithGroupTest() {
  // Given
  FF4j ff4j = new FF4j("ff4j-groups.xml");
  assertTrue(ff4j.exist("featA"));
  // When 
  ff4j.getStore().addToGroup("featA", "new-group");
  // Then
  assertTrue(ff4j.getStore().existGroup("new-group"));
  assertTrue(ff4j.getStore().readAllGroups().contains("new-group"));
  Map<String, Feature> myGroup = ff4j.getStore().readGroup("new-group");
  assertTrue(myGroup.containsKey("featA"));
  // A feature can be in a single group 
  // Here changing => deleting the last element of a group => deleting the group 
  ff4j.getStore().addToGroup("featA", "group2");
  assertFalse(ff4j.getStore().existGroup("new-group"));
}

aop 方式的编程模型

传统模式,我们都是基于if 以及else 模式check feature ,但是我们可以通过aop 的模式编程,当然aop 的模式也很不错(
基于规则的开发模式也很不错)
传统模式

 
if (ff4j.check("featA")) {
 // new code 
} else {
 // legacy 
}
  • aop 模式
    依赖
 
<dependency>
  <groupId>org.ff4j</groupId>
  <artifactId>ff4j-aop</artifactId>
  <version>${ff4j.version}</version>
</dependency>

定义接口注解

public interface GreetingService {
 @Flip(name="language-french", alterBean="greeting.french")
 String sayHello(String name);
}
 
 

定义一个实现

@Component("greeting.english")
public class GreetingServiceEnglishImpl implements GreetingService {
 public String sayHello(String name) {
  return "Hello " + name;
 }
}

定义另外一个实现

@Component("greeting.french")
public class GreetingServiceFrenchImpl implements GreetingService {
  public String sayHello(String name) {
    return "Bonjour " + name;
  }
}
 

定义bean

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="org.ff4j.aop, org.ff4j.sample"/>
<bean id="ff4j" class="org.ff4j.FF4j" >
 <property name="store" ref="ff4j.store.inmemory" />
</bean>
<bean id="ff4j.store.inmemory" class="org.ff4j.store.InMemoryFeatureStore" >
 <property name="location" value="ff4j-aop.xml" />
</bean>
</beans>

定义feature xml

<?xml version="1.0" encoding="UTF-8" ?>
  <features>
    <feature uid="language-french" enable="false" />
  </features>

引用bean

import junit.framework.Assert;
import org.ff4j.FF4j; 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:*applicationContext-aop.xml")
public class FeatureFlippingThoughAopTest {
 @Autowired
 private FF4j ff4j;
 @Autowired
 @Qualifier("greeting.english")
 private GreetingService greeting;
 @Test
 public void testAOP() {
  Assert.assertTrue(greeting.sayHello("CLU").startsWith("Hello"));
  ff4j.enable("language-french");
  Assert.assertTrue(greeting.sayHello("CLU").startsWith("Bonjour"));
  } 
}

审计以及监控

前边有介绍过ff4j 对于审计的介绍,我们只需要通过配置就可以,ff4j底层帮助我们处理了

ff4j.setEventRepository(<HERE YOUR EVENT_REPOSITORY DEFINITION>);
ff4j.audit(true);

底层实现

if (isEnableAudit()) {
 if (fstore != null && !(fstore instanceof FeatureStoreAuditProxy)) {
   this.fstore = new FeatureStoreAuditProxy(this, fstore);
 }
 if (pStore != null && !(pStore instanceof PropertyStoreAuditProxy)) { 
   this.pStore = new PropertyStoreAuditProxy(this, pStore);
 }

支持的event 存储

// JDBC
HikariDataSource hikariDataSource;
ff4j.setEventRepository(new EventRepositorySpringJdbc(hikariDataSource));
// ELASTICSEARCH
URL urlElastic = new URL("http://" + elasticHostName + ":" + elasticPort);
ElasticConnection connElastic = new ElasticConnection(ElasticConnectionMode.JEST_CLIENT, elasticIndexName, urlElastic);
ff4j.setEventRepository(new EventRepositoryElastic(connElastic));
// REDIS
RedisConnection redisConnection = new RedisConnection(redisHostName, redisPort, redisPassword);
ff4j.setEventRepository(new EventRepositoryRedis(redisConnection ));
// MONGODB
MongoClient mongoClient;
ff4j.setEventRepository(new EventRepositoryMongo(mongoClient, mongoDatabaseName));
// CASSANDRA
Cluster cassandraCluster;
CassandraConnection cassandraConnection = new CassandraConnection(cassandraCluster)
ff4j.setEventRepository(new EventRepositoryCassandra(cassandraConnection));

cache 处理

cache 对于大量数据访问是一个不错的选择,ff4j 提供了好多cache 的支持
参考内置实现

 
// REDIS (dependency: ff4j-store-redis)
RedisConnection redisConnection = new RedisConnection(redisHostName, redisPort, redisPassword);
FF4JCacheManager ff4jCache = new FF4jCacheManagerRedis(redisConnection );
// EHCACHE (dependency: ff4j-store-ehcache)
FF4JCacheManager ff4jCache = new FeatureCacheProviderEhCache();
// HAZELCAST (dependency: ff4j-store-hazelcast)
Config hazelcastConfig;
Properties systemProperties;
FF4JCacheManager ff4jCache = new CacheManagerHazelCast(hazelcastConfig, systemProperties);
// JHIPSTER
HazelcastInstance hazelcastInstance;
FF4JCacheManager ff4jCache = new JHipsterHazelcastCacheManager(hazelcastInstance);
 

配置ff4j 实例

ff4j.cache(ff4jCache);

spring boot 集成

当然官方提供了spring boot 的starter,使用起来也是比较简单的
添加依赖

 
<dependency>
    <groupId>org.ff4j</groupId>
    <artifactId>ff4j-jmx</artifactId>
</dependency>

引用


@Configuration
public class FF4JConfiguration {
    /**
     * Create and configure FF4J
     */
    @Bean
    public FF4j getFF4j() {
        return new FF4j("ff4j.xml");
    }
}

参考资料

https://github.com/ff4j/ff4j/wiki/Advanced-Concepts