What are the different ways to use feature flag toggles in applications?
If you were to explain to a developer the exact things that should be done in order to get from nothing to a full feature-flag-toggled application, what would those steps be?
Feature flags are an engineering device that can be used to avoid long-lived branch and conflicts in product development. Here is how it can be used the context of an object-oriented language to help developers collaborate on a specific product feature while one handle a new version. This solution can also be used in non object-oriented contexts, provided a notion of “interface” exists. (cf. OCaml module system.)
For the purpose of illustration, we assume a tool presenting reports about data stored in a database. The code implements a DatabaseClient class used to perform requests. As the dataset grows, it becomes clear that some alternative data layout would improve the application performance. Therefore Alice will develop a new version of the DatabaseClient able to retrieve data from the structures with improved layout, while Bob will maintain the historical DatabaseClient.
With the following steps, Alice and Bob can collaborate on short-lived branches while minimising their conflicts.
Alice rename DatabaseClient to DatabaseClient_v1 and create a delegate class called DatabaseClient that uses an object DatabaseClient_v1 and implements an interface called DatabaseClientInterface. (If possible, this DatabaseClientInterface should be a code artefact but duck-typed languages not always support this.)
Bob reviews changes made by Alice in 1 and is aware that his maintenance job should happen on DatabaseClient_v1.
Alice introduces a new configuration flag in the application that governs the behaviour of the DatabaseClient delegate and implements a DatabaseClient_v2 placeholder, a class implementing the DatabaseClientInterface whose methods all throw a “Not implemented” exception.
After this, Alice and Bob can collaborate without explicit synchronisation, because code written in their respective iterations is subject to the DatabaseClientInterface. This minimises the risk of a conflict resulting from their concurrent work.
Iterations from Alice can be very short, like implementing a test, implementing a method, or even partially doing so, because in production, the code is not selected for use and does not need to be fully functional. The automated testsuite should be configured so that the DatabaseClientInterface always uses DatabaseClient_v1 while Alice can easily toggle to DatabaseClient_v2 when running the testsuite locally – or in a custom CI setup. Once everything is ready, a single commit can perform the change, by updating the configuration value governing the DatabaseClient delegate.
The steps are quite "easy", to move to a feature flag app you need basically two things:
The basic of feature flag is to turn them on/off, but quickly you'll wish to release a new feature in a ramp-up manner, for example: 1 server on 5 hosting the app have the feature "on" to start, you then turn on the feature on another server, until all servers have it "on".
This means you have to be careful about your feature being compatible with the app without it (extra column in DB for example).
Frameworks exists in various languages to avoid reinventing the wheel, the now un-maintained one from Etsy's have an interesting readme to explain how it works.
The embedded software world often uses build-time flags, in the app code itself (#define
/#ifdef
statements, for example) and/or in build tools configuration files (makefile
's, for example).
Build flags can be used, in similar manner, not only for features, but also for all kinds of code refactoring, migrations, debug support, etc). They allow committing in the integration branch partial or unverified changes without breaking the build or causing regressions in the features/projects already working in the branch. Excellent for handling point fixes alongside large/risky/slow progress changes (which would otherwise require a long-lived branch) in a continuous integration manner.
But in addition to verifying the already existing branch code for regressions, it's also possible to perform progress/stability verifications of the new code. For this the build-time flags need to be toggled.
One way of toggling the flags would be to use, in a separate verification pipeline of the CI system of the same branch (if it has support for such functionality), a patchfile toggling the flag - to be applied to a separate workspace prior to the build. A different set of artifacts would be built in this workspace and then verified.
Alternatively a long-lived feature branch can be pulled from the main integration branch, but the only change in this feature branch would be the toggled flag. Due to this tiny change the feature branch can be automatically sync'd extremely fast - practically shadowing very closely the main integration branch. A separate CI execution on this branch would not need a preliminary patchfile anymore. It'd be trivial to carry around such feature branch even for an extended period of time.
It may also be possible to create, in the main integration branch, new build artifacts which would really be just clones of the existing build artifacts but with the flags toggled. This way neither the preliminary patchfile nor the feature branch would be necessary to verify the new code, right in the main branch.