Streamline feature flag management: Automate removal with OpenRewrite & Moderne

Tim te Beek
March 31, 2024
Flag pins on a world map representing finding feature flags in source code

Key Takeaways

Feature flags are a great enabler for teams and companies; they allow you to run quick experiments by only showing a new feature to a subset of users. You can then measure the impact of each of these features to make an informed decision on the best path forward for all users. Furthermore, they allow you to decouple deploying a feature from when it’s enabled—which reduces risk and allows you to roll out features when they are convenient to do so.

With all that being said, feature flags have some inherent risks. Consider the situation where a feature flag is added and then just left there without ever being cleaned up. A year or two down the line when people don’t remember as much about the code, the flag may be removed or toggled, and then a critical feature stops working. In one extreme case, an investment firm collapsed after it lost $440 million during such a feature flag-related mishap

Read on to learn how you can streamline your feature flag management and efficiently purge related code debt using OpenRewrite open-source recipes and automated refactoring at scale with Moderne.

A practical example of feature flag experimentation 

Let’s say that we want to run an experiment to see if search results returned by an AI model are better than a more traditional ranked search. We could define a feature flag key, enable that feature flag for a certain segment of users, and track whether they are more likely to opt for the results presented.

Feature flags are often enabled through integration with a vendor SDK, to check at runtime if a feature is enabled in a particular context. Based on the response from the vendor API call, folks might see the result of either side of a conditional.

The below example uses the SDK from our friends at LaunchDarkly

Figure 1. Toggle between search implementations for the active user

On line 15 in the above example, you can see we call out to LaunchDarkly to determine whether or not AI search should be enabled for this particular user. The same user will always receive the same search options so they don’t get different results during different page loads.

Note that in this simplified example, we’re not showing how success for either search implementation is defined or tracked. There would be at least a couple factors—such as response times, number of results, how often folks go for one of the presented results, or if they refine their query to try again. These are all part of the experiment and would be needed to determine which conditional block should be retained in the future. 

Challenges of feature flag management are solvable with automation 

In practice, feature flags might well not be isolated to a single point of use as seen here, but rather span multiple methods, services, or even teams. For instance: when two teams collaborate on a new feature, one team might be done well ahead of another team. That first team can then put the feature behind a feature flag and deploy, with the feature only enabled once the second team catches up. This ensures teams can continue to deploy and build upon changes, without coordination, merge conflicts, or risks to deployments.

However, instances have occurred where feature flags were left permanently enabled or disabled, with their conditionals forgotten and not cleaned up. This oversight might leave developers confused about whether certain code paths are still relevant or maintained. At its worst, it can cause severe breakdowns if these flags are mistakenly toggled.

One criticism leveled against the use of feature flags is the need to have and maintain parallel code paths for them, as the necessary cleanup and pruning require manual work long after an experiment has run. We think that's a shame, and we have developed automation to make working with feature flags easier.

Building feature flag confidence: From initial tests to broad rollouts 

Usually, experiments start small. A feature flag can be enabled for a single developer, then a team, then colleagues before rolling out to early adopters. With each step, you can measure, learn, and adapt as necessary—or even choose to abandon an experiment early. That early feedback avoids spending a lot of effort on a feature that's just not performing well with users.

Once you grow more confident that your experiment is performing well, you might want to roll it out more broadly or enable it by default. In the code example above you might have noticed the call to boolVariation takes a fallback value as the last argument (in the above example, we choose to disable AI search if we can’t reach the feature flag service). This value determines what happens when the vendor API can not be reached at runtime. At some point, you might want to flip that fallback value anywhere the feature flag is used. But you wouldn't want to revisit all usages of a feature flag by hand to do so, for the rare cases where the API call might fail.

At Moderne, we have developed an OpenRewrite recipe (see Figure 2) that you can run to change the fallback value for a particular feature flag. You can run this open-source recipe in any of the ways you've come to expect, including OSS plugins for Maven and Gradle, your IDE, the Moderne CLI, and the Moderne Platform.

Figure 2. A declarative YAML recipe list to change feature flag default values

You can expand this declarative recipe list as features progress and run it at scale through Moderne to affect all services that might use these flags. You could also call out your vendor API to dynamically determine the new default fallback values based on the feature flag maturity. That way, you can continuously update the use of feature flags across your organization.

The easy path to phasing out feature flags

Once your experiment runs to completion, it's time to revisit the feature flags in your source code and phase out the associated conditionals. Another recipe (see Figure 3) allows you to choose which side of a conditional you want to retain going forward and clear out any conditionals or fields no longer needed.

Figure 3. A declarative YAML recipe list to phase out feature flags, associated conditionals and fields

Running this recipe against our example above runs a series of steps to phase out the feature flag, and associated conditional and fields.

  1. First, we replace the boolVariation method call with the replacement value, such as true or false.
  2. Then we delegate to SimplifyConstantIfBranchExecution to retain only the active code path.
  3. Finally, we clear out any unused local variables and fields with the respective existing recipes.

This delegation to existing OpenRewrite recipes made this an easy recipe to implement while relying on robust existing recipes for the majority of the code changes seen in Figure 4 below.

Figure 4. Phase out a feature flag and retain one side of the conditional

Notice how we're completely removing the if statement while retaining the call to the new AiSearchService, and clear out the fields associated with the call to LaunchDarkly, and the more traditional LuceneSearchService.

As we've seen before a list of such recipes can be generated from a call to your feature flag vendor API, and run at scale through Moderne for a thorough clearing out of outdated feature flags across the board.

Flexibility for your stack: Adapting OpenRewrite recipes for any SDK 

The above open-source feature flag recipes work out of the box for the LaunchDarkly SDK to complement the SDK version migration recipes we have there. But we've also heard from developers who have their own internal library that wraps the LaunchDarkly SDK, or even use a different SDK entirely, such as the one for Open Feature. Moderne allows the same recipe to be used with different SDKs through an additional optional method pattern argument.

Figure 5. A declarative YAML recipe list to phase out feature flags using an alternative SDK.

Notice how the methodPattern matches an internal method. The only requirement here is that the first argument is the feature key.

Automation is the recipe for feature flag management success 

With the above automations, we can eliminate the pain of working with feature flags and unlock their value for running quick experiments without the associated technical debt. It's yet another case of how you can use existing OpenRewrite recipes to help maintain your code as part of your everyday work. What will you build next? Learn more about finding LaunchDarkly feature flags through OpenRewrite or the Moderne Platform.

Also, please join the OpenRewrite Slack or Discord channels to get involved in building auto-refactoring automation for your organization.

And if you want to learn more about automating mass-scale code changes in your organization, contact us at Moderne.

Colorful, semi-transparent Moderne symbol breaking apart like a puzzleColorful, semi-transparent Moderne symbol breaking apart like a puzzleColorful, semi-transparent Moderne symbol breaking apart like a puzzle

Back to Blog

Colorful, semi-transparent Moderne symbol breaking apart like a puzzleColorful, semi-transparent Moderne symbol breaking apart like a puzzleColorful, semi-transparent Moderne symbol breaking apart like a puzzle

Back to Engineering Blog