Hey 👋 I’m Carl, a Senior Software Engineer at TheFork in France.
In this article, I will explain how we came to create a custom feature flip engine for our B2B product called TFM (TheForkManager).
I will be saying “we” a lot because, of course, I did not work on this alone.
Restaurants use TFM (TheForkManager) to manage their online/offline reservations, customer database, services, floor plans. The restaurant employees, which we call restaurant users, can have different roles: Administrator, Manager, Waiter, that will give them access to more or less functionalities in the application. Restaurants subscribe to a client package/plan when joining the fork: Basic, Pro, Pro+, which also limits the list of available functionalities.
We have a legacy version of the application called TFM2. And the latest, all shiny and new, version called TFM3. In this article we focus on TFM3.
With the need to deploy a “light” version of the application for a given segment of restaurants, we needed to hide a lot of functionalities in the application. Relying on the current APIs would have introduced a lot of complexity in the code, drastically increasing the maintenance cost.
Introducing feature flips/toggles FTW! 🙌
But… what are really feature flips?
I won’t go into too much details, better articles exist that explain it very well already. Like Martin Fowlers article about feature toggles which has helped us tremendously to clarify the concept of features between all people involved on the project.
To me, it boils down to dynamically telling your application which parts should be visible or not when given a specific user context.
The front-end only needs an API to list all the features. Each feature having a boolean state, true
meaning the feature is enabled, else false
.
The front-end code will hold the logic expressing how to react in case the feature is not enabled, for example: display an unauthorized page, promote a package upgrade, just display nothing at all.
The logic expressing for which reason a feature is enabled, is only expressed by back-end code. As illustrated by the graph above, a feature can be enabled for a lot of different reasons:
- A user role permission handling system. For TheFork: is the user a restaurant owner, a manager, a waiter
- The client package/plan: Basic, Pro, Pro+, etc
- Rollout strategies: Open the feature progressively to specific segments
- Experiments: Proof of concepts for internal use only
The important thing, in my opinion, is to clearly identify what features exist and why they exist in the application.
Identifying our issue
At TheFork we have been working on TFM3 for years. Complexity has accumulated due to combined factors like working in feature teams without real governance on “how to do things” and not enough communication/documentations.
In the end, we were left with several transverse APIs with equivalent outcomes: displaying/hiding parts of the application but for different reasons.
Introducing yet another way of technically expressing the need to display or hide functionalities in the application would have brought havoc to the code base. The new logic would have intervened at the same places as all those different transverse APIs illustrated above.
Now we could have caved into shear laziness and implement the go-fast solution that would have made our future selves scream 😱. But instead, we decided to be bold, slammed down our fists on the table and started digging into how we could simplify it all.
Documentation as code
Now let’s get our hand dirty while explaining a bit more the technical specifics.
We wanted to exploit the capabilities of our stack as much as possible:
- All our code is in TypeScript
- We use GraphQL between the front-end and API
- We use type-graphql to generate GQL schema from TypeScript code
- We use graphql-shield to express rules to secure all GQL queries and mutations
Let’s see how each of these played an important role in the creation of a simple yet extensible feature flip engine.
Unifying the transport: the complete list of features with their status (enabled/disabled) would be accessible through a single getFeatures
GraphQL query.
Simplifying discoverability for developers: features are defined as a TypeScript enum with comments for each feature. IDEs easily pick up on these types so you get a very accessible documentation for which features exist.
Naming convention: To make sure we do not create features with names that we won’t understand when looking back in a few years, we introduced a naming convention:
domain_feature_subFeature_accessLevel
Only the domain
part is mandatory. In the screen above it only shows feature that control a big part of the application and thus only use the domain_feature
parts.
An example of a more specific feature would be:
offer_promotion_festival_dayByDay
This gives access to managing the festival (e.g. mothers day) promotions (-50%) day-by-day availabilities (time slots vs party size) for diners.
For an enum to be converted correctly to the generated schema using type-graphql you have to use a specific registerEnumType()
helper.
The getFeatures
query handler loops over each feature in the enum and executes a list of rules that are declared using graphql-shield.
The resolveRules
helper executes the graphql-shield rules that are declared for each feature.
The combination of rules associated with a feature will determine why the feature is enabled/disabled. Thus, defining what type of feature toggle it is.
From the above example:
authenticatedRule()
will make sure the user is logged in on the platform.- Finally, the
userHasCapabilityOnRestaurantRule()
will check the user permissions.
As each rule is a simple function, we can implement any kind of logic, for example integrating with external feature toggling systems like LaunchDarkly.
With all this, the front-end code is now able to pull the GraphQL schema from the API and thus propagate the types and documentation.
At TheFork we use React. The front-end calls the getFeatures
query once when loading the application and exposes it to all components through a specific provider.
Now each component can easily ask if a feature is enabled through: A higher order component, a hook or the provider which exposes some helper functions.
Ok, we have a nice technical implementation which can transport value and documentation from back to front. But another very important aspect of having feature toggles in an application is making sure that each feature makes sense for the product and how it will be marketed.
Sharing the knowledge
At TheFork, we migrated all our documentations to Notion.
To make sure that everyone in the company could, if they needed, know what features exist in TFM3 and why, we created a notion database re-listing all features that were declared in the enum
.
Company-wide alignment of the product is essential. Seems obvious when I write it like that, but in reality it can be very challenging to reach a common state of understanding between departments like IT, product or marketing, even though we are all working on the same product in the same company.
What’s next?
We managed to have a simple and extensible technical implementation for feature declaration between our front-end application and our back-end APIs. Not making it simple for developers only, but also helping the whole company to understand what exists through clear documentations.
For now the documentation on notion was created manually, but no one wants to maintain this kind of document while all these informations are already stored in the code and application DB. A good next step would be to automate the notion documentation generation directly from changes to the code and app DB.
Having a nice dashboard from which we can control the enabling of the features would also be great. I mentioned LaunchDarkly earlier. Others exist. We need to try and find the best solution for our needs.
Conclusion
As I write this article we haven’t yet battle tested the system we’ve put in place. The adoption of the new API is still ongoing. We have received positive feedbacks about several aspects like the API simplifications or the shareable documentation. But will it really be as useful as I hope… Only time, good measurements and feedback loops, will tell. And maybe another article coming up with the results. 🤭
The system is not perfect, but simple enough so that we can easily iterate or even throw it to the bin if something much better that we hadn’t considered comes along.
If I try to sum up everything we went through with this project into a catchy advice, I would say:
Strive for simplicity and challenge the status quo
Todays needs are not the same as yesterday, nor will they remain in the future. Follow KISS principles and have fun.
You’re still here?! 😮 Wow, thank you 👏
We are hiring at TheFork, come join an awesome adventure! 🙌