ff4j 一些核心概念

了解ff4j 的一些核心概念我们就可以更好的学习以及使用ff4j,以下是一些学习,整理

Feature

Feature 主要是用表示应用的一个功能,通过一个唯一的id标示(uid),主要目的是在运行时可以按需启用以及禁用
特性,FF4j 添加了一些属性(比如描述,可选的grouoname)访问控制列表,以及一些flipping 策略,同时我们也可以
添加自己的自定义属性

  • 参考代码使用
 
// Simplest declaration
Feature f1 = new Feature("f1");
// Declare with description and initial state
Feature f2 = new Feature("f2", false, "sample description");
// Illustrate ACL & Group
Set < String > permission = new HashSet<String>();
permission.add("BETA-TESTER");
permission.add("VIP");
Feature f3 = new Feature("f3", false, "sample description", "GROUP_1", permission);
// Custom properties
Feature f4 = new Feature("f4");
f4.addProperty(new PropertyString("p1", "v1"));
f4.addProperty(new PropertyDouble("pie", Math.PI));
f4.addProperty(new PropertyInt("myAge", 12));
// Flipping Strategy
Feature f5 = new Feature("f5");
Calendar nextReleaseDate = Calendar.getInstance();
nextReleaseDate.set(Calendar.MONTH, Calendar.SEPTEMBER);
nextReleaseDate.set(Calendar.DAY_OF_MONTH, 1);
f5.setFlippingStrategy(new ReleaseDateFlipStrategy(nextReleaseDate.getTime()));
Feature f6 = new Feature("f6");
f6.setFlippingStrategy(new DarkLaunchStrategy(0.2d));        
Feature f7 = new Feature("f7");
f7.setFlippingStrategy(new WhiteListStrategy("localhost"));

FeatureStore

FeatureStore 的目的是实现持久化存储的,提供了一个通用的crud,可以方便集成各类后端存储

  • 参考代码使用
InMemoryFeatureStore fStore = new InMemoryFeatureStore();
// Operations on features
fStore.create(f5);
fStore.exist("f1");
fStore.enable("f1");
// Operations on permissions
fStore.grantRoleOnFeature("f1","BETA");
// Operation on groups  
fStore.addToGroup("f1", "g1");      
fStore.enableGroup("g1");
Map < String, Feature > groupG1 = fStore.readGroup("g1");
// Read all informations
Map < String, Feature > mapOfFeatures = fStore.readAll();
  • 说明
    对于FeatureStore的访问我们应该通过ff4j

Property

Property 是一个实体,可以包含各类的值,同时ff4j提供了一个通用的泛型类型我们可以自由扩展

  • 参考使用
 
PropertyBigDecimal p01 = new PropertyBigDecimal();
PropertyBigInteger p02 = new PropertyBigInteger("d2", new BigInteger("1"));
PropertyBoolean p03 = new PropertyBoolean("d2", true);
PropertyByte p04 = new PropertyByte("d2", "1");
PropertyCalendar p05 = new PropertyCalendar("d2", "2015-01-02 13:00");
PropertyDate p06 = new PropertyDate("d2", "2015-01-02 13:00:00");
PropertyDouble p07 = new PropertyDouble("d2", 1.2);
PropertyFloat p08 = new PropertyFloat("d2", 1.1F);
PropertyInt p09 = new PropertyInt("d2", 1);
PropertyLogLevel p10 = new PropertyLogLevel("DEBUG");
PropertyLong p11 = new PropertyLong("d2", 1L);
PropertyShort p12 = new PropertyShort("d2", new Short("1"));
PropertyString p13 = new PropertyString("p1");
  • 自定义扩展
import org.ff4j.property.Property;
import org.ff4j.test.property.CardinalPoint.Point;
public class CardinalPoint extends Property<Point> {
    private static final long serialVersionUID = 1792311055570779010L;
    public static enum Point {NORTH, SOUTH, EAST, WEST};
    public CardinalPoint(String uid, Point lvl) {
        super(uid, lvl, Point.values());
    }
    /** {@inheritDoc} */
    public Point fromString(String v) { return Point.valueOf(v); } 
    public void north() { setValue(Point.NORTH); }
    public void south() { setValue(Point.SOUTH); }  
    public void east() { setValue(Point.EAST); } 
    public void west() { setValue(Point.WEST); }    
}

PropertyStore

类似于FeatureStore,目的是存储Property

  • 参考使用
PropertyStore pStore = new InMemoryPropertyStore();
// CRUD
pStore.existProperty("a");
pStore.createProperty(new PropertyDate("a", new Date()));
Property<Date> pDate = (Property<Date>) pStore.readProperty("a");
pDate.setValue(new Date());
pStore.updateProperty(pDate);
pStore.deleteProperty("a");
// Several
pStore.clear();
pStore.readAllProperties();
pStore.listPropertyNames();
  • 操作
// Access Property Store (with all its proxy : Audit, Cache, AOP....)
PropertyStore pStore1 = ff4j.getPropertiesStore();
// Access concrete class and implementation of the property store
PropertyStore pStore2 = ff4j.getConcretePropertyStore();
  • 一些语法糖
ff4j.getProperties();
ff4j.createProperty(new PropertyString("p1", "v1"));
ff4j.getProperty("p1");
ff4j.deleteProperty("p1");

ff4j 架构概览

ff4j的设计是所有的操作都应通过ff4j 类,这样可以隐藏底层

  • 细节
* FeatureStore and PropertyStore 主要是关于存储的通用crud
* EventRepository 主要是方便事件监控
* 如果 `audit` 设置为true, 对于存储的访问会通过包装的代理类`FeatureStoreAuditProxy` 以及`PropertyStoreAuditProxy` 进行数据访问,同时每个操作都会同时`EventRepository`EventPublisher

参考内部代码

// Publish feature usage to repository
private void publishCheck(String uid, boolean checked) {
   if (isEnableAudit()) {
     getEventPublisher().publish(new EventBuilder(this)
                                      .feature(uid)
                                      .action(checked ? ACTION_CHECK_OK : ACTION_CHECK_OFF)
                                      .build());
   }
}
// PropertyStoreAuditProxy : Publish create operation to repository
public < T > void createProperty(Property<T> prop) {
  long start = System.nanoTime();
    target.createProperty(prop);
    ff4j.getEventPublisher().publish(new EventBuilder(ff4j)
                    .action(ACTION_CREATE)
                    .property(prop.getName())
                    .value(prop.asString())
                    .duration(System.nanoTime() - start)
                    .build());
}
  • FF4jCacheProxy
    主要目的是加速数据的获取,方便处理数据库以及http 类型的数据操作,底层依赖FF4JCacheManager
    对于需要分布式一致性的场景可以使用 Terracotta, HazelCast or Redis (even if eh-cache is available).
    参考代码
 
public Feature read(String featureUid) {
  Feature fp = getCacheManager().getFeature(featureUid);
    // not in cache but may has been created from now
    if (null == fp) {
      fp = getTargetFeatureStore().read(featureUid);
        getCacheManager().putFeature(fp);
    }
    return fp;
}
public void delete(String featureId) {
  // Access target store
    getTargetFeatureStore().delete(featureId);
    // even is not present, evict won't failed
    getCacheManager().evictFeature(featureId);
}
  • AuthorizationsManager
    主要是处理feature的权限,但是ff4j不自己创建role,底层依赖shiro以及spring security 等实现
    参考代码
 
public boolean isAllowed(Feature featureName) {
    // No authorization manager, returning always true
    if (getAuthorizationsManager() == null) {
      return true;
    }
    // if no permissions, the feature is public
    if (featureName.getPermissions().isEmpty()) {
      return true;
    }
    Set<String> userRoles = getAuthorizationsManager().getCurrentUserPermissions();
    for (String expectedRole : featureName.getPermissions()) {
        if (userRoles.contains(expectedRole)) {
            return true;
        }
    }
    return false;
}

ff4j 的使用

  • init
    我们是需要通过FF4j 初始化的
    一些细节
 
* If a proxy is not explicitly declared it won't be enabled (Cache, Audit)
* If stores are not explicitly defined, ff4j will use in-memory implementations (features, properties, events)

同时已经包含了一个灵活的基于内存以及文件的访问包装

FF4j ff4j = new FF4j("ff4j.xml");

以下高级配置

// Default constructor
FF4j ff4j = new FF4j();
// Initialized stores with JDBC
BasicDataSource dbcpDataSource = new BasicDataSource();
dbcpDataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dbcpDataSource.setUsername("sa");
dbcpDataSource.setPassword("");
dbcpDataSource.setUrl("jdbc:hsqldb:mem:.");
ff4j.setFeatureStore(new JdbcFeatureStore(dbcpDataSource));
ff4j.setPropertiesStore(new JdbcPropertyStore(dbcpDataSource));
ff4j.setEventRepository(new JdbcEventRepository(dbcpDataSource));
// Enable Audit Proxy
ff4j.audit();
// Enable Cache Proxy
ff4j.cache(new InMemoryCacheManager());
// Explicite import
XmlConfig xmlConfig = ff4j.parseXmlConfig("ff4j.xml");
ff4j.getFeatureStore().importFeatures(xmlConfig.getFeatures().values());
ff4j.getPropertiesStore().importProperties(xmlConfig.getProperties().values());
 

当ff4j 初始化之后,使用方法

FF4j ff4j = new FF4j("ff4j.xml");
if (ff4j.check("foo") {
 // do something
}
 

自动创建模式

@Test
public void createFeatureDynamically() {
 // Given : Initialize as empty store
 FF4j ff4j = new FF4j();
 // When: Dynamically register new features
 ff4j.create("f1").enable("f1");
 // Then
 assertTrue(ff4j.exist("f1")); 
 assertTrue(ff4j.check("f1"));
}

默认对于不包含的feature会触发异常,但是可以通过autocreate 为true,避免

@Test(expected = FeatureNotFoundException.class)
public void readFeatureNotFound() {
  // Given
  FF4j ff4j = new FF4j();
  // When
  ff4j.getFeature("i-dont-exist");
  // Then, expect error...
}
 
@Test
public void readFeatureNotFoundAutoCreate() {
  // Given
  FF4j ff4j = new FF4j();
  ff4j.autoCreate(true);
  assertFalse(ff4j.exist("foo"));
  // When
  ff4j.check("foo");
  // Then
  assertTrue(ff4j.exist("foo"));
  assertFalse(ff4j.check("foo"));
}

权限以及安全

很对时候对于特性的启用,可能是部分用户,但是ff4j不进行权限的管理,实际的处理需要依赖外部的权限以及安全实现

  • AuthorizationManager
    基于spring security 的一个开箱即用的实现
    参考实现
 
public class CustomAuthorizationManager implements AuthorizationsManager {
  public static ThreadLocal<String> currentUserThreadLocal = new ThreadLocal<String>();
  private static final Map<String, Set<String>> permissions = new HashMap<String, Set<String>>();
  static {
    permissions.put("userA", new HashSet<String>(Arrays.asList("user", "admin", "beta")));
    permissions.put("userB", new HashSet<String>(Arrays.asList("user")));
    permissions.put("userC", new HashSet<String>(Arrays.asList("user", "beta")));
  }
  /** {@inheritDoc} */
  @Override
  public Set<String> getCurrentUserPermissions() { 
    String currentUser = currentUserThreadLocal.get();
    return permissions.containsKey(currentUser) ? permissions.get(currentUser) : new HashSet<String>();
  }
  /** {@inheritDoc} */
  @Override
  public Set<String> listAllPermissions() {
    Set<String> allPermissions = new HashSet<String>();
    for (Set<String> subPersmission : permissions.values()) {
      allPermissions.addAll(subPersmission);
    }
    return allPermissions;
  }
}
 

配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>
<features>
 <feature uid="sayHello" description="my first feature" enable="true">
 <security>
  <role name="admin" />
 </security>
</feature>
<feature uid="sayGoodBye" description="null" enable="true">
 <security>
  <role name="beta" />
  <role name="user" />
 </security>
</feature>
</features>
 
 

代码集成

@Test
public void sampleSecurityTest() {
 // Create FF4J
 FF4j ff4j = new FF4j("ff4j-security.xml");
 // Add the Authorization Manager Filter
 AuthorizationsManager authManager = new CustomAuthorizationManager();
 ff4j.setAuthorizationsManager(authManager);
 // Given : Feature exist and enable 
 assertTrue(ff4j.exist("sayHello"));
 assertTrue(ff4j.getFeature("sayHello").isEnable());
 // Unknow user does not have any permission => check is false
 CustomAuthorizationManager.currentUserThreadLocal.set("unknown-user");  
 System.out.println(authManager.getCurrentUserPermissions());
 assertFalse(ff4j.check("sayHello"));
 // userB exist but he has not role Admin
 CustomAuthorizationManager.currentUserThreadLocal.set("userB");
 System.out.println(authManager.getCurrentUserPermissions());
 assertFalse(ff4j.check("sayHello"));
 // userA is admin
 CustomAuthorizationManager.currentUserThreadLocal.set("userA");
 System.out.println(authManager.getCurrentUserPermissions());
 assertTrue(ff4j.check("sayHello"));
}

Flipping Strategy

主要是用来处理特性是否开启的

  • 参考处理逻辑

 

 


参考代码

 
public class YaFF4jTest{
    @Test
    public void sampleFlippingStrategy() {
        // Given
        //default, in memory and empty.
        FF4j ff4j = new FF4j();
        Feature f1 = new Feature("f1", true);
        ff4j.getFeatureStore().create(f1);
        //The feature is enabled, no flipping strategy
        Assert.assertTrue(ff4j.check("f1"));
        // Let's add a flipping strategy (yyyy-MM-dd-HH:mm)
        f1.setFlippingStrategy(new ReleaseDateFlipStrategy("2027-03-01-00:00"));
        ff4j.getFeatureStore().update(f1);
        // Even is feature is enabledn as strategy is false...
        Assert.assertFalse(ff4j.check("f1"));
    }
}
  • FlippingStrategy 接口
    参考图

     

     

参考资料

https://github.com/ff4j/ff4j/wiki/Core-Concepts
https://github.com/ff4j/ff4j/wiki/Flipping-Strategies

posted on 2020-04-20 19:12  荣锋亮  阅读(856)  评论(0编辑  收藏  举报

导航