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");
}
}