Skip to content

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

Merged
merged 44 commits into from
Aug 10, 2021
Merged
Changes from 12 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
faec4ce
feat(logger): add option to clear state per invocation
heitorlessa Jun 8, 2021
c7bd7fb
Merge branch 'develop' of https://github.com/awslabs/aws-lambda-power…
heitorlessa Jun 8, 2021
9019f30
refactor: rename to remove_custom_keys
heitorlessa Jun 8, 2021
d1b7c22
refactor: rename to clear_state
heitorlessa Jun 8, 2021
5a3aa25
Merge branch 'develop' of https://github.com/awslabs/aws-lambda-power…
heitorlessa Jun 8, 2021
a691676
docs: fix anchor
heitorlessa Jun 8, 2021
745ac9f
Merge branch 'develop' of github.com:awslabs/aws-lambda-powertools-py…
Aug 5, 2021
7a66dab
docs(feature-flags): add basic docs
Aug 6, 2021
b34643d
docs: fix tip rendering banner
heitorlessa Aug 6, 2021
e5a24fd
docs(feature-flags): refactored basic section structure
Aug 6, 2021
20cce82
Merge branch 'docs/feature-toggle-examples' of github.com:am29d/aws-l…
Aug 6, 2021
527921e
docs(feature-flags): refactored examples for getting started
Aug 6, 2021
85d2fc5
add external articles links
Aug 10, 2021
84f8bfd
add CDK example for appconfig
Aug 10, 2021
0250511
add envelope
Aug 10, 2021
5385b4f
add appconfig store provider
Aug 10, 2021
d246f67
add testing section
Aug 10, 2021
7c5a881
docs: add feat flags utility
heitorlessa Aug 10, 2021
0f6a230
docs: fix typo
heitorlessa Aug 10, 2021
4a159ec
docs: complete adjusting in-memory cache section
heitorlessa Aug 10, 2021
1c72a06
docs: complete envelope section
heitorlessa Aug 10, 2021
186293f
fix(feature flags): set default cache to 5 as documented
heitorlessa Aug 10, 2021
8a3ee9d
docs: complete built-in stores section
heitorlessa Aug 10, 2021
b8ee65f
docs: complete testing your code section
heitorlessa Aug 10, 2021
4cb287b
fix: typo
heitorlessa Aug 10, 2021
1195f7c
fix: test docs identation
heitorlessa Aug 10, 2021
48abcba
fix: missing import on test
heitorlessa Aug 10, 2021
350abac
fix: remove namespace warning after testing hypothesis
heitorlessa Aug 10, 2021
8466cf7
remove todo comments
Aug 10, 2021
ee69581
Merge branch 'docs/feature-toggle-examples' of github.com:am29d/aws-l…
Aug 10, 2021
b5e837e
fix: tip banner rendering
heitorlessa Aug 10, 2021
7ff670d
Merge branch 'docs/feature-toggle-examples' of https://github.com/am2…
heitorlessa Aug 10, 2021
642d0eb
docs: improve terminology section
heitorlessa Aug 10, 2021
1628b2a
fix: frontmatter
heitorlessa Aug 10, 2021
f1e98ca
docs: add note about Beta
heitorlessa Aug 10, 2021
5567f6e
refactor(store): use max_age over cache_seconds for consistency
heitorlessa Aug 10, 2021
02c9169
docs: complete key features section
heitorlessa Aug 10, 2021
58258cd
docs: minor change on IAM permissions
heitorlessa Aug 10, 2021
1dabcd9
docs: complete required resources section
heitorlessa Aug 10, 2021
dcbe08b
Merge branch 'develop' into docs/feature-toggle-examples
heitorlessa Aug 10, 2021
eb72d96
Merge branch 'develop' of https://github.com/awslabs/aws-lambda-power…
heitorlessa Aug 10, 2021
fec2a60
docs: complete single and all features section
heitorlessa Aug 10, 2021
802aab3
docs: add rule engine flowchart; move schema to advanced
heitorlessa Aug 10, 2021
203664c
docs: complete schema section
heitorlessa Aug 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
353 changes: 331 additions & 22 deletions docs/utilities/feature_flags.md
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 ;)
https://isenberg-ran.medium.com/aws-lambda-feature-toggles-made-simple-580b0c444233

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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
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:

=== "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