Skip to content

Commit fec2a60

Browse files
committed
docs: complete single and all features section
1 parent eb72d96 commit fec2a60

File tree

1 file changed

+163
-91
lines changed

1 file changed

+163
-91
lines changed

docs/utilities/feature_flags.md

+163-91
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ The following sample infrastructure will be used throughout this documentation:
9292
}
9393
}
9494
},
95-
"feature2": {
96-
"default": true
95+
"ten_percent_off_campaign": {
96+
"default": false
9797
}
9898
}
9999
ContentType: 'application/json'
@@ -139,7 +139,7 @@ The following sample infrastructure will be used throughout this documentation:
139139
}
140140
}
141141
},
142-
"feature2": {
142+
"ten_percent_off_campaign": {
143143
"default": true
144144
}
145145
}
@@ -175,138 +175,210 @@ The following sample infrastructure will be used throughout this documentation:
175175
}
176176
```
177177

178-
### Use feature flag store
178+
### Evaluating a single feature flag
179+
180+
To get started, you'd need to initialize `AppConfigStore` and `FeatureFlags`. Then call `FeatureFlags` `evaluate` method to fetch, validate, and evaluate your feature.
179181

180-
After you have created and configured `AppConfigStore` and added your feature configuraiton you can use the feature
181-
flags in your code:
182+
The `evaluate` method supports two optional parameters:
183+
184+
* **context**: Value to be evaluated against each rule defined for the given feature
185+
* **default**: Sentinel value to use in case we experience any issues with our store, or feature doesn't exist
182186

183187
=== "app.py"
184188

185-
```python
189+
```python hl_lines="3 9 13 17-19"
190+
from aws_lambda_powertools.utilities.feature_flags import FeatureFlags, AppConfigStore
191+
186192
app_config = AppConfigStore(
187193
environment="dev",
188194
application="product-catalogue",
189195
name="features"
190196
)
197+
191198
feature_flags = FeatureFlags(store=app_config)
192-
ctx = {"username": "lessa", "tier": "premium", "location": "NL"}
193199

194-
has_premium_features: bool = feature_flags.evaluate(name="premium_features",
195-
context=ctx,
196-
default=False)
200+
def lambda_handler(event, context):
201+
# Get customer's tier from incoming request
202+
ctx = { "tier": event.get("tier", "standard") }
203+
204+
# Evaluate whether customer's tier has access to premium features
205+
# based on `has_premium_features` rules
206+
has_premium_features: bool = feature_flags.evaluate(name="has_premium_features",
207+
context=ctx, default=False)
208+
if has_premium_features:
209+
# enable premium features
210+
...
197211
```
198212

213+
=== "event.json"
214+
215+
```json hl_lines="3"
216+
{
217+
"username": "lessa",
218+
"tier": "premium",
219+
"basked_id": "random_id"
220+
}
221+
```
199222
=== "features.json"
200223

201-
```json
202-
{
203-
"premium_features": {
204-
"default": false,
205-
"rules": {
206-
"customer tier equals premium": {
207-
"when_match": true,
208-
"conditions": [
209-
{
210-
"action": "EQUALS",
211-
"key": "tier",
212-
"value": "premium"
213-
}
214-
]
215-
}
216-
}
217-
}
224+
```json hl_lines="2 6 9-11"
225+
{
226+
"premium_features": {
227+
"default": false,
228+
"rules": {
229+
"customer tier equals premium": {
230+
"when_match": true,
231+
"conditions": [
232+
{
233+
"action": "EQUALS",
234+
"key": "tier",
235+
"value": "premium"
236+
}
237+
]
238+
}
239+
}
240+
},
241+
"ten_percent_off_campaign": {
242+
"default": false
243+
}
218244
}
219245
```
220246

221-
### Evaluating a single feature flag
247+
#### Static flags
222248

223-
To fetch a single feature, setup the `FeatureFlags` instance and call the `evaluate` method.
249+
We have a static flag named `ten_percent_off_campaign`. Meaning, there are no conditional rules, it's either ON or OFF for all customers.
250+
251+
In this case, we could omit the `context` parameter and simply evaluate whether we should apply the 10% discount.
224252

225253
=== "app.py"
226254

227-
```python
228-
feature_flags = FeatureFlags(store=app_config)
255+
```python hl_lines="12-13"
256+
from aws_lambda_powertools.utilities.feature_flags import FeatureFlags, AppConfigStore
257+
258+
app_config = AppConfigStore(
259+
environment="dev",
260+
application="product-catalogue",
261+
name="features"
262+
)
263+
264+
feature_flags = FeatureFlags(store=app_config)
265+
266+
def lambda_handler(event, context):
267+
apply_discount: bool = feature_flags.evaluate(name="ten_percent_off_campaign",
268+
default=False)
229269

230-
new_feature_active: bool = feature_flags.evaluate(name="new_feature",
231-
default=False)
270+
if apply_discount:
271+
# apply 10% discount to product
272+
...
232273
```
233274

234275
=== "features.json"
235276

236-
```json
237-
{
238-
"new_feature": {
239-
"default": true
240-
}
241-
}
277+
```json hl_lines="2-3"
278+
{
279+
"ten_percent_off_campaign": {
280+
"default": false
281+
}
282+
}
242283
```
243284

244-
In this example the feature flag is **static**, which mean it will be evaluated without any additional context such as
245-
user or location. If you want to have **dynamic** feature flags that only works for specific user group or other contex
246-
aware information you need to pass a context object and add rules to your feature configuration.
285+
### Getting all enabled features
286+
287+
As you might have noticed, each `evaluate` call means an API call to the Store and the more features you have the more costly this becomes.
288+
289+
You can use `get_enabled_features` method for scenarios where you need a list of all enabled features according to the input context.
247290

248291
=== "app.py"
249292

250-
```pyhthon
251-
feature_flags = FeatureFlags(store=app_config)
252-
ctx = {"username": "lessa", "tier": "premium", "location": "NL"}
293+
```python hl_lines="17-20 23"
294+
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
295+
from aws_lambda_powertools.utilities.feature_flags import FeatureFlags, AppConfigStore
253296

254-
has_premium_features: bool = feature_flags.evaluate(name="premium_features",
255-
context=ctx,
256-
default=False
257-
```
297+
app = ApiGatewayResolver()
258298

259-
=== "features.json"
299+
app_config = AppConfigStore(
300+
environment="dev",
301+
application="product-catalogue",
302+
name="features"
303+
)
260304

261-
```json
262-
{
263-
"premium_features": {
264-
"default": false,
265-
"rules": {
266-
"customer tier equals premium": {
267-
"when_match": true,
268-
"conditions": [
269-
{
270-
"action": "EQUALS",
271-
"key": "tier",
272-
"value": "premium"
273-
}
274-
]
275-
}
276-
}
277-
}
278-
}
279-
```
305+
feature_flags = FeatureFlags(store=app_config)
280306

281-
### Get all enabled features
282307

283-
In cases where you need to get a list of all the features that are enabled according to the input context you can
284-
use `get_enabled_features` method:
308+
@app.get("/products")
309+
def list_products():
310+
ctx = {
311+
**app.current_event.headers,
312+
**app.current_event.json_body
313+
}
285314

286-
=== "app.py"
315+
# all_features is evaluated to ["geo_customer_campaign", "ten_percent_off_campaign"]
316+
all_features: list[str] = feature_flags.get_enabled_features(context=ctx)
287317

288-
```python
289-
feature_flags = FeatureFlags(store=app_config)
290-
ctx = {"username": "lessa", "tier": "premium", "location": "NL"}
318+
if "geo_customer_campaign" in all_features:
319+
# apply discounts based on geo
320+
...
291321

292-
all_features: list[str] = feature_flags.get_enabled_features(context=ctx)
293-
# all_features is evaluated to ["feautre1", "feature2"]
322+
if "ten_percent_off_campaign" in all_features:
323+
# apply additional 10% for all customers
324+
...
325+
326+
def lambda_handler(event, context):
327+
return app.resolve(event, context)
294328
```
295329

330+
=== "event.json"
331+
332+
```json hl_lines="2 8"
333+
{
334+
"body": '{"username": "lessa", "tier": "premium", "basked_id": "random_id"}',
335+
"resource": "/products",
336+
"path": "/products",
337+
"httpMethod": "GET",
338+
"isBase64Encoded": false,
339+
"headers": {
340+
"CloudFront-Viewer-Country": "NL",
341+
}
342+
}
343+
```
344+
296345
=== "features.json"
297346

298-
```json hl_lines="2 6"
299-
{
300-
"feature1": {
301-
"default": false,
302-
"rules": {...}
303-
},
304-
"feature2": {
305-
"default": false,
306-
"rules": {...}
307-
},
308-
...
309-
}
347+
```json hl_lines="17-18 20 27-29"
348+
{
349+
"premium_features": {
350+
"default": false,
351+
"rules": {
352+
"customer tier equals premium": {
353+
"when_match": true,
354+
"conditions": [
355+
{
356+
"action": "EQUALS",
357+
"key": "tier",
358+
"value": "premium"
359+
}
360+
]
361+
}
362+
}
363+
},
364+
"ten_percent_off_campaign": {
365+
"default": true
366+
},
367+
"geo_customer_campaign": {
368+
"default": false,
369+
"rules": {
370+
"customer in temporary discount geo": {
371+
"when_match": true,
372+
"conditions": [
373+
{
374+
"action": "IN",
375+
"key": "CloudFront-Viewer-Country",
376+
"value": ["NL", "IE", "UK", "PL", "PT"},
377+
}
378+
]
379+
}
380+
}
381+
}
310382
}
311383
```
312384

0 commit comments

Comments
 (0)