RFC: Feature Flags utility with AppConfig multi-variant support #5614
dreamorosi
started this conversation in
RFCs (Request for Comments)
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Is this related to an existing feature request or issue?
#3754
Which area does this RFC relate to?
New Feature: Feature Flags
Summary
A Feature Flags utility for Powertools for AWS Lambda (TypeScript) that covers both what Powertools for Python ships today (a local rule engine over a freeform JSON document) and AWS AppConfig multi-variant flags, which are evaluated by AppConfig itself through the AppConfig Agent. One
FeatureFlagsAPI sits over both. It reuses the Parameters utility for retrieval where a Parameters provider exists, including the AppConfig Agent support added in v2.35.0 (#5399).This RFC is the output of a design session. The decisions below are proposals, and the questions at the end are the ones we could not settle without wider input.
Use case
Two groups asked for this in #3754 and they want different things:
Both want a
defaultthat is honoured when the store is unreachable, and neither wantsundefinedchecks sprinkled through handler code.Proposal
Language
Terms used throughout, with the words we avoid so the docs and code stay consistent:
string,number,boolean, or flat arrays of those; never nested objects. Never read from the environment or event automatically.evaluateasdefault, returned only when the Store cannot answer or the flag does not exist. Distinct from Flag Default.API
Result contract, shared by every Store:
evaluatenever returnsundefined. Retrieval failure and "flag not found" return the Fallback. Misconfiguration throws: malformed Flag Document, access denied, incompatible operand types in a Condition, unparseable Agent response.evaluateManyapplies that per flag. If the transport itself fails, every requested flag gets its Fallback and one warning is logged. Misconfiguration throws for the batch.getEnabledFeaturesreturnsstring[]; on a store failure it returns[].Two kinds of Store, one evaluator
The Store owns evaluation. This inverts Python, where the store is a dumb document source and
FeatureFlagsruns the rules. The inversion is what lets one API cover both cases:RuleEngineStore): takes asource: () => Promise<JSONValue>that returns a Flag Document, validates it once per document version, evaluates Rules in-process. Any Parameters provider, S3, or a plain function can be the source.Context: key=valueheaders to the AppConfig Agent, which selects the Variant.evaluaterequests?flag=<name>;evaluateManyrepeats?flag=;getEnabledFeaturesfetches the whole profile. Requires Agent 2.0.45 or later. No cache of ours; the Agent is the cache.Built-in stores under
@aws-lambda-powertools/feature-flags/appconfig, mirroring Parameters naming: one overAppConfigProvider(SDK, freeform documents), one overgetConfig(Agent, freeform documents), and the Agent multi-variant store. All accept the sameawsSdkV3Client/clientConfigpassthrough that Parameters accepts, plusenvelope(JMESPath) for documents embedded in a larger configuration.Flag Document: Python's schema, verbatim
The local rule engine reads exactly the document Powertools for Python defines:
default,boolean_type,ruleskeyed by name,when_match,conditionswithaction/key/value, the same Action vocabulary including theIN/KEY_IN_VALUEalias pairs, all three time Actions with IANATIMEZONE,MODULO_RANGE, and first-match rule order by object key order.We considered a TypeScript-native shape (array of rules, explicit priority). The only quirk it fixes that matters is implicit rule ordering, and that is not worth losing cross-language documents. Deferred, not rejected.
Conforming on shape does not mean copying every behaviour. Where Python's evaluation is a footgun rather than a design choice, we pick the defensible semantics, document the divergence, and open an issue on Powertools for Python proposing the same change for its next major. Proposed divergences:
None;NOT_EQUALSand negative membership Actions matchSTARTSWITHon a number)validation_exception_handlercan intercept23:30to23:00never matchesHH:MMHH:MMvalidationboolean_typedefaultwhen absent (agrees with Python for boolean defaults)evaluaterules: [], empty top level)Multi-variant: the Variant type
The Agent returns
{ "_variant": "QA", "enabled": true, "dark_mode_support": true }per flag. The Agent store returns it as aVariant:always, even when the flag has no attributes. On that store the Fallback must itself be a
Variant. We rejected: returningenabledalone (discards the attributes that justify multi-variant), returning the raw object (always truthy, soif (await evaluate(...))silently breaks, and_variantleaks wire format), and unwrapping to a boolean when there are no attributes (return type would change when someone adds an attribute in the console, with no code change).Because
Context:headers cannot carry arrays, the Agent store throws, naming the key, if an Evaluation Context value is an array. The local store supports array values forALL_IN_VALUE/ANY_IN_VALUE/NONE_IN_VALUE, as Python does.Package boundary
@aws-lambda-powertools/feature-flagsis a new package. The core sub-path (evaluator,Storeinterface, local rule engine) depends only oncommons. Stores that wrap Parameters live under/appconfigand import@aws-lambda-powertools/parameters(and@aws-lambda-powertools/jmespathfor envelopes) as optional peers, the same pattern Parameters uses for AWS SDK clients. An Agent-only or custom-store user never pulls the AppConfigData SDK client into the bundle, which was the first concern raised in #3754.Time and clock
All three time Actions ship in v1 using
Intl.DateTimeFormatfor IANA zones, no new dependency. The local store accepts an optionalnow: () => Datefor tests; documents cannot override the clock, matching Python.Logging
Evaluation Context values are never logged at any level; key names only. With a logger supplied: debug lines for flag name, matched Rule or Flag Default, store, and Variant name; one warning per
evaluate/evaluateManythat returned a Fallback. No metrics or traces. No logger means no output (noconsolefallback).Phasing
evaluateManybatching, e2e against a real multi-variant profile. Closes Feature request: Add support for multi-variant AWS AppConfig feature flags #3754.Multi-variant is last even though it is the original ask: it is the only part with no Python reference and gains most from RFC feedback, and the local store is the harder half and should pressure-test the API first.
Out of scope
validation_exception_handler.getFeatureFlag()with a hidden default store (would need three new env vars).Potential challenges
{ enabled: false, variant: 'default', attributes: {} }). AdisabledVariant()helper is a likely mitigation.404rather than{}for an unknown?flag=key; our "missing flag" detection depends on it.MODULO_RANGEin a Flag Document and AppConfig's own split hashing bucket users differently. Documentation only.Dependencies and Integrations
@aws-lambda-powertools/commons(hard).@aws-lambda-powertools/parametersv2.35.0+ (optional peer):AppConfigProvider,getConfig.@aws-lambda-powertools/jmespath(optional peer): envelopes.Alternative solutions
parameters. Muddles a retrieval library with a rule engine; Python's clean split comes partly from the package boundary.evaluateAll()returning every flag's value. No per-flag Fallback is possible, so it reintroducesundefined; replaced byevaluateManywith explicit Fallbacks.Open questions for reviewers
Storeinterface. Sketch:get(name, context): Promise<{ value, enabled } | { missing: true }>andgetAll(context).evaluateManyon the local store validate only the named flags, or the whole document?VariantFallback acceptable, or do you wantdefault: falseto mean "disabled Variant" on Agent stores?Acknowledgment
Future readers
Please react with 👍 and your use case to help us understand customer demand.
All reactions