Search code examples
ff4j

How to setup a FF4j RegionFlippingStrategy based on RestController path parameters?


I've just started using FF4j to switch between 2 different API implementations depending on which market the user is on (market = brand/country pair). Here is the code snippet :

@RestController
@RequestMapping("/{brand}/{country}")
public class HomeController {

    private final ApiService apiService;

    public HomeController(@Qualifier("new-api") ApiService apiService) {
        this.apiService = apiService;
    }

    @GetMapping("/hey")
    public String hey(@PathVariable("brand") String brand,
                      @PathVariable("country") String country) {
        return apiService.whichApi();
    }

}

interface ApiService {
    @Flip(name = "legacy-api", alterBean = "legacy-api")
    String whichApi();
}

@Component("new-api")
class NewApiApiService implements ApiService {
    @Override
    public String whichApi() {
        return "NEW_API";
    }
}

@Component("legacy-api")
class LegacyApiApiService implements ApiService {
    @Override
    public String whichApi() {
        return "LEGACY_API";
    }
}

I created a RegionFlippingStrategy (as the doc says) to define for which market I want to use the legacy-api, but I cannot make it work.

How can I register my new strategy into FF4j ?

How can I switch between API dynamically based on the home controller brand/country inputs ?


Solution

  • How can I register my new strategy into FF4j ?

    Let's remind the strategy code

    public class MarketFlippingStrategy extends AbstractFlipStrategy {
    
     private final Set<String> setOfGrantedMarket = new HashSet<String>();
    
     @Override
     public void init(String featureName, Map<String, String> initValue) { 
       super.init(featureName, initValue);  
       assertRequiredParameter("grantedMarket");
       String[] arrayOfRegions = initValue.get("grantedMarket").split(",");
       setOfGrantedRegions.addAll(Arrays.asList(arrayOfRegions));
     }
    
     @Override
     public boolean evaluate(String fName, FeatureStore fStore, FlippingExecutionContext ctx) {
      return setOfGrantedRegions.contains(ctx.getString("market", true));
     }
    }
    

    The FlipStrategy should be registered in the definition of the feature legacy-api:

    • You can do it manually with the web console (Features > Edit the feature > Pick field Strategy and edit the class and parameters (param1=value1).
    • You can do it in a file and import with console ff4j now support xml, yaml or properties.

    With the following definition the feature will be enabled only for the granted markets:

    <?xml version="1.0" encoding="UTF-8" ?>
    <features>
     <feature uid="legacy-api" enable="true" >
      <flipstrategy class="org.ff4j.sample.strategy.MarketFlippingStrategy" >
        <param name="grantedMarket">country1-brand1,country1-brand2</param>
      </flipstrategy>
     </feature>
    </features>
    

    How can I switch between API dynamically based on the home controller brand/country inputs ?

    The "trick" to is pass the couple brand/country as a single variable market in the FlippingExecutionContext and this would help you to understand but here how it works.

    @RestController
    @RequestMapping("/{brand}/{country}")
    public class HomeController {
    
        private final ApiService apiService;
    
        public HomeController(@Qualifier("new-api") ApiService apiService) {
            this.apiService = apiService;
        }
    
        @GetMapping("/hey")
        public String hey(@PathVariable("brand") String brand,
                          @PathVariable("country") String country) {
            FlippingExecutionContext executionContext = new FlippingExecutionContext();
            executionContext.putString("market", brand + "-" + country);
            return apiService.whichApi(executionContext);
        }
    
    }
    
    interface ApiService {
        @Flip(name = "legacy-api", 
              alterBean = "legacy-api", 
              flippingStrategy = MarketFlippingStrategy.class,
              contextLocation = ContextLocation.PARAMETER)
        String whichApi(FlippingExecutionContext context);
    }
    
    @Component("new-api")
    class NewApiApiService implements ApiService {
        @Override
        public String whichApi(FlippingExecutionContext context) {
            return "NEW_API";
        }
    }
    
    @Component("legacy-api")
    class LegacyApiApiService implements ApiService {
        @Override
        public String whichApi(FlippingExecutionContext context) {
            return "LEGACY_API";
        }
    }