When we’re continuously integrating code changes, we may want to avoid prematurely exposing new behaviour to our users. A popular technique is to use Feature Toggles to turn a feature on based on a configuration setting. These are certainly useful, and are great for gradually rolling features out, but they involve introducing code into our systems that is not relevant to the system itself, and if poorly implemented can lead to us bloating our codebases with vapourware.
There is another technique that I think deserves more attention; I refer to this as Feature Triggers. The idea is to use an intrinsic part of your system to enable the new feature, rather than an extrinsic piece of configuration. The discipline lies in identifying the final change that will trigger the feature.
In his write-up of Feature Toggles, Martin Fowler describes a version of this technique that focuses on UI changes:
Release toggles are a useful technique and lots of teams use them. However they should be your last choice when you’re dealing with putting features into production.
Your first choice should be to break the feature down so you can safely introduce parts of the feature into the product. The advantages of doing this are the same ones as any strategy based on small, frequent releases. You reduce the risk of things going wrong and you get valuable feedback on how users actually use the feature that will improve the enhancements you make later.
If you really must hide a partly built feature, then the best way is to build all of it save the UI entry point and add that UI in a single release cycle. This way the non-ui code is fully integrated with everything else, but nothing is visible or used until you add the last bit at the end.
However, we needn’t restrict this technique to the UI. In particular, if you are practising Feature Pull, then the trigger should be the last piece of work in the chain, rather than necessarily a UI change.
Let me give an example.
I worked on a system that sold recorded music on line, and we wanted to start selling high-resolution tracks. The system had theretofore only supported a single audio format per track, usually 320 bps MP3, so the UI simply presented the customer with a ‘Add to basket’ button. Now we wanted to present them with a choice of, say, MP3 or FLAC, at different prices.
Our feature flow worked something like this:
- When a customer has a track as a FLAC in their library, then they can download and listen to it with a FLAC codec.
- When a customer has a track as a FLAC in their shopping basket, and they check out, it ends up in their library.
- When a customer on the website clicks to add a track to their basket as a FLAC, then the track ends up in their shopping basket as a FLAC.
- When a track is in the catalogue with a FLAC format, then the website shows that it can be added to basket as a FLAC.
- When we have negotiated FLAC rights for a track that is ingested, then that track ends up in the catalogue with a FLAC format.
Now, each of these steps could be implemented in turn, using the principle of fake it till you make it to drive out the interface and schema changes as we went along. Crucially, we maintained existing behaviour for existing data. In the case of the UI, this meant that when a track was available in a single format, we just showed an ‘add to basket’ button with format-agnostic behaviour, whereas a track with multiple formats triggered a slightly more complex UI whose buttons had content-specific behaviour. Notice that in this case the UI was not the last piece of the puzzle: it’s behaviour was triggered by upstream data.
Instead, the entirety of this new feature was triggered by enabling the new ingestion rules—which then enabled multiple formats in the catalogue—which then enabled multiple formats on the website—which then enabled tracks to be added to the basket with specified formats—which then enabled format-aware tracks in the library—which then enabled format-aware downloads. All through this we limited the vapidity of the feature by keeping the code branching to a minimum and by releasing continuously, which meant that new code was almost immediately in use in production.
I’m sure this technique is widespread, and I think it’s worth reminding ourselves that the trigger needn’t be in the UI: by combining this technique with Feature Pull, we can often do without feature toggles and write more focused code.