Article summary
Earlier this year, Ryan Abel wrote about Managing Multiple Releases in a Production Application. One of the strategies he discussed was using “feature flags” to manage when sets of features are released in production. I’ve found that feature flags work well when there’s a need to maintain backward compatibility with multiple versions of an external integration. In my case, it’s with a Bluetooth Low Energy (BLE) device, but the same would hold true for a remote web service API, etc.
Feature Flags
Pete Hodgson has an excellent article on Feature Toggles on Martin Fowler’s site. He describes how these toggles (or flags) allow you to “ship alternative codepaths within one deployable unit and choose between them at runtime.”
The article lists several categories of feature toggles:
- Release Toggles
- Experiment Toggles
- Ops Toggles
- Permissioning Toggles
I’m going to add my own category to that list: Capability Toggles (although I’m going to switch back to calling them “flags” from here on out).
Capability Flags
These types of flags are set based on the capabilities of an external integration point. Very often, they will be determined by the version number of whatever they’re integrating with.
Alternatively, when working with something that provides some level of discoverability, a version number isn’t strictly necessary. For example, the services and characteristics of a BLE device can be discovered and used to determine the remote device’s capabilities.
They can even be used for enabling/disabling functionality based on the version of the OS or hardware the application is running.
Capability flags will be long-lived. They stick around in the code for as long as backward compatibility is supported for the older version of the device/API. This could potentially be for the lifetime of the application.
Granular Features
I’ve found it’s better to create separate flags for each individual fine-grained feature. This is preferable even when they can all be lumped together in one large set (i.e., all of the features that require Version 2.0 of an external device).
By focusing the functionality associated with any particular flag, you can make sure your code is better documented. It will also be much more robust at supporting future rule changes when a feature becomes available.
Decouple Feature Availability
The logic that determines if a feature is available should be decoupled from the place where the flag is used. I like to implement separate classes or functions for each feature flag to encapsulate the rules for determining its availability. When the application’s state changes, the list of “rules” are invoked again to determine the current set of available features.
This way, the code that’s branching based on a feature flag isn’t concerned about why the feature is/isn’t available. That’s not its responsibility.
Example: Battery Level
Let’s take, for example, a mobile app that’s a companion to an external BLE device. The first version of the external device doesn’t provide any information about its current battery level to the app. This was a big request from users. The second version of the device added support for the standard Battery Service and Battery Level characteristic.
If the app is connected to a device that can provide the battery information, we want it to show a battery level indicator on a Dashboard screen. When connected to older devices, the indicator shouldn’t be visible.
To handle this, we’ll establish a battery_level_supported
flag. The rule for determining whether this feature is available or not will be the presence of the Battery Level characteristic in the set of discovered characteristics. When the app first connects to a device, it goes through the discovery process and updates the app’s internal representation of services and characteristics available on the device. If the Battery Level characteristic is in that set, the rule for battery_level_supported
adds it to the set of available features.
In the code that determines what should be rendered on the Dashboard, the set of available features is inspected. If it contains battery_level_supported
, the battery level indicator is rendered.
Conclusion
Maintaining backward compatibility with older versions of anything is difficult. Using granular, decoupled feature flags can be a great way to manage support for your application’s changing dependencies.