Skip to content
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

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

Closed
ran-isenberg opened this issue Jun 8, 2021 · 6 comments
Closed
Labels
Proposed Community submited Python

Comments

@ran-isenberg
Copy link

ran-isenberg commented Jun 8, 2021

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.

  • What other designs have been considered? Why not them?
    You can use Launch Darkly. However, this solution is very simple and provides the same client side API that launch darkly provide with AWS AppConfig.

  • What is the impact of not doing this?

Unresolved questions

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

@ran-isenberg
Copy link
Author

@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!

aws-powertools/powertools-lambda-python#494

@heitorlessa heitorlessa transferred this issue from aws-powertools/powertools-lambda-python Jul 16, 2021
@heitorlessa heitorlessa added Proposed Community submited Python labels Jul 16, 2021
@heitorlessa
Copy link
Contributor

Update: working on documentation as we speak

@ran-isenberg
Copy link
Author

Update: working on documentation as we speak

I'm available on Slack for questions

@heitorlessa
Copy link
Contributor

@heitorlessa
Copy link
Contributor

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

  • Store: Any Configuration Store that fetches a JSON document containing feature flag schema
  • SchemaValidator: Validates feature flag schema retrieves from a Store
  • FeatureFlags: Actual rule engine that uses the Store to fetch configuration, uses SchemaValidator to validate the feature schema, and then evaluate whether a feature is enabled based on context.

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Proposed Community submited Python
Projects
None yet
Development

No branches or pull requests

2 participants