First Foray For Feature Flags
Earlier this year, Shapeways released a new navigation system with a simple top navigation augmented by a left side navigation. This replaced a complex system of tabs in a top navigation only. Historically, Shapeways releases features and updates to every user at the same time. Invariably, manual and automated testing can only go so far; we rarely ship something that is perfect in initial release. With a complete overhaul of our navigation, we decided to release the feature to our employees and beta users first, then deploy to the remaining users two weeks later once we had ironed out some bugs. While the concept of a feature flag is nowhere near new, this was our first experiment with it at Shapeways.
Configuration of the Flag
Our first step was to develop a trigger for the new navigation. We wanted to keep it simple, as this code was temporary and would be removed from the codebase following public release of the new navigation. While this feature flag code was temporary, we continue to use this pattern to trigger other feature flags until we can design a microservice that would house this logic.
For the navigation system flag, we used an existing function that checked if the user was in any of a predefined set of groups. The list of groups was stored as an array in a high level configuration file for simplicity. We also had a flag in that same configuration file that would force the feature if set to true. This was helpful in automated testing and allowed for a quick deploy, as simply setting the flag to true caused the new navigation to render for all users. In pseudocode, this is what our flag looked like:
If (userIsInOneOfGroups(navigationWhiteListGroupArray) or
forceNewNavigation == True) {
renderNewNavigation = True
} else {
renderNewNavigation = False
}
While not the most elegant solution, the fastest way for us to implement conditional template rendering within components was to create a separate render method that we called renderLegacy
, which was called by the top level Navigation
component to render itself as well as the subcomponents (in this case Cart
and Private Messages
). With just two components and only one level down in the component tree, we felt comfortable implementing this temporary solution. With more complexity and depth, we would have considered forking the components so that there would be two distinct and namespaced component render trees for new navigation and old navigation. The top level component would then choose the path and the subcomponents would render normally with their inherited render
method present in all of our components. Forking completely would allow for quick cleanup and removal of old components when they are no longer needed. In lieu of this, we rearranged the methods in our existing components so that they would be easy to clean up later with a specific TODO string.
TODO’s
While TODO’s can be a mess in your codebase (and better handled with an issue tracker) we used TODO’s in this project using a specific TODO string with a linked Jira issue. This means that when it came time to remove the temporary code flags, no longer used methods, and entire files — the html templates and respective CSS modules — we could just search for the specific TODO string and quickly delete the old code.
Namespace and Separate
One major mistake we made during this project was not properly namespacing and separating one of the subcomponents in the decision tree. Our cart modal had been recently updated and met all of our current coding standards, including a proper Blocks, Elements and Modifiers (BEM) module. We initially decided not to reinvent the wheel and reuse the cart component for both old and new navigation. However, this proved harmful as positioning of the modal in new navigation and old navigation used incompatible methods. The old navigation assumed a fixed height and therefore used a hardcoded top position to center the cart icon and position the open modal. Going forward we wanted to ability to quickly change the height of the navigation and possibly vary it between mobile and desktop via a media query. Therefore, the new navigation needed a proper vertical align method. My first thought was to leverage the namespace of the parent module (where the new navigation was properly namespaced) and nest overriding classes inside the parent namespace. At the time I did not realize this is against BEM principles as its modules should only contain classes with their own namespace. The team agreed that it would slide in code review for the beta release since it would be cleaned up quickly following public navigation release.
Here we can see a entity relationship diagram (ERD) with the components and their respective templates and BEM/SCSS modules:
In addition to suboptimal code quality, this mistake also meant code clean-up would be more difficult. If the component had been separated properly, we could have just deleted the logic used to branch the render tree and delete the code and files in the unused branch. Instead, I needed to integrate the overriding classes into the base css. If you take away one thing from this post, it’s that namespacing and separating your render logic is crucial.
The Beta Deploy
With the exception of the aforementioned cart modal, we could be reasonably assured any bugs in the new Navigation experience were not affecting our old navigation due to a proper separation. Our beta group is only 0.2% of our users and if any major problems arose we had instructed our customer service group to ask users to leave the beta group and continue using the old navigation. Having only the beta users and Shapeways employees groups seeing new navigation allowed our Product Managers to show off the feature and our UX group to continue to iterate the design and experience. We could iron out bugs without exposing all of users to the new experience. And sure enough, not even an hour after beta release, we had a report of a bug:
This was an easily fixable issue related to an empty href of a link for our shopowners. However, this bug would have been quite frustrating to our tens of thousands of shopowners if the link to their sales page was not working. By releasing only to our beta users, we significantly reduced our exposure to the bug.
The “Dirty” Deploy
Creating a separate render branch with a feature flag gave us an easy deploy strategy: set the flag to true, pushing every user to the new navigation. The “dirty” part of this is the fact that we had an entire vestigial render branch and unused logic. However, since the feature was deployed and public, the project was “launched” from a Product perspective. Meanwhile we could take our time cleaning up and deleting code.
Future Work and Other Thoughts
With the success of this project, the team has realized the benefits of a phased deploy. As mentioned, with a more complex render component tree we would have completely separated the two render trees instead of simply having different HTML templates and BEM modules rendered by existing components. This would further simplify our clean-up and reduce our complexity in a rollout release project. While we understand that namespaced BEM/CSS and javascript modules will temporarily increase the size of our static assets, we feel the tradeoff is acceptable for the greatly reduced complexity.
One of our long term goals is to create a microservice that would house the feature flag. Given the necessary information such as user id, feature, etc. it would return what feature or features the user would have access to, centralizing and simplifying our logic for conditional features in the future.