Over the past few months, we’ve been going through an iterative process of improving how we use feature flags.
Feature flags have long been a popular tool in software development and DevOps. For the uninitiated, their basic functionality is pretty simple. Feature flags are essentially “if-else” statements used to modify an application’s functionality without making a code change.
Many companies find feature flags useful for making changes in the application without having to wait for code changes. They’re a surprisingly powerful tool, as they open the door to many different options…
How we use feature flags at Seccl
At Seccl, we primarily use feature flags to continuously integrate features into our applications.
For example, we might have a new feature to implement that will take some time to build. We could keep all these changes in a feature branch from the main release until they are all tested and ready to deploy, but this often means ending up with a branch with a large number of changes. We would rather get the code merged into the main project as soon as possible, but not make that functionality available until it is ready for release.
Feature flags come to the rescue by allowing us to turn the feature off until we want to release it. This allows us to develop smaller parts of the new feature and test them in our QA environments but won’t impact production environments until we want to turn the feature on.
Getting the code merged in often with small incremental functionality fits in better with the trunk-based development that we tend to follow, which allows for continuous integration of our work.
Once a feature is ready to be released, we can also turn it on by making no-code changes. It’s as simple as going into a dashboard and setting the feature flag to turn on the feature.
The first implementation
We specifically use Optimizely React SDK to implement feature flags within our React application. This is achieved through wrapping the application in a OptimizelyProvider, which uses useContext, and then wraps an OptimizelyFeature component around the specific features that need to be toggled on or off.
A basic example of this straight from the Optimizely docs is:
We found that this method worked well for isolated “react” components but was problematic for other situations. For example:
-
If the feature flag wrapped multiple components throughout the application it became repetitive adding multiple OptimizelyFeature components
-
We use Redux for state management. This approach did not work immediately with the Reduct side of the application
-
The OptimizelyProvider was very “chatty “, as the request to Optimizely’s CDN was made repeatedly (effectively when parent props change), even though it’s a resource that does not update often
-
Ad-blockers and fire walls could block the request to Optimizely. This had a massive impact as it prevented feature flags from working, so users may have never seen the new feature without you removing the feature flag! This prevented us from fully utilising the feature flags functionality, restricting them to part of the continuous deployment process, and hiding work in progress.
The second implementation
The issues from the first implementation made us completely revaluate how we use feature flags.
The first issue we needed to resolve was to make the Redux side of the application aware of the feature flags status – for example, if we wanted to use a feature flag to change the behaviour of a selector.
We needed to inform the Redux store of the feature flag status. A way of implementing this was to dispatch an action from within the OptimizelyFeature component with the status of the feature flag, which could then be stored within the Redux store and made available to use in the Redux state management side of the application.
A simplistic example of this using a react hook within a component is:
This method did allow for use of feature flags within Redux, so it was a good step forward. However, the approach still had its drawbacks and was not ideal for several reasons:
-
The app state initially had no knowledge of feature flag status. Only after the Optimizely Feature had mounted would the action be dispatched, and the state updated
-
If you needed knowledge of the same feature flag in multiple parts of the application, you would need to add the code to dispatch the action from multiple components. This led to a lot of ‘code bloat’, and increased the risk of missing a component that needed to dispatch the action for a specific situation
-
When using isFeatureFlagEnabled, each OptimizelyFeature could only check the status of one feature blog
Third implementation
The issues from the second implantation combined with the massive issue of ad-blockers resulted in a third implementation.
We could have carried on using React SDK to get all the feature flag statuses for the application (using getEnabledFeatures rather than isFeatureFlagEnabled) and move this component to wrap the application at its root component. This would’ve solved the issue of multiple OptimizelyFeatures within the application and loaded the feature flag status into the central state faster.
However, this would not get around our main issue of firewalls and ad-blockers, so we looked for another option.
Within our architecture, our UIs (User Interfaces) are served by invoking lambdas. This allows us to make server-side requests before our UIs are rendered. We traditionally use this method to style sites for our clients using their branding, although we can of course make other requests at this stage.
We experimented by moving the Optimizely request here, and after some data manipulation, we passed the results to the UI. The UI then set the feature flags to the Reduct state as soon as a user logged in.
The benefits of this approach were:
-
By moving the request to Optimizely from the client side to server side, we were able to avoid any ad-blockers or firewalls preventing the request. This also meant we could cut down the number of requests to Optimizely – in fact, none were needed on the client side once we removed the OptimizelyProvider
-
Feature flag state was available immediately after a user logged in and so were available sooner in the applications load cycle
-
Feature flag state was available across the application, without us having to add extra code to components to dispatch actions for each feature flag. This also made it easier to know which feature flags were enabled for that environment at any time. This meant that we had one source of truth for both the React and Redux side of the application to use
Final thoughts
This iterative approach has been beneficial for us. There are always more improvements to be made, but that’s all part of the fun! For now, we’re happy to be in a position where we can utilise feature flags to their full extent.
You can also take a look at our open roles on our career page, and keep an eye on our blog for more engineering insights and company updates.