Dieser Artikel ist auch auf Deutsch verfügbar

As a rule, we don’t just write software for the sake of it, but to support one business function or another and thus bring about added value. This added value however only comes to fruition when the software is actually actively used. In times of centralized web- and software-as-a-service applications, that means that we rely on continuous delivery in many projects. The goal here is to minimize the time from idea to use in production, and ideally to immediately adopt every change.

Alongside many others, the question of how we want to deal with new features arises. How do we enable a feature before it becomes visible to everyone? How do we coordinate features across multiple systems? What impact on performance and stability does the new feature have? Is the new feature really error-free?

Many of these questions can be resolved by establishing an optimal quality process before deployment to production. However, this delays the point in time when the new code can provide added value. An alternative approach is the use of feature flags, also known as feature toggles or feature controls. However, even with their use we should not and must not forgo a good quality process. Rather, the two approaches complement each other. Feature flags reduce the risk during a deployment and, as we will see, have other advantages as well.

Below we will look in detail at what a feature flag actually is, which approaches they allow us to pursue, and how they can be applied in Java with the three libraries FF4j, Togglz, and Unleash.

What is a feature flag?

At its core, a feature flag is a logical value that we query in the code and can then react to. Let’s assume that our new feature is to display below one article a list of recommendations of other articles. The feature has been successfully developed, but is not to be activated yet as we have to wait for a marketing campaign to be ready. We now have two options. Either we leave the feature in a branch of the version control system that is not to be deployed, wait until the campaign starts, and then merge and deploy the code at the correct moment, or we secure the feature with a feature flag. In the latter case, the lists will only be shown when the feature flag is activated.

But feature flags can also be used for large or risky refactorings. At the corresponding point we query the feature flag and depending on its status call either the old or the new code. Theoretically, we can also call both code paths and compare the results with one another. In the case of refactoring, they should be identical.

Up to now we have had the idea of creating feature flags ourselves and adding them at the appropriate point in the code with a Boolean variable. This however would mean that to change the feature flag a change in the code and a new deployment were also required. Although this is possible, it would rule out a whole range of possible uses.

The next step is therefore to make the feature flag configurable. In this way, at the start of an application, it can be decided which features are active. With this mechanism I can activate a new feature in a test environment, so that it is then enabled, and at the same time deploy it after production. As the feature is not yet enabled there, the code lies dormant without being called.

Feature flag libraries go one step further though. I can use them to determine with various strategies the state of a feature flag at runtime. For this purpose, when querying a feature flag a feature context, for example with information about the users, is additionally evaluated. The state of the feature flag can therefore be individually determined for the specific iteration.

With the principle of feature flags hopefully now clear, in the next step we will consider some concrete applications.

Possible uses for feature flags

As already indicated, there are various uses for feature flags. These can be crudely split into four categories:

  • release,
  • operation,
  • permissions, and
  • experiments.

The categories do not refer to the concrete use in the code, but rather to the goal that we want to achieve. In turn, the goal determines for every category different requirements on how dynamically we have to determine the status of the feature flag and for how long such a feature flag has been in our code base.

In the case of release feature flags, the primary intention is the decoupling of the release of a new feature from the time of its deployment. On the one hand, this approach allows us to deploy code that is not 100 percent fully developed. Because it is protected by a feature flag, it will never be executed. On the other hand, it allows features to be deployed that are ready but which for other reasons are not yet to be used.

As already stated, both these approaches can be considered alternatives to branches in the version control system. The advantage of feature flags is that the new code has already been integrated. Merge conflicts resulting from long branches are therefore excluded.

This form of feature flag is generally only found very briefly in our code base. As soon as the features are activated, the feature flags can be and should be removed. They also do not need to be highly dynamic. For the activation of such a feature flag we can even as a rule use a new deployment, so do not need the ability to configure at runtime.

Feature flags can also be useful for the operation, for example for switching off parts of the application at runtime in order to ensure performance or stability. Alongside a specific kill switch, we can also apply this principle to a gradual roll-out of a new feature. To this end, we only activate the new feature for a specific percentage of queries or users and observe the impact. If the application continues to run smoothly, we can continue to increase the percentage until eventually everyone can use the feature. This can of course also be achieved in different ways, for example with the load balancer.

Another possible use of feature flags during operation is for a maintenance mode. This is especially helpful when a system that we have to interoperate with is unavailable for a specific length of time. We can then place our application, or at least those parts of it that call the other system, into maintenance mode and thus avoid erroneous calls and log messages that may trigger alarms.

The dwell time of these feature flags in the code varies from short to long term. Feature flags for gradual roll-outs are removed immediately after deployment. Kill switches or the maintenance mode in contrast presumably remain in the code forever. In terms of dynamics, such feature flags are found in the middle of the spectrum. As a rule they are not changed statically during a deployment, but at the same time do not all need to be evaluated with every request.

Permissions are generally created by means of a unique concept. It is however sensible to use feature flags at this point and to check their status with a permission. This then allows us for example to activate a feature for a specific country and to allow early access. Other features are also possible, such as the blocking of users for example due to local regulations, the possibility of allowing users to test new features via opt-in, or offering users extended interfaces based on their experience.

These feature flags require a high level of dynamics, as information about the current users is always required for the evaluation of the status. They also remain in the code base for a long time – at least as long as the feature that they secure exists.

Feature flags can also be used for experiments within an application. They are enormously helpful for A/B testing. Users of an application are assigned to one of two groups. One group sees version A, the other version B. We can now ascertain which of the two groups better supports the goal we are trying to achieve and thus which of the two versions we should keep in the future.

Such feature flags are usually only present in the code in the short to medium term, specifically for as long as the experiment runs. They require however a high level of dynamics, as every request received can belong to a different user and so the code path can be a different one.

Having considered what feature flags are and how they can be used, we now look at how we can use them in Java with the help of libraries. We start with FF4j.

FF4j

FF4j is now over five years old and offers above all a big selection with regards to the persistence of the feature flags. But before we come to this, we should first take a look at the actual program interface.

The main entry point in FF4j is the eponymous class FF4j. Within our application we require an instance of this in order to check with the assistance of the check method whether a feature flag is active or not. In Listing 1 it can be seen how we generate such an instance, register two feature flags, and then query their status.

package de.mvitz.featureflags.ff4j;

import org.ff4j.FF4j;

public class FF4jExample {

    public static void main(String[] args) {
        FF4j ff4j = new FF4j();
        ff4j.createFeature("mein-feature-a", true);
        ff4j.createFeature("mein-feature-b", false);

        System.out.println(ff4j.check("mein-feature-a"));
        System.out.println(ff4j.check("mein-feature-b"));
    }
}
Listing 1: Configuration and Use of FF4j

When a feature flag is active, FF4j checks in a second step whether the current user has the necessary permissions. FF4j delegates the determination of the permissions to an implementation of AuthorizationsManager. This implementation can then refer for example to Spring Security in order to determine the roles of the user.

In the third step, FF4j then additionally checks by means of a FlippingStrategy whether the activated feature is actually active for exactly this request. In this way it is possible to implement strategies like a gradual roll-out or a date-based launch without requiring further infrastructure. Listing 2 shows how we program a feature that becomes active on December 24, 2020, at 12 noon.

package de.mvitz.featureflags.ff4j;

import org.ff4j.FF4j;
import org.ff4j.core.Feature;
import org.ff4j.core.FlippingStrategy;
import org.ff4j.strategy.time.ReleaseDateFlipStrategy;

public class FF4jExampleFlippingStrategy {

    public static void main(String[] args) {
        final FlippingStrategy strategy =
            new ReleaseDateFlipStrategy("2020-12-24-12:00");

        final Feature feature = new Feature("mein-feature");
        feature.setEnable(true);
        feature.setFlippingStrategy(strategy);

        final FF4j ff4j = new FF4j();
        ff4j.createFeature(feature);

        System.out.println(ff4j.check("mein-feature"));
    }
}
Listing 2: Date-based Launch by Means of FF4j

FF4j has a number of other strategies in addition to this one and through a separate implementation of FlippingStrategy also allows the implementation of strategies otherwise not available.

In order to share the status of our feature flags between multiple applications or instances of our application, FF4j allows us to select how this is to be persisted. For this purpose, FF4j already offers us more than ten implementations of the FeatureStore interface for widely deployed databases. In addition, there is also a web interface where we can see the status of our feature flags at runtime and change them.

Togglz

Although Togglz is conceptually largely identical to FF4j, the APIs for querying the feature flags differ significantly. Togglz relies on Java enum as its primary means. In Listing 3 we can see the equivalent to the FF4j version in Listing 1.

package de.mvitz.featureflags.togglz;

import org.togglz.core.Feature;
import org.togglz.core.annotation.EnabledByDefault;
import org.togglz.core.context.FeatureContext;
import org.togglz.core.context.StaticFeatureManagerProvider;
import org.togglz.core.manager.FeatureManager;
import org.togglz.core.manager.FeatureManagerBuilder;

public class TogglzExample {

    public enum MyFeatures implements Feature {
        @EnabledByDefault A,
        B;

        public boolean isActive() {
            return FeatureContext.getFeatureManager()
                .isActive(this);
        }
    }

    public static void main(String[] args) {
        final FeatureManager featureManager = FeatureManagerBuilder
            .begin()
            .featureEnum(MyFeatures.class)
            .build();
        StaticFeatureManagerProvider.setFeatureManager(featureManager);

        System.out.println(MyFeatures.A.isActive());
        System.out.println(MyFeatures.B.isActive());
    }
}
Listing 3: Configuration and Use of Togglz

Through the use of enums for the checking of the feature flags, small differences in use emerge. Firstly, at the points where the check is to be undertaken we no longer need access to a concrete instance of a class, but can use the enum directly, effectively statically. Secondly, the programming interface is a tiny bit more typed and it is no longer possible to query a feature flag that doesn’t exist.

Other than that the construction is relatively similar to FF4j, only with different names. For the checking of permissions the interface UserProvider is implemented, and for strategies ActivationStrategy is used. As with FF4j, Togglz is delivered with a few strategies and the persistence is configurable. Togglz also offers a web interface for administration.

Unleash

Alongside FF4j and Togglz, Unleash is a third representative that I would like to mention here. In terms of the programming interface Unleash is virtually identical to that of FF4j. Via the Unleash interface we can query the status of a flag by means of the isEnabled method (see Listing 4).

package de.mvitz.featureflags.unleash;

import no.finn.unleash.DefaultUnleash;
import no.finn.unleash.Unleash;
import no.finn.unleash.util.UnleashConfig;

public class UnleashExample {

    public static void main(String[] args) {
        UnleashConfig config = UnleashConfig.builder()
            .appName("unleash-example")
            .unleashAPI("http://....")
            .build();
        Unleash unleash = new DefaultUnleash(config);
        System.out.println(unleash.isEnabled("mein-feature-a"));
    }
}
Listing 4: Configuration and Use of Unleash

In comparison to the previous two libraries mentioned, Unleash has three advantages. The first is that Unleash was not written as a feature flag library. Instead, the persistence and the admin interface take center stage. Unleash is operated as a web application that provides a programming interface with which the library communicates. Unleash itself is open source and can therefore be hosted without cost. There is however an Enterprise version with additional features and also a software-as-a-service version.

The second advantage results from the fact that Unleash has been conceived as a service. Alongside the Unleash client for Java, clients are available for nearly all currently relevant programming languages. We can therefore share feature flags across multiple applications and only need one central interface for their administration.

The third advantage is that GitLab now also offers a feature flag feature. Feature flags can therefore be managed in GitLab and then queried from the application via the Unleash API. In this scenario we don’t even need to host the Unleash service.

Even though Unleash is in my experience not as common as FF4j or Togglz, it is worth considering at least these three libraries when choosing one.

Conclusion

In this article we learned about feature flags. In addition to the underlying principle, we also considered use cases in which they can support us. The four categories release, operation, permissions, and experiments are differentiated above all in the dimensions length of existence of the feature flags and the degree of dynamics required to determine the status.

We then looked at in my opinion the currently two most popular libraries for feature flags in Java, which differ predominantly in their programming interfaces. Unleash offers a third option. This differentiates itself above all through a dedicated service for persistence and administration, ready-made clients also for other programming languages, and out-of-the-box support by GitLab.

It goes without saying that not every application needs to use feature flags. And even where we do use them, we do not have to use them for all four of the categories listed above. But when the advantages are clear, one of the libraries should be selected and applied for the specific context. I would personally not recommend developing an in-house solution.

TAGS

Comments