Feature flags are often touted as a tool for feature development and streamlining release processes, but did you know that you can use feature flags for much more than feature toggling and A/B testing? In this new series, we will explore some of the many ways LaunchDarkly uses feature flags to maintain various aspects of its own product.
In the last year at LaunchDarkly, our engineering team has grown immensely, and so has our product. As we’ve continued to add more and more powerful capabilities for enterprise teams to leverage against their unique needs, it became clear we needed a more centralized and standardized way to surface complex feature sets to different account types.
This turned out to be a major undertaking, of course; neatly dividing up a large and diverse feature set maintained by multiple people (and flags!) was bound to hit a few logistical difficulties. On the integrations team alone, we manage and maintain over 50 integrations across multiple parts of the code base.
Taking stock, we found that one permanent boolean flag was responsible for enabling/disabling a large subset of integrations for different segments of customers. This permanent flag had been paired with a half dozen other scattered flags that different members of the team had created at various times. Since the first flag was already heavily embedded into our code, we thought we could avoid introducing further complexity via deep-reaching code changes or creating new flags that would, in turn, need to be documented and maintained.
So we thought: how can we distill the way we group integrations by plan type so that we can manage all of them with one existing flag?
There were a few clear requirements:
- We were keeping our boolean flag. This meant we could only ever return enabled or disabled at any given checkpoint in the code.
- The flag could only make use of data already available on our integration configuration objects (“manifests”) to minimize change and ensure easy extensibility. The integrations themselves would effectively act as our "user" entities for flag evaluation.
- The flag should also control the display of integration cards (pictured above) for integrations not accessible to the customer. These would help upsell customers on our diverse integration offerings for higher-level plans, which is one of the many ways feature flags can be leveraged to manage entitlements.
After a bit of trial and error and various drafts of nested-chart-style visuals, we landed on a simple structure we felt would fulfill all of the above.
The principle of our flag design is basically that of a binary tree: by layering booleans over booleans, you can achieve an indefinite number of outcomes simply by bifurcating on true/false for unidimensional attributes like integrationKey
or planType
, where each layer in the tree represents its own feature.
Since our feature flag rules evaluate in order, each rule serves as a filter for those integrations that pass to the next. It is the same concept as prerequisite flags. Rather than create separate flags that need to be separately maintained though, we manage the prerequisites via custom properties. This allows us to manage multivariate outcomes via a simple boolean flag.
Our flag contains four boolean layers, described by four custom properties available at all points of evaluation:
1. released/nonReleased, managed by the learnMore
flag rule: is the learnMore
dummy capability defined on an integration object with integrationKey
x? If so, the integration is released. If not, do not display the card on the integrations page under any circumstance. This can be evaluated separately from the other main properties (planType
and capability
) as it will be true or false for all (therefore rendering those properties implicit).
(Above: The top-level filters determining which integration cards can be displayed to all users. The third and fourth rule filter for integrations that are universally accessible regardless of plan type.)
2. integrationKey
: each third party we integrate with has a unique integration key. (This can, however, can include multiple integrations; for example, we offer both flag triggers and audit log events for Datadog.)
3. planType
: given the integration key, are any of its capabilities available to the current customer’s planType
? If not, return false (disabled).
4. capability
: each integrations is categorized by capability (i.e. webhook, flag triggers, etc). Given the integration key and plan type, should the given capability
be available to the customer?
From these properties, we were then able to create simple helper functions in our code to determine (1) whether an integration should be displayed on the Integrations page at all (whether it is released), (2) whether it is enabled for the user’s plan, (3) whether we should allow configuration from the Integrations page directly or show a "learn more" card (for integrations that require configuration external to the integrations page), and (4) whether we should upsell on the "learn more" card if the latter.
We are able to determine (3) - whether a "learn more" card should upsell based on whether it serves true for any other capabilities. If it has no enabled capabilities for the user’s plan, but is a released integration, then we upsell. For example, a Professional plan would show a "learn more" card without upsell for Ansible, which is included on a Pro plan (as per the chart above) but needs to be configured elsewhere. Meanwhile, the same plan would show with upsell for ADFS, an SSO integration that is either included with an Enterprise plan or can be added as part of non-Enterprise plans.
(Above: On the left, you can see what the Ansible integration card would look like to Professional users. Our Ansible integration is fully accessible to Professional users, but requires further configuration. On the right, you'll see how the ADFS integration card would to Professional users. We provide ADFS SSO as an add-on to all non-enterprise plan types.)
Features that must be purchased via add-on such as data export or SSO for non-Enterprise plans can be separately maintained within the same flag via user segments.
This may all seem a bit exhausting, not to mention confusing. And sorting out the precise set of custom properties by which we could determine not only whether an integration should be enabled for a given customer but also whether and how to upsell did prove a bit more difficult than anticipated (so many diagrams!) But we believe there are many benefits to this strategy:
- The flag serves as a single source of truth for all integrations managed by our team
- The system is easily extensible to any new integrations we may build in the future
- We require minimal documentation for how our pricing tiers are structured vis-à-vis integration offerings
- We would not have to create a new PR in the product code every time we wanted to add a new integration via our main integration framework.
Using flags for entitlements allows us to move any integration from Pro to Enterprise or vice versa with a simple flag change, no code required. This gives incredible power to your product or pricing teams and saves toil and back-and-forth for your engineering team.
If you are interested in setting up a flag like this one, we highly recommend investing the time to plan and drawing up some diagrams to ensure you have your bases covered. But we can promise it pays off!
To learn more about the different features LaunchDarkly plans offer, check out our Pricing page.
Stay tuned for our next installment in this series: "How LaunchDarkly Uses Feature Flags: Feature Flags for API Rate Limiting."