aws-powertools / powertools-lambda

MIT No Attribution
73 stars 4 forks source link

RFC: Feature toggles rule engine - make AWS AppConfig great again #23

Closed ran-isenberg closed 2 years ago

ran-isenberg commented 3 years ago

Key information

Maintainers Note: Update RFC after docs have been created as implementation was improved

Summary

[summary]: Simplify the usage of feature toggles with AppConfig. Take it to the next level with a rule engine that provides calculated values of feature toggles according to input context.

Motivation

Why are we doing this? What use cases does it support? What is the expected outcome?

App config is great but it's very raw/barebones. This feature will encourage people to use AppConfig. I'd like to build a feature toggles on top the current app config utility.

Proposal

Build a simple feature toggle rule engine. The rules will be part of the JSON schema that is uploaded to AppConfig. The rule engine will use existing powertools utility to load and parse the json from AppConfig. It will provide a simple function (same API as launch darkly support) for getting a feature toggle by name. The function will also receive a context dict which will be matched against a set of rules. The feature will have a default boolean value. However, if the context data matches a rule in the schema JSON, the returned value will be the value that is defined in the matched rule. This can allow you to have a feature toggle turned off by default but turning it on for a specific user or customer in a specific region etc. The rules will accept any key/value context. Supported actions can be equals, starts with, regex match , endswith, in a list and many more. It's very easy to extend the engine. An example rule: 'if customer_name' equals coca cola and username starts with 'admin' , trigger the feature on. For other cases, the feature is off. See configuration language below. This type of API will take appconfig to the next level. It's very much barebones at the moment.

If this feature should be available in other runtimes (e.g. Java, Typescript), how would this look like to ensure consistency? It can be done in other languages, it's a very simple rule engine that I've already written in Python.

User Experience

How would customers use it?

conf_store: ConfigurationStore = ConfigurationStore( environment='test_env', service='test_app', conf_name="test_conf_name", cache_seconds=600, )

toggle: bool = conf_store.get_feature_toggle(feature_name='my_feature', rules_context={'customer_name': 'coca-cola', 'username': 'abc'}, default_value=False)

The default value parameter in the API is the default value to return if the feature doesn't exist in the json schema.

Any configuration or corner cases you'd expect?

Example configuration: { 'log_level': 'DEBUG', 'features': { 'my_feature': { 'default_value': 'False', 'rules': [ { 'name': 'set the toggle on for customer name 666 and username abc ', 'default_value': True, 'restrictions': [ { 'action': 'EQUALS', 'key': 'customer_name', 'value': 'coca-cola', }, { 'action': 'EQUALS', 'key': 'username', 'value': 'abc', } ] }, ] } } }

Drawbacks

Current solution has support for only boolean values for feature toggles. This can be of course expanded if required rather easily.

Why should we not do this? dont see a reason not to :)

Do we need additional dependencies? Impact performance/package size? Solution can be based upon pydantic (very easy) or regular json parsing.

Rationale and alternatives

Alternative is to use a third party tool (which is not free) like Launch Darkly.

Unresolved questions

Optional, stash area for topics that need further development e.g. TBD

ran-isenberg commented 3 years ago

@heitorlessa After our chat in slack, I thought i'd advance this by providing a working poc with most of the tests. It's pure python, no pydantic!

https://github.com/awslabs/aws-lambda-powertools-python/pull/494

heitorlessa commented 3 years ago

Update: working on documentation as we speak

ran-isenberg commented 3 years ago

Update: working on documentation as we speak

I'm available on Slack for questions

heitorlessa commented 3 years ago

This is now launched as part of 1.19.0 release: https://github.com/awslabs/aws-lambda-powertools-python/releases/tag/v1.19.0

heitorlessa commented 3 years ago

The UX has largely changed and it's now broken down into the following components:

StoreProvider will likely change to make it easier to bring additional providers - AppConfig is the only supported one as we speak. AppConfigStore is using Parameters utility to reuse caching and AppConfig logic. However, this makes it harder to create other Stores and yet make use of cache and JMESPath for a consistent experience across Stores --- That is the reason is still in Beta.

Full docs with examples are here: https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/feature_flags/


Current UX

Sample feature flag schema config

{
  "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
  }
}

Evaluate single feature flag

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") }

    # Evaluate whether customer's tier has access to premium features
    # based on `has_premium_features` rules
    has_premium_features: bool = feature_flags.evaluate(name="premium_features",
                                                        context=ctx, default=False)
    if has_premium_features:
        # enable premium features
        ...

Evaluate and return all enabled feature flags

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)

Rule engine and Schema spec

Schema specification: https://awslabs.github.io/aws-lambda-powertools-python/latest/api/utilities/feature_flags/index.html#aws_lambda_powertools.utilities.feature_flags.SchemaValidator

Rule engine flowchart

Rule engine decision flowchart

heitorlessa commented 3 years ago

I'll keep this open until we go GA so it's easier to catch up on interfaces and such for future implementations ;)