• 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

  • 相关阅读:
    python中向函数传递列表
    python中函数与while循环结合
    python中使用函数和不使用函数程序的比较
    python中函数返回字典
    python中传递任意数量的实参 (收集参数)
    python中给函数添加返回字典中的可选键值对
    python中禁止函数修改列表
    python中结合使用位置实参和任意数量实参(收集参数)
    SAP所有用户出口列表(4.6C)(续)
    常见的abap面试题目,请大家对照学习
  • 原文地址:https://www.cnblogs.com/rongfengliang/p/12739832.html
Copyright © 2020-2023  润新知