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
自实现

// 内置的各种策略
BlackList
ClientFilter
DarkLaunch
Drools
Expression
OfficeHour
Ponderation
ReleaseDate
ServerFilter
WhiteList

最常见的就是ReleaseDateFlipStrategy. 先开发验证好“国庆活动”等国庆当天再生效

Group

分组,将特性打包分组,把多个开关合成一个开关

存储形式

前面提到了FF4J提供了很多持久化方式,包括内存、jdbc等

内存

默认的方式,以xml作为存储介质,然后解析xml将Feature等加载到内存。
该方式会在应用重启后复原,即之前做的改动都不存在了,会重新读xml到内存

<?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>

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(特性框架)简介及入门相关推荐

  1. 分布式应用框架Akka快速入门

    转自:分布式应用框架Akka快速入门_jmppok的专栏-CSDN博客_akka 本文结合网上一些资料,对他们进行整理,摘选和翻译而成,对Akka进行简要的说明.引用资料在最后列出. 1.什么是Akk ...

  2. 分布式事物框架--EasyTransaction的入门介绍

    分布式事物框架--EasyTransaction的入门介绍 柔性事务,分布式事务,TCC,SAGA,可靠消息,最大努力交付消息,事务消息,补偿,全局事务,soft transaction, distr ...

  3. Python自动化开发【1】:Python简介和入门

    Python自动化开发之路 [第1篇]:Python简介和入门 编程与编程语言 一 编程与编程语言python是一门编程语言,作为学习python的开始,需要事先搞明白:编程的目的是什么?什么是编程语 ...

  4. 几种常用深度学习框架简介

    几种常用深度学习框架简介 一.TensorFlow 1.1 Tensorflow简介 1.2 使用文档 1.3 预训练模型 二.Pytorch 2.1 Pytorch简介 2.2 使用文档 2.3 预 ...

  5. 【Spring】框架简介

    [Spring]框架简介 Spring是什么 Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IOC(Inverse Of Control:反转控制)和AOP(Asp ...

  6. Java开源——常见J2EE框架简介

    Java开源--常见J2EE框架简介 Spring Framework Spring是一个解决了许多在J2EE开发中常见的问题的强大框架. Spring提供了管理业务对象的一致方法并且鼓励了注入对接口 ...

  7. Spring框架简介

    Spring框架简介 Spring Framework 是一个开源的Java/Java EE全功能栈(full-stack)的应用程序框架,以Apache许可证形式发布,也有.NET平台上的移植版本. ...

  8. AI之FL:联邦学习(Federated Learning)的简介、入门、应用之详细攻略

    AI之FL:联邦学习(Federated Learning)的简介.入门.应用之详细攻略 导读       2019 年2 月,微众银行 AI 团队自主研发的全球首个工业级联邦学习框架 FATE(Fe ...

  9. DL之CG:Computational Graph计算图的简介、入门、使用之详细攻略

    DL之CG:Computational Graph计算图的简介.入门.使用之详细攻略 目录 计算图的简介 计算图的入门 CG与局部计算 计算图的使用 计算图的简介 计算图Computational G ...

  10. Ubuntu:Ubuntu下安装Anaconda和Tensorflow的简介、入门、安装流程之详细攻略

    Ubuntu:Ubuntu下安装Anaconda和Tensorflow的简介.入门.安装流程之详细攻略 目录 安装流程 1.安装nvidia显卡驱动 2.安装cuda8 3.安装Cudnn 4.Ana ...

最新文章

  1. pytorch笔记 torch.clamp(截取上下限)
  2. python列表生成器语法_python语法_列表生成器_生成器_迭代器_异常捕获
  3. Oracle从小白到大牛的刷题之路(建议收藏学习)
  4. (转)ASP.NET中常见文件类型及用途
  5. 在线数据库链接字符串查询
  6. 微信小程序个人笔记!
  7. 修复/boot及/etc/fstab、自制linux、编译安装内核
  8. Java进阶架构实战——Redis在京东到家的订单中的使用
  9. trimmed ICP及其在PCL代码解析与使用
  10. 菜鸟起步2-逆向分析学习
  11. 超市产品关联性分析——天池竞赛
  12. mysql 连续打卡_MySQL查询连续打卡信息?
  13. iOS/Mac OS X 汉字转拼音
  14. python获取ALM数据
  15. 广告联盟是什么,其优势有哪些?
  16. vacuum 数据库 用法_postgresql vacuum操作
  17. 智能卡的操作系统——COS
  18. 中文转换成NCR编码(utf-8 16进制)
  19. python对比2个文件内容
  20. 理解 ARC 实现原理 -- 详细总结

热门文章

  1. 芯旺微车规级功夫KF32A150,SPI调试经验
  2. c语言中怎么画直线,ps如何画直线 【操作流程】
  3. 畅聊微信支付遇到的坑
  4. Java虚拟机(Jvm详解)
  5. Python:variable in function(argument、function) name should be lowercase 处理方式
  6. 微信小程序开发(九)之开发版和测试版小程序打不开的问题
  7. 敏俊物联MJIOT-AMB-03 RTL8710BN 高性能wifi模块
  8. neo4j-OGM 动态cypher java查询
  9. 小波学习笔记——模极大值去噪
  10. Java图片压缩及解决遇到压缩时出现黑底的问题