Skip to content

Commit 762f6af

Browse files
ran-isenbergRan Isenbergrubenfonseca
authored andcommitted
docs(idempotency): add CDK example (aws-powertools#2434)
Co-authored-by: Ran Isenberg <[email protected]> Co-authored-by: Ruben Fonseca <[email protected]>
1 parent 63693c3 commit 762f6af

File tree

3 files changed

+79
-38
lines changed

3 files changed

+79
-38
lines changed

docs/utilities/idempotency.md

+27-38
Original file line numberDiff line numberDiff line change
@@ -77,31 +77,16 @@ If you're not [changing the default configuration for the DynamoDB persistence l
7777
???+ tip "Tip: You can share a single state table for all functions"
7878
You can reuse the same DynamoDB table to store idempotency state. We add `module_name` and [qualified name for classes and functions](https://peps.python.org/pep-3155/){target="_blank"} in addition to the idempotency key as a hash key.
7979

80-
```yaml hl_lines="5-13 21-23" title="AWS Serverless Application Model (SAM) example"
81-
Resources:
82-
IdempotencyTable:
83-
Type: AWS::DynamoDB::Table
84-
Properties:
85-
AttributeDefinitions:
86-
- AttributeName: id
87-
AttributeType: S
88-
KeySchema:
89-
- AttributeName: id
90-
KeyType: HASH
91-
TimeToLiveSpecification:
92-
AttributeName: expiration
93-
Enabled: true
94-
BillingMode: PAY_PER_REQUEST
95-
96-
HelloWorldFunction:
97-
Type: AWS::Serverless::Function
98-
Properties:
99-
Runtime: python3.9
100-
...
101-
Policies:
102-
- DynamoDBCrudPolicy:
103-
TableName: !Ref IdempotencyTable
104-
```
80+
=== "sam.yaml"
81+
82+
```yaml hl_lines="6-14 24-31" title="AWS Serverless Application Model (SAM) example"
83+
--8<-- "examples/idempotency/sam.yaml"
84+
```
85+
=== "cdk.py"
86+
87+
```python hl_lines="10 13 16 19-21" title="AWS Cloud Development Kit (CDK) Construct example"
88+
--8<-- "examples/idempotency/cdk.py"
89+
```
10590

10691
???+ warning "Warning: Large responses with DynamoDB persistence layer"
10792
When using this utility with DynamoDB, your function's responses must be [smaller than 400KB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html#limits-items){target="_blank"}.
@@ -148,7 +133,7 @@ You can quickly start by initializing the `DynamoDBPersistenceLayer` class and u
148133

149134
=== "Example event"
150135

151-
```json
136+
```json
152137
{
153138
"username": "xyz",
154139
"product_id": "123456789"
@@ -334,10 +319,12 @@ In this example, we have a Lambda handler that creates a payment for a user subs
334319

335320
Imagine the function executes successfully, but the client never receives the response due to a connection issue. It is safe to retry in this instance, as the idempotent decorator will return a previously saved response.
336321

337-
**What we want here** is to instruct Idempotency to use `user` and `product_id` fields from our incoming payload as our idempotency key. If we were to treat the entire request as our idempotency key, a simple HTTP header change would cause our customer to be charged twice.
322+
**What we want here** is to instruct Idempotency to use `user` and `product_id` fields from our incoming payload as our idempotency key.
323+
If we were to treat the entire request as our idempotency key, a simple HTTP header change would cause our customer to be charged twice.
338324

339325
???+ tip "Deserializing JSON strings in payloads for increased accuracy."
340-
The payload extracted by the `event_key_jmespath` is treated as a string by default. This means there could be differences in whitespace even when the JSON payload itself is identical.
326+
The payload extracted by the `event_key_jmespath` is treated as a string by default.
327+
This means there could be differences in whitespace even when the JSON payload itself is identical.
341328

342329
To alter this behaviour, we can use the [JMESPath built-in function](jmespath_functions.md#powertools_json-function){target="_blank"} `powertools_json()` to treat the payload as a JSON object (dict) rather than a string.
343330

@@ -410,15 +397,17 @@ Imagine the function executes successfully, but the client never receives the re
410397
???+ note
411398
This is automatically done when you decorate your Lambda handler with [@idempotent decorator](#idempotent-decorator).
412399

413-
To prevent against extended failed retries when a [Lambda function times out](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-verify-invocation-timeouts/){target="_blank"}, Powertools for AWS Lambda (Python) calculates and includes the remaining invocation available time as part of the idempotency record.
400+
To prevent against extended failed retries when a [Lambda function times out](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-verify-invocation-timeouts/){target="_blank"},
401+
Powertools for AWS Lambda (Python) calculates and includes the remaining invocation available time as part of the idempotency record.
414402

415403
???+ example
416404
If a second invocation happens **after** this timestamp, and the record is marked as `INPROGRESS`, we will execute the invocation again as if it was in the `EXPIRED` state (e.g, `expire_seconds` field elapsed).
417405

418406
This means that if an invocation expired during execution, it will be quickly executed again on the next retry.
419407

420408
???+ important
421-
If you are only using the [@idempotent_function decorator](#idempotent_function-decorator) to guard isolated parts of your code, you must use `register_lambda_context` available in the [idempotency config object](#customizing-the-default-behavior) to benefit from this protection.
409+
If you are only using the [@idempotent_function decorator](#idempotent_function-decorator) to guard isolated parts of your code,
410+
you must use `register_lambda_context` available in the [idempotency config object](#customizing-the-default-behavior) to benefit from this protection.
422411

423412
Here is an example on how you register the Lambda context in your handler:
424413

@@ -698,14 +687,14 @@ When using DynamoDB as a persistence layer, you can alter the attribute names by
698687

699688
Idempotent decorator can be further configured with **`IdempotencyConfig`** as seen in the previous example. These are the available options for further configuration
700689

701-
| Parameter | Default | Description |
702-
| ------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
703-
| **event_key_jmespath** | `""` | JMESPath expression to extract the idempotency key from the event record using [built-in functions](/utilities/jmespath_functions){target="_blank"} |
704-
| **payload_validation_jmespath** | `""` | JMESPath expression to validate whether certain parameters have changed in the event while the event payload |
705-
| **raise_on_no_idempotency_key** | `False` | Raise exception if no idempotency key was found in the request |
706-
| **expires_after_seconds** | 3600 | The number of seconds to wait before a record is expired |
707-
| **use_local_cache** | `False` | Whether to locally cache idempotency results |
708-
| **local_cache_max_items** | 256 | Max number of items to store in local cache |
690+
| Parameter | Default | Description |
691+
| ------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
692+
| **event_key_jmespath** | `""` | JMESPath expression to extract the idempotency key from the event record using [built-in functions](/utilities/jmespath_functions){target="_blank"} |
693+
| **payload_validation_jmespath** | `""` | JMESPath expression to validate whether certain parameters have changed in the event while the event payload |
694+
| **raise_on_no_idempotency_key** | `False` | Raise exception if no idempotency key was found in the request |
695+
| **expires_after_seconds** | 3600 | The number of seconds to wait before a record is expired |
696+
| **use_local_cache** | `False` | Whether to locally cache idempotency results |
697+
| **local_cache_max_items** | 256 | Max number of items to store in local cache |
709698
| **hash_function** | `md5` | Function to use for calculating hashes, as provided by [hashlib](https://docs.python.org/3/library/hashlib.html){target="_blank"} in the standard library. |
710699

711700
### Handling concurrent executions with the same payload

examples/idempotency/cdk.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from aws_cdk import RemovalPolicy
2+
from aws_cdk import aws_dynamodb as dynamodb
3+
from aws_cdk import aws_iam as iam
4+
from constructs import Construct
5+
6+
7+
class IdempotencyConstruct(Construct):
8+
def __init__(self, scope: Construct, name: str, lambda_role: iam.Role) -> None:
9+
super().__init__(scope, name)
10+
self.idempotency_table = dynamodb.Table(
11+
self,
12+
"IdempotencyTable",
13+
partition_key=dynamodb.Attribute(name="id", type=dynamodb.AttributeType.STRING),
14+
billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
15+
removal_policy=RemovalPolicy.DESTROY,
16+
time_to_live_attribute="expiration",
17+
point_in_time_recovery=True,
18+
)
19+
self.idempotency_table.grant(
20+
lambda_role, "dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem"
21+
)

examples/idempotency/sam.yaml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Transform: AWS::Serverless-2016-10-31
2+
Resources:
3+
IdempotencyTable:
4+
Type: AWS::DynamoDB::Table
5+
Properties:
6+
AttributeDefinitions:
7+
- AttributeName: id
8+
AttributeType: S
9+
KeySchema:
10+
- AttributeName: id
11+
KeyType: HASH
12+
TimeToLiveSpecification:
13+
AttributeName: expiration
14+
Enabled: true
15+
BillingMode: PAY_PER_REQUEST
16+
17+
HelloWorldFunction:
18+
Type: AWS::Serverless::Function
19+
Properties:
20+
Runtime: python3.9
21+
Handler: app.py
22+
Policies:
23+
- Statement:
24+
- Sid: AllowDynamodbReadWrite
25+
Effect: Allow
26+
Action:
27+
- dynamodb:PutItem
28+
- dynamodb:GetItem
29+
- dynamodb:UpdateItem
30+
- dynamodb:DeleteItem
31+
Resource: !GetAtt IdempotencyTable.Arn

0 commit comments

Comments
 (0)