-
Notifications
You must be signed in to change notification settings - Fork 432
docs(feature-flags): create concrete documentation #594
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
faec4ce
c7bd7fb
9019f30
d1b7c22
5a3aa25
a691676
745ac9f
7a66dab
b34643d
e5a24fd
20cce82
527921e
85d2fc5
84f8bfd
0250511
5385b4f
d246f67
7c5a881
0f6a230
4a159ec
1c72a06
186293f
8a3ee9d
b8ee65f
4cb287b
1195f7c
48abcba
350abac
8466cf7
ee69581
b5e837e
7ff670d
642d0eb
1628b2a
f1e98ca
5567f6e
02c9169
58258cd
1dabcd9
dcbe08b
eb72d96
fec2a60
802aab3
203664c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,366 @@ | ||
--- | ||
title: Feature flags | ||
description: Utility | ||
title: Feature flags description: Utility | ||
--- | ||
|
||
The feature flags utility provides a simple rule engine to define when one or multiple features should be enabled depending on the input. | ||
The feature flags utility provides a simple rule engine to define when one or multiple features should be enabled | ||
depending on the input. | ||
|
||
!!! tip "For simpler use cases where a feature is simply on or off for all users, use [Parameters](parameters.md) utility instead." | ||
!!! tip "For simpler use cases where a feature is simply on or off for all users, use [Parameters](parameters.md) | ||
utility instead." | ||
|
||
## Terminology | ||
|
||
Feature flags are used to modify a system behaviour without having to change their code. These flags can be static or dynamic. | ||
Feature flags are used to modify a system behaviour without having to change their code. These flags can be static or | ||
dynamic. | ||
|
||
**Static feature flags** are commonly used for long-lived behaviours that will rarely change, for example `TRACER_ENABLED=True`. These are better suited for [Parameters utility](parameters.md). | ||
**Static feature flags** are commonly used for long-lived behaviours that will rarely change, for | ||
example `TRACER_ENABLED=True`. These are better suited for [Parameters utility](parameters.md). | ||
|
||
**Dynamic feature flags** are typically used for experiments where you'd want to enable a feature for a limited set of customers, for example A/B testing and Canary releases. These are better suited for this utility, as you can create multiple conditions on whether a feature flag should be `True` or `False`. | ||
**Dynamic feature flags** are typically used for experiments where you'd want to enable a feature for a limited set of | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i dont think this is accurate. you can achieve the same thing with static flags. The dynamic part just allows you to change them faster without redeploying your service. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even if we can redploy fast (enough), without rules the flags will have the same value in our code execution, it doesn't make them dynamic. I think we can't achieve dyamic behaivour, say A/B testing, by just redeploying static flags. |
||
customers, for example A/B testing and Canary releases. These are better suited for this utility, as you can create | ||
multiple conditions on whether a feature flag should be `True` or `False`. | ||
|
||
That being said, be mindful that feature flags can increase your application complexity over time if you're not careful; use them sparingly. | ||
That being said, be mindful that feature flags can increase your application complexity over time if you're not careful; | ||
use them sparingly. | ||
|
||
!!! tip "Read [this article](https://martinfowler.com/articles/feature-toggles.html){target="_blank"} for more details on different types of feature flags and trade-offs" | ||
!!! tip "Read [this article](https://martinfowler.com/articles/feature-toggles.html){target="_blank"} for more details | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would appreciate a link to my original feature flags blog, but no pressure ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also a pretty good article https://www.atlassian.com/continuous-delivery/principles/feature-flags Although Martin Fowler's blog is very comprehensive. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CloudBees have their own implementaiton: https://www.cloudbees.com/blog/ultimate-feature-flag-guide There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ha! I have stumbled upon all of the mentioned articles by now 😅, we can make a list |
||
on different types of feature flags and trade-offs" | ||
|
||
## Key features | ||
|
||
> TODO: Revisit once getting started and advanced sections are complete | ||
|
||
* Define simple feature flags to dynamically decide when to enable a feature | ||
* Fetch one or all feature flags enabled for a given application context | ||
* Bring your own configuration store | ||
|
||
## Getting started | ||
|
||
### IAM Permissions | ||
|
||
By default, this utility provides AWS AppConfig as a configuration store. As such, you IAM Role needs permission - `appconfig:GetConfiguration` - to fetch feature flags from AppConfig. | ||
Because powertools needs to fetch the configuration from the AppConfig, you need to add `appconfig:GetConfiguration` | ||
action to your function. | ||
|
||
### Required resources | ||
|
||
By default, this utility | ||
provides [AWS AppConfig](https://docs.aws.amazon.com/appconfig/latest/userguide/what-is-appconfig.html) as a | ||
configuration store. To create a dedicate you can use this cloudformation template: | ||
|
||
=== "template.yaml" | ||
|
||
```yaml | ||
AWSTemplateFormatVersion: "2010-09-09" | ||
Description: A sample template | ||
Resources: | ||
FeatureStoreApp: | ||
Type: AWS::AppConfig::Application | ||
Properties: | ||
Description: "AppConfig Appliction for feature toggles" | ||
Name: my-app | ||
|
||
FeatureStoreDevEnv: | ||
Type: AWS::AppConfig::Environment | ||
Properties: | ||
ApplicationId: !Ref FeatureStoreApp | ||
Description: "Development Environment for the App Config Store" | ||
Name: "development" | ||
|
||
FeatureStoreConfigProfile: | ||
Type: AWS::AppConfig::ConfigurationProfile | ||
Properties: | ||
ApplicationId: !Ref FeatureStoreApp | ||
Name: "MyTestProfile" | ||
LocationUri: "hosted" | ||
|
||
HostedConfigVersion: | ||
Type: AWS::AppConfig::HostedConfigurationVersion | ||
Properties: | ||
ApplicationId: !Ref FeatureStoreApp | ||
ConfigurationProfileId: !Ref FeatureStoreConfigProfile | ||
Description: 'A sample hosted configuration version' | ||
Content: | | ||
{ | ||
"premium_features": { | ||
"default": false, | ||
"rules": { | ||
"customer tier equals premium": { | ||
"when_match": true, | ||
"conditions": [ | ||
{ | ||
"action": "EQUALS", | ||
"key": "tier", | ||
"value": "premium" | ||
} | ||
] | ||
} | ||
} | ||
}, | ||
"feature2": { | ||
"default": true | ||
} | ||
} | ||
ContentType: 'application/json' | ||
|
||
ConfigDeployment: | ||
Type: AWS::AppConfig::Deployment | ||
Properties: | ||
ApplicationId: !Ref FeatureStoreApp | ||
ConfigurationProfileId: !Ref FeatureStoreConfigProfile | ||
ConfigurationVersion: !Ref HostedConfigVersion | ||
DeploymentStrategyId: "AppConfig.AllAtOnce" | ||
EnvironmentId: !Ref FeatureStoreDevEnv | ||
``` | ||
|
||
The `Content` parameter is a json structure of the feature flags and rules. | ||
|
||
TODO: add steps to create new version and new deployment for the config | ||
|
||
TODO: add CDK example | ||
|
||
### Use feature flag store | ||
|
||
After you have created and configured `AppConfigStore` and added your feature configuraiton you can use the feature | ||
flags in your code: | ||
|
||
=== "app.py" | ||
|
||
```python | ||
app_config = AppConfigStore( | ||
environment="dev", | ||
application="product-catalogue", | ||
name="features" | ||
) | ||
feature_flags = FeatureFlags(store=app_config) | ||
ctx = {"username": "lessa", "tier": "premium", "location": "NL"} | ||
|
||
has_premium_features: bool = feature_flags.evaluate(name="premium_features", | ||
context=ctx, | ||
default=False) | ||
``` | ||
|
||
=== "features.json" | ||
|
||
```json | ||
{ | ||
"premium_features": { | ||
"default": false, | ||
"rules": { | ||
"customer tier equals premium": { | ||
"when_match": true, | ||
"conditions": [ | ||
{ | ||
"action": "EQUALS", | ||
"key": "tier", | ||
"value": "premium" | ||
} | ||
] | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Evaluating a single feature flag | ||
|
||
To fetch a single feature, setup the `FeatureFlags` instance and call the `evaluate` method. | ||
|
||
=== "app.py" | ||
|
||
```python | ||
feature_flags = FeatureFlags(store=app_config) | ||
|
||
new_feature_active: bool = feature_flags.evaluate(name="new_feature", | ||
default=False) | ||
``` | ||
|
||
=== "features.json" | ||
|
||
```json | ||
{ | ||
"new_feature": { | ||
"default": true | ||
} | ||
} | ||
``` | ||
|
||
In this example the feature flag is **static**, which mean it will be evaluated without any additional context such as | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think global would be the better term. that's what i was going to say at the webinar too There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. static is accurate when referencing a programming language. Something that constantly returns the same value regardless of the context. |
||
user or location. If you want to have **dynamic** feature flags that only works for specific user group or other contex | ||
heitorlessa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
aware information you need to pass a context object and add rules to your feature configuration. | ||
|
||
### Creating feature flags | ||
=== "app.py" | ||
|
||
> NOTE: Explain schema, provide sample boto3 script and CFN to create one | ||
```pyhthon | ||
feature_flags = FeatureFlags(store=app_config) | ||
ctx = {"username": "lessa", "tier": "premium", "location": "NL"} | ||
|
||
has_premium_features: bool = feature_flags.evaluate(name="premium_features", | ||
context=ctx, | ||
default=False | ||
``` | ||
|
||
=== "features.json" | ||
|
||
```json | ||
{ | ||
"premium_features": { | ||
"default": false, | ||
"rules": { | ||
"customer tier equals premium": { | ||
"when_match": true, | ||
"conditions": [ | ||
{ | ||
"action": "EQUALS", | ||
"key": "tier", | ||
"value": "premium" | ||
} | ||
] | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Get all enabled features | ||
|
||
In cases where you need to get a list of all the features that are enabled you can use `get_enabled_features` method: | ||
heitorlessa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
=== "app.py" | ||
|
||
```python | ||
feature_flags = FeatureFlags(store=app_config) | ||
ctx = {"username": "lessa", "tier": "premium", "location": "NL"} | ||
|
||
all_features: list[str] = feature_flags.get_enabled_features(context=ctx) | ||
# all_features is evaluated to ["feautre1", "feature2"] | ||
``` | ||
|
||
=== "features.json" | ||
|
||
```json hl_lines="2 6" | ||
{ | ||
"feature1": { | ||
"default": false, | ||
"rules": {...} | ||
}, | ||
"feature2": { | ||
"default": false, | ||
"rules": {...} | ||
}, | ||
... | ||
} | ||
} | ||
``` | ||
|
||
As a result you will get a list of all the names of the features from your feature flags configuration. | ||
|
||
### Feature flags schema | ||
|
||
When using the feature flags utility powertools expects specific schema stored in your AppConfig configuration. The | ||
minimal requirement is the name of the feature and the default value, for example: | ||
|
||
```json | ||
{ | ||
"global_feature": { | ||
"default": true | ||
} | ||
} | ||
``` | ||
|
||
This is a static flag that will be applied to every evaluation within your code. If you need more control and want to | ||
provide context such as user group, permisisons, location or other information you need to add rules to your feature | ||
flag configuration. | ||
|
||
#### Rules | ||
|
||
To use feature flags dynamically you can configure rules in your feature flags configuration and pass context | ||
to `evaluate`. The rules block must have: | ||
|
||
* rule name as a key | ||
* value when the condition is met | ||
* list conditions for evaluation | ||
|
||
### Fetching a single feature flag | ||
```json hl_lines="4-11" | ||
|
||
### Fetching all feature flags | ||
{ | ||
"premium_feature": { | ||
"default": false, | ||
"rules": { | ||
"customer tier equals premium": { | ||
"when_match": true, | ||
"conditions": [ | ||
{ | ||
"action": "EQUALS", | ||
"key": "tier", | ||
"value": "premium" | ||
} | ||
] | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Advanced | ||
You can have multiple rules with different names. The powertools will return the first result `when_match` of the | ||
matching rule configuration or `default` value when none of the rules apply. | ||
|
||
#### Adjusting cache TTL | ||
#### Conditions | ||
|
||
### Partially enabling features | ||
The conditions block is a list of `action`, `key` `value`: | ||
|
||
### Bring your own store provider | ||
```json | ||
{ | ||
"action": "EQUALS", | ||
"key": "tier", | ||
"value": "premium" | ||
} | ||
``` | ||
|
||
## Testing your code | ||
The `action` configuration can have 5 different values: `EQUALS`, `STARTSWITH`, `ENDSWITH`, `IN`, `NOT_IN`. | ||
The `key` and `value` will be compared to the input from the context parameter. | ||
|
||
If you have multiple conditions powertools will evaluate the list of conditions as a logical AND, so all conditions needs to be | ||
matched to return `when_match` value. | ||
|
||
=== "features.json" | ||
|
||
```json hl_lines="10-11" | ||
{ | ||
"premium_feature": { | ||
"default": false, | ||
"rules": { | ||
"customer tier equals premium": { | ||
"when_match": true, | ||
"conditions": [ | ||
{ | ||
"action": "EQUALS", | ||
"key": "tier", | ||
"value": "premium" | ||
} | ||
] | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
=== "app.py" | ||
|
||
```python hl_lines="2" | ||
feature_flags = FeatureFlags(store=app_config) | ||
ctx = {"username": "lessa", "tier": "premium", "location": "NL"} | ||
|
||
> NOTE: Share example on how customers can unit test their feature flags | ||
has_premium_features: bool = feature_flags.evaluate(name="premium_features", | ||
context=ctx, | ||
default=False | ||
``` | ||
|
||
## Advanced | ||
|
||
### Adjusting in-memory cache | ||
|
||
### Envelope (any better name) | ||
|
||
### Built-in store provider | ||
|
||
#### AppConfig | ||
|
||
## Testing your code |
Uh oh!
There was an error while loading. Please reload this page.