Summary
This release highlights 1/ a brand new Feature Flags utility, 2/ auto-disable Tracer in non-Lambda environments to ease unit testing, 3/ API Gateway event handler now supports a custom JSON serializer, and a number of documentation improvements & bugfixes.
We hope you enjoy this new utility as much as we did working on it!!
New Feature Flags utility in Beta
Special thanks to: @risenberg-cyberark, @michaelbrewer, @pcolazurdo and @am29d
You might have heard of feature flags when:
- Looking to conditionally enable a feature in your application for your customers
- A/B testing a new feature for a subset of your customers
- Working with trunk-based development where a feature might not be available right now
- Working on short-lived features that will only be enabled for select customers
This new utility makes this entire process so much easier by fetching feature flags configuration from AWS AppConfig, and evaluating contextual values against rules you defined to decide whether a feature should be enabled.
Let's dive into the code to better understand what this all means.
Evaluating whether a customer should have access to premium features
from aws_lambda_powertools.utilities.feature_flags import FeatureFlags, AppConfigStore
app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features"
)
feature_flags = FeatureFlags(store=app_config)
def lambda_handler(event, context):
# Get customer's tier from incoming request
ctx = { "tier": event.get("tier", "standard") }
has_premium_features: bool = feature_flags.evaluate(name="premium_features",
context=ctx, default=False)
if has_premium_features:
# enable premium features
...
Sample feature flag configuration in AWS AppConfig
{
"premium_features": {
"default": false,
"rules": {
"customer tier equals premium": {
"when_match": true,
"conditions": [
{
"action": "EQUALS",
"key": "tier",
"value": "premium"
}
]
}
}
},
"ten_percent_off_campaign": {
"default": false
}
}
Notice we have premium_features
flag that will conditionally be available for premium customers, and a static feature flag named ten_percent_off_campaign
that is disabled by default.
Sample invocation event for this function
{
"username": "lessa",
"tier": "premium",
"basked_id": "random_id"
}
There's a LOT going on here. Allow me to break it down:
- We're defining a feature flag configuration that is stored in AWS AppConfig
- We initialize an
AppConfigStore
using AWS AppConfig values created via Infrastructure as code (available on docs) - We initialize
FeatureFlags
and use our previously instantiatedAppConfigStore
- We call
evaluate
method and pass the name of the premium feature, along with our contextual information our rules should run against, and a sentinel value to be used in case service errors happen - Feature flags go through the rules defined in
premium_features
and evaluate whethertier
key has the valuepremium
- FeatureFlags returns
True
which is then stored ashas_premium_features
variable
But what if you have multiple feature flags and only want all enabled features?
We've got you covered! You can use get_enabled_features
to make a single call to AWS AppConfig and return a list of all enabled features at once.
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
from aws_lambda_powertools.utilities.feature_flags import FeatureFlags, AppConfigStore
app = ApiGatewayResolver()
app_config = AppConfigStore(
environment="dev",
application="product-catalogue",
name="features"
)
feature_flags = FeatureFlags(store=app_config)
@app.get("/products")
def list_products():
ctx = {
**app.current_event.headers,
**app.current_event.json_body
}
# all_features is evaluated to ["geo_customer_campaign", "ten_percent_off_campaign"]
all_features: list[str] = feature_flags.get_enabled_features(context=ctx)
if "geo_customer_campaign" in all_features:
# apply discounts based on geo
...
if "ten_percent_off_campaign" in all_features:
# apply additional 10% for all customers
...
def lambda_handler(event, context):
return app.resolve(event, context)
But hang on, why Beta?
We want to hear from you on the UX and evaluate how we can make it easier for you to bring your own feature flag store such as Redis, HashiCorp Consul, etc.
When would you use feature flags vs env variables vs Parameters utility?
Environment variables. For when you need simple configuration that will rarely if ever change, because changing it requires a Lambda function deployment.
Parameters utility. For when you need access to secrets, or fetch parameters in different formats from AWS System Manager Parameter Store or Amazon DynamoDB.
Feature flags utility. For when you need static or feature flags that will be enable conditionally based on the input and on a set of rules per feature whether that applies for all customers or on a per customer basis.
In both Parameters and Feature Flags utility you can change their config without having to change your application code.
Changes
Changes
πNew features and non-breaking changes
- feat(tracer): auto-disable tracer when for non-Lambda envs (#598) by @michaelbrewer
- feat(feature-flags): Add not_in action and rename contains to in (#589) by @risenberg-cyberark
- refactor(feature-flags): add debug for all features evaluation" (#590) by @heitorlessa
- refactor(feature-flags): optimize UX and maintenance (#563) by @heitorlessa
- feat(api-gateway): add support for custom serializer (#568) by @michaelbrewer
- feat(params): expose high level max_age, raise_on_transform_error (#567) by @michaelbrewer
- feat(data-classes): decode json_body if based64 encoded (#560) by @michaelbrewer
π Documentation updates
- fix(feature-toggles): correct cdk example (#601) by @michaelbrewer
- docs(parameters): auto-transforming values based on suffix (#573) by @dreamorosi
- docs(feature-flags): create concrete documentation (#594) by @am29d
- refactor(feature-flags): optimize UX and maintenance (#563) by @heitorlessa
- docs(readme): add code coverage badge (#577) by @michaelbrewer
π Bug and hot fixes
- fix(feature-flags): bug handling multiple conditions (#599) by @risenberg-cyberark
- fix(parser): apigw wss validation check_message_id; housekeeping (#553) by @michaelbrewer
π§ Maintenance
- chore(deps): bump boto3 from 1.18.15 to 1.18.17 (#597) by @dependabot
- chore(deps-dev): bump mkdocs-material from 7.2.2 to 7.2.3 (#596) by @dependabot
- chore(deps): bump boto3 from 1.18.1 to 1.18.15 (#591) by @dependabot
- chore(deps): bump codecov/codecov-action from 2.0.1 to 2.0.2 (#558) by @dependabot
- fix(deps): bump poetry to latest (#592) by @michaelbrewer
- chore(deps-dev): bump mkdocs-material from 7.2.1 to 7.2.2 (#582) by @dependabot
- refactor(feature-flags): add debug for all features evaluation" (#590) by @heitorlessa
- chore(deps-dev): bump pdoc3 from 0.9.2 to 0.10.0 (#584) by @dependabot
- docs(feature-flags): correct docs and typing (#588) by @michaelbrewer
- refactor(feature-flags): optimize UX and maintenance (#563) by @heitorlessa
- chore(deps-dev): bump isort from 5.9.2 to 5.9.3 (#574) by @dependabot
- chore(deps-dev): bump mkdocs-material from 7.2.0 to 7.2.1 (#566) by @dependabot
- chore(deps-dev): bump mkdocs-material from 7.1.11 to 7.2.0 (#551) by @dependabot
- chore(deps-dev): bump flake8-black from 0.2.1 to 0.2.3 (#541) by @dependabot
This release was made possible by the following contributors:
@am29d, @dependabot, @dependabot[bot], @dreamorosi, @heitorlessa, @michaelbrewer, @risenberg-cyberark and @pcolazurdo