Back to Blog
Jovica Zoric

2 minutes read

Exploring vendor agnostic feature management with OpenFeature

Jovica Zorić

Chief Technology Officer

Feature flags have become a cornerstone of modern software development, empowering developers to modify system behavior without rewriting code. By placing conditional statements, usually if-else cases, around sections of code, developers can control the activation or deactivation of features in real time.

Feature flags are not just tools for developers; they have far-reaching benefits across various business functions. They enable continuous deployment, incremental rollouts, A/B testing, risk mitigation, and feature experimentation. Their versatility extends beyond the coding realm to impact sales, customer support, marketing, and more. While feature flags may appear straightforward to implement and comprehend, it’s important to understand that they introduce additional code and complexity to your existing codebase. If a feature is eventually made accessible to all users, the additional code associated with it should be removed.

In this blog post, we’ll explore the transformative power of feature flags and introduce OpenFeature, a vendor-agnostic solution designed to work with any feature flag management tool or in-house solution.

Alright, let’s kick things off. In the vast sea of tools and software options, we have contenders like LaunchDarkly, Rollout, ConfigCat, Optimizely, Toggled, DevCycle, Unleash, and the list goes on. Personally, I lean towards standards and the freedom to experiment with various providers and solutions until I discover the one that best aligns with my needs. This is where OpenFeature steps in, as it allows you to explore different providers seamlessly.

Use case

Let’s delve into a practical use case. We aim to leverage feature flags to determine if a user, identified by their email address, has access to a new feature. I’ve chosen three providers—flagd, ConfigCat, and DevCycle—but for a comprehensive ecosystem overview, feel free to visit this page.

The example is written in Quarkus  and you can find the source code here.

flagd

Let’s create a simple API for our use case.

@Path("/api")
public class PdAPI {

   @Inject()
   @FlagdQualifier
   OpenFeatureAPI openFeatureAPI;

   @GET()
   @Path("/check/{email}")
   @Produces(MediaType.APPLICATION_JSON)
   public Response checkFeature(@PathParam("email") String email) {
       final var client = openFeatureAPI.getClient();
       client.addHooks(new PDTrackerHook());

       var ctx = new MutableContext();
       ctx.setTargetingKey("isfromproductdock");

       ctx.add("Email", email);

       if (client.getBooleanValue("isfromproductdock", false, ctx)) {
           return Response.ok(new FeatureFlagResponse("This feature is enabled! User is from ProductDock.")).build();
       }
       return Response.ok(new FeatureFlagResponse("This feature is disabled! User is not from ProductDock.")).build();
   }
}

record FeatureFlagResponse(String message) {}

We have a simple API with a GET method that, depending on the email provided, will return a message if the feature is enabled or disabled. To be able to inject and use OpenFeatureAPI, we need to create an instance and set our flagd provider. Don’t mind the Qualifier annotations, we will use different Qualifiers to distinguish between different implementations.

@ApplicationScoped
public class FlagdAPI {

   @Produces
   @FlagdQualifier
   public OpenFeatureAPI getApi() {
       final OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance();
       openFeatureAPI.setProvider(new FlagdProvider());
       return openFeatureAPI;
   }
}

Now that we have our instance in place, we need to run flagd. Visit this link to explore different installation options and their documentation. For this demo, we just run a Docker container:

docker run --rm -it --name flagd -p 8013:8013 -v $(pwd)/flagd-docker:/etc/flagd ghcr.io/open-feature/flagd:latest start --uri file:./etc/flagd/flagd.json

and provide the following config:

{
"flags": {   
"isfromproductdock": {
     "state": "ENABLED",
     "variants": {
       "on": true,
       "off": false
     },
     "defaultVariant": "off",
     "targeting": {
       "if": [
         {
           "$ref": "isFromProductDock"
         },
         "on",
         null
       ]
     }
   }
 },
 "$evaluators": {
   "isFromProductDock": {
     "in": [
       "@productdock.com",
       {
         "var": ["Email"]
       }
     ]
   }
 }
}

It’s easy to understand, and flagd docs are awesome.

After running the application, we can use curl to test our API:

curl http://localhost:8080/api/check/example@productdock.com
or
curl http://localhost:8080/api/check/example@gmail.com 

How difficult is it to change to another provider? Let’s try ConfigCat.

ConfigCat

We created a ConfigCat account and an SDK key that we will be using in our configuration.

Here is what the flag looks like in ConfigCat.

Exploring Vendor-Agnostic Feature Management with OpenFeature

Next, we need to configure the OpenFeatureAPI instance to use ConfigCat as the provider.

@ApplicationScoped
public class ConfigCat {
   @ConfigProperty(name = "configcat.sdkkey")
   public String configCatKey;

   @Produces
   @ConfigCatQualifier
   public OpenFeatureAPI getApi() {
       final OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance();
       var configCat = ConfigCatProviderConfig.builder()
               .sdkKey(configCatKey)
               .build();
       openFeatureAPI.setProviderAndWait(new ConfigCatProvider(configCat));
       return openFeatureAPI;
   }
}

Finally, we change the qualifier in our PdAPI so that our DI container knows which OpenFeatureAPI instance to use.

@Path("/api")
public class PdAPI {

   @Inject()
// @FlagdQualifier
   @ConfigCatQualifier
   OpenFeatureAPI openFeatureAPI;

   @GET()
   @Path("/check/{email}")
   @Produces(MediaType.APPLICATION_JSON)
   public Response checkFeature(@PathParam("email") String email) {
       final var client = openFeatureAPI.getClient();
       client.addHooks(new PDTrackerHook());

       var ctx = new MutableContext();
       ctx.setTargetingKey("isfromproductdock");

       ctx.add("Email", email);

       if (client.getBooleanValue("isfromproductdock", false, ctx)) {
           return Response.ok(new FeatureFlagResponse("This feature is enabled! User is from ProductDock.")).build();
       }
       return Response.ok(new FeatureFlagResponse("This feature is disabled! User is not from ProductDock.")).build();
   }
}

record FeatureFlagResponse(String message) {
}

Everything else stays the same.

DevCycle

For DevCycle, it’s the same story, almost. They use “email” instead of “Email” for their user object, but that’s an easy fix.

Before we continue, you should have a DevCycle account and your SDK key.

The following image shows the DevCycle UI for defining targeting rules.

Exploring Vendor-Agnostic Feature Management with OpenFeature

Once again, we configure the OpenFeatureAPI instance, but now with the DevCycle provider.

@ApplicationScoped
public class DevCycle {
   @ConfigProperty(name = "devcycle.sdkkey")
   public String devCycleKey;

   @Produces
   @DevCycleQualifier
   public OpenFeatureAPI getApi() {
       final OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance();
       DevCycleLocalOptions options = DevCycleLocalOptions.builder().build();
       DevCycleLocalClient devCycleClient = new DevCycleLocalClient(devCycleKey, options);

       // This is wild. Should be handled by the client.
       // https://github.com/DevCycleHQ/java-server-sdk/pull/111/files/4e21dc9a8f7d5d4d063528b355fc5c6125d9c78b#r1381707824
       for (int i = 0; i < 10; i++) {
           if (devCycleClient.isInitialized()) {
               break;
           }
           try {
               TimeUnit.MILLISECONDS.sleep(500);
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
       }
       openFeatureAPI.setProvider(devCycleClient.getOpenFeatureProvider());
       return openFeatureAPI;
   }
}

We change the Qualifier and the email field casing for the context.

@Path("/api")
public class PdAPI {

   @Inject()
//  @FlagdQualifier
//  @ConfigCatQualifier
   @DevCycleQualifier
   OpenFeatureAPI openFeatureAPI;

   @GET()
   @Path("/check/{email}")
   @Produces(MediaType.APPLICATION_JSON)
   public Response checkFeature(@PathParam("email") String email) {
       final var client = openFeatureAPI.getClient();
       client.addHooks(new PDTrackerHook());

       var ctx = new MutableContext();
       ctx.setTargetingKey("isfromproductdock");
//       ctx.add("Email", email);
       ctx.add("email", email);

       if (client.getBooleanValue("isfromproductdock", false, ctx)) {
           return Response.ok(new FeatureFlagResponse("This feature is enabled! User is from ProductDock.")).build();
       }
       return Response.ok(new FeatureFlagResponse("This feature is disabled! User is not from ProductDock.")).build();
   }
}

record FeatureFlagResponse(String message) {
}

Hooks

You might have noticed the addHooks code. I like the idea of ​​tracking, and the hooks are a perfect place to set this up. I haven’t done anything special, just wanted to see how it behaves.

public class PDTrackerHook implements BooleanHook {
   private static final Logger LOG = Logger.getLogger(PDTrackerHook.class);
   @Override
   public void after(HookContext<Boolean> ctx, FlagEvaluationDetails<Boolean> details, Map<String, Object> hints) {
       LOG.info(details.getFlagKey() + " : " + details.getValue());
   }
}

More on hooks, visit this page.

What’s next

As you can see in the examples above, OpenFeature gives you flexibility with feature management providers. The team behind OpenFeature is actively working on further improvements, and I’m eagerly anticipating the exciting development ahead.

While this article primarily focuses on the server-side implementation, it’s important to note that OpenFeature also has robust client-side capabilities. If you’re keen to dive in, I recommend beginning with exploring their documentation. From there, feel free to explore any direction that aligns with your goals and preferences.

Jovica Zoric

Jovica Zorić

Chief Technology Officer

Jovica is a techie with more than ten years of experience. His job has evolved throughout the years, leading to his present position as the CTO at ProductDock. He will help you to choose the right technology for your business ideas and focus on the outcomes.


Related posts.