FF4J(特性框架)简介及入门
FF4J
什么是FF4J
ff4j是一款開源的實現特性功能切換的框架。簡單來說通過aop和各種配置,去替代用硬代碼if…else
簡單入門
ff4j.xml
ff4j提供多種持久化方式(jdbc、redis、mongodb等)
<?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>Usage
public class FF4jHelloTest {@Testpublic void testMyFirst(){assertNotNull(getClass().getClassLoader().getResourceAsStream("ff4j.xml"));// When: initFF4j ff4j = new FF4j("ff4j.xml");assertEquals(5, ff4j.getFeatures().size());// 是否存在特性assertTrue(ff4j.exist("hello"));// 特性是否生效assertTrue(ff4j.check("hello"));// Usageif (ff4j.check("hello")){System.out.println("Hello FF4j !");}ff4j.disable("hello");assertFalse(ff4j.check("hello"));}@Testpublic void testAutoCreate(){assertNotNull(getClass().getClassLoader().getResourceAsStream("ff4j.xml"));// When: initFF4j ff4j = new FF4j("ff4j.xml");try {// 如果沒有開始自動創建,會拋異常if (ff4j.check("notExistFeature")){// do nothing}} catch (Exception e){System.out.println("ff4j throw exception when feature not exist !");}// 開啟后,如果不存在會自動創建,默認不生效ff4j.setAutocreate(true);if (!ff4j.check("notExistFeature")){System.out.println("feature toggle off or not exist");}}@Testpublic void testGroups(){assertNotNull(getClass().getClassLoader().getResourceAsStream("ff4j.xml"));// When: initFF4j 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"));// 啟用分組,這里用分組可以同時開啟或關閉多個特性功能ff4j.getStore().enableGroup("sprint_3");assertTrue(ff4j.check("userStory3_1"));assertTrue(ff4j.check("userStory3_2"));} }核心組件
Feature
Feature,顧名思義,就是特性,通過唯一標識來代表一個特性
// Simplest declaration Feature f1 = new Feature("f1");// Declaration with desc and init state Feature f2 = new Feature("f2", false, "sample desc");// ACL & Group Set<String> permission = new HashSet<>(2); permission.add("BETA-TESTER"); permission.add("VIP"); Feature f3 = new Feature("f3", false, "sample desc", "g1", permission);// Custom Properties Feature f4 = new Feature("f4"); f4.addProperty(new PropertyString("p1", "v1")); f4.addProperty(new PropertyDouble("pie", Math.PI));// Flipping Strategy Feature f5 = new Feature("f5"); Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.MONTH, Calendar.SEPTEMBER); calendar.set(Calendar.DAY_OF_MONTH, 1); f5.setFlippingStrategy(new ReleaseDateFlipStrategy(calendar.getTime()));Feature f6 = new Feature("f6"); f6.setFlippingStrategy(new DarkLaunchStrategy(0.2D));Feature f7 = new Feature("f7"); f7.setFlippingStrategy(new WhiteListStrategy("localhost"));FeatureStore
同理,特征持久化抽象(內存、jdbc、redis等)
Feature f5 = new Feature("f5"); Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.MONTH, Calendar.SEPTEMBER); calendar.set(Calendar.DAY_OF_MONTH, 1); f5.setFlippingStrategy(new ReleaseDateFlipStrategy(calendar.getTime()));InMemoryFeatureStore store = new InMemoryFeatureStore(); store.create(f5); store.exist("f1"); store.enable("f1");// grant permission store.grantRoleOnFeature("f1", "BETA");store.addToGroup("f1", "g1"); store.enable("g1"); Map<String, Feature> g1 = store.readGroup("g1");store.readAll();Property
屬性,特性的屬性
// wrap java type// 默認包裝了很多Java類型,也可以通過extends Property<?>擴展 PropertyBigDecimal propertyBigDecimal = new PropertyBigDecimal(); PropertyBoolean f2 = new PropertyBoolean("f2", false); PropertyByte b1 = new PropertyByte("b1", Byte.valueOf("1")); PropertyDate d1 = new PropertyDate("d1", new Date());PropertyStore
屬性持久化
PropertyStore pStore = new InMemoryPropertyStore();// CURD pStore.existProperty("a"); pStore.createProperty(new PropertyDate("d1", new Date())); Property<Date> pDate = (Property<Date>) pStore.readProperty("a"); pDate.setValue(new Date()); pStore.updateProperty(pDate); pStore.deleteProperty("a");pStore.clear(); pStore.readAllProperties(); pStore.listPropertyNames();// get PropertyStore from ff4j FF4j ff4j = new FF4j("ff4j.xml"); PropertyStore propertiesStore = ff4j.getPropertiesStore();// Usage FF4J can wrap all operation // proxy all, u know ff4j.createProperty(new PropertyDate("ddd", new Date()));Cache
如果持久化方式為jdbc等(數據在磁盤),可以考慮將Feature、Property等緩存到內存, 具體可參考FF4JCacheManager
public Feature read(String featureUid) {Feature fp = getCacheManager().getFeature(featureUid);// not in cache but may has been created from nowif (null == fp) {fp = getTargetFeatureStore().read(featureUid);getCacheManager().putFeature(fp);}return fp; }Security
老生常談,認證和授權。可以用于線上做灰度,新特性功能只有灰度用戶可以體驗
FF4J集成了springSecurity和ApacheShiro
Flipping Strategy
FlippingStrategy的evaluate方法判斷是否放開特性。自定義的strategy可以extends AbstractFlipStrategy
自實現
最常見的就是ReleaseDateFlipStrategy. 先開發驗證好“國慶活動”等國慶當天再生效
Group
分組,將特性打包分組,把多個開關合成一個開關
存儲形式
前面提到了FF4J提供了很多持久化方式,包括內存、jdbc等
內存
默認的方式,以xml作為存儲介質,然后解析xml將Feature等加載到內存。
該方式會在應用重啟后復原,即之前做的改動都不存在了,會重新讀xml到內存
JDBC
主要是基于RDBMS,FF4J內置了JdbcFeatureStore、JdbcPropertyStore等,同時
提供了基于關系型數據庫的DDL腳本
DDL腳本
-- 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 -- 存儲ACL(訪問控制列表) 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") );簡單使用
jdbc
// 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));spring-jdbc
// 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);Redis
非關系型的KV數據庫, 內置了FeatureStoreRedis、PropertyStoreRedis等,相當于
將feature、property之類的存儲在Redis
redis連接
// 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")));ff4j curd
// 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");存儲形式
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>其他相關
參考:https://github.com/ff4j/ff4j/wiki
1. spring框架整合, 可以通過@Flip注解的方式松耦合 2. 內置的web管理頁面,一般叫它dashboard 3. 事件處理,觀察者模式 4. FF4j這個類相當于一個門面,通過它可以操作上面提到的核心組件,并且做一些代理的事情總結
以上是生活随笔為你收集整理的FF4J(特性框架)简介及入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Jupyter notebook远程访问
- 下一篇: GPS数据格式分析