Skip to content

Commit 427d365

Browse files
committed
docs(idempotency): cleanup serialization, fields subset, move batch to new common use cases section
Signed-off-by: heitorlessa <lessa@amazon.co.uk>
1 parent 971677f commit 427d365

File tree

2 files changed

+42
-46
lines changed

2 files changed

+42
-46
lines changed

docs/utilities/idempotency.md

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,11 @@ By default, `idempotent_function` serializes, stores, and returns your annotated
191191

192192
The output serializer supports any JSON serializable data, **Python Dataclasses** and **Pydantic Models**.
193193

194-
!!! info "When using the `output_serializer` parameter, the data will continue to be stored in DynamoDB as a JSON object."
194+
!!! info "When using the `output_serializer` parameter, the data will continue to be stored in DynamoDB as a JSON string."
195195

196196
=== "Pydantic"
197197

198-
You can use `PydanticSerializer` to automatically serialize what's retrieved from the persistent storage based on the return type annotated.
198+
Use `PydanticSerializer` to automatically serialize what's retrieved from the persistent storage based on the return type annotated.
199199

200200
=== "Inferring via the return type"
201201

@@ -215,7 +215,7 @@ The output serializer supports any JSON serializable data, **Python Dataclasses*
215215

216216
=== "Dataclasses"
217217

218-
You can use `DataclassSerializer` to automatically serialize what's retrieved from the persistent storage based on the return type annotated.
218+
Use `DataclassSerializer` to automatically serialize what's retrieved from the persistent storage based on the return type annotated.
219219

220220
=== "Inferring via the return type"
221221

@@ -235,7 +235,7 @@ The output serializer supports any JSON serializable data, **Python Dataclasses*
235235

236236
=== "Any type"
237237

238-
You can use `CustomDictSerializer` to have full control over the serialization process for any type. It expects two functions:
238+
Use `CustomDictSerializer` to have full control over the serialization process for any type. It expects two functions:
239239

240240
* **to_dict**. Function to convert any type to a JSON serializable dictionary before it saves into the persistent storage.
241241
* **from_dict**. Function to convert from a dictionary retrieved from persistent storage and serialize in its original form.
@@ -248,42 +248,20 @@ The output serializer supports any JSON serializable data, **Python Dataclasses*
248248
2. This function does the following <br><br>**1**. Receives the dictionary saved into the persistent storage <br>**1** Serializes to `OrderOutput` before `@idempotent` returns back to the caller.
249249
3. This serializer receives both functions so it knows who to call when to serialize to and from dictionary.
250250

251-
#### Batch integration
252-
253-
You can can easily integrate with [Batch utility](batch.md){target="_blank"} via context manager. This ensures that you process each record in an idempotent manner, and guard against a [Lambda timeout](#lambda-timeouts) idempotent situation.
254-
255-
???+ "Choosing an unique batch record attribute"
256-
In this example, we choose `messageId` as our idempotency key since we know it'll be unique.
257-
258-
Depending on your use case, it might be more accurate [to choose another field](#choosing-a-payload-subset-for-idempotency) your producer intentionally set to define uniqueness.
259-
260-
=== "Integration with Batch Processor"
261-
262-
```python hl_lines="2 12 16 20 31 35 37"
263-
--8<-- "examples/idempotency/src/integrate_idempotency_with_batch_processor.py"
264-
```
265-
266-
=== "Sample event"
267-
268-
```json hl_lines="4"
269-
--8<-- "examples/idempotency/src/integrate_idempotency_with_batch_processor_payload.json"
270-
```
271-
272251
### Choosing a payload subset for idempotency
273252

274253
???+ tip "Tip: Dealing with always changing payloads"
275254
When dealing with a more elaborate payload, where parts of the payload always change, you should use **`event_key_jmespath`** parameter.
276255

277-
Use [`IdempotencyConfig`](#customizing-the-default-behavior) to instruct the idempotent decorator to only use a portion of your payload to verify whether a request is idempotent, and therefore it should not be retried.
256+
Use [`IdempotencyConfig`](#customizing-the-default-behavior)'s **`event_key_jmespath`** parameter to select one or more payload parts as your idempotency key.
278257

279258
> **Payment scenario**
280259
281260
In this example, we have a Lambda handler that creates a payment for a user subscribing to a product. We want to ensure that we don't accidentally charge our customer by subscribing them more than once.
282261

283-
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.
262+
Imagine the function runs successfully, but the client never receives the response due to a connection issue. It is safe to immediately retry in this instance, as the idempotent decorator will return a previously saved response.
284263

285-
**What we want here** is to instruct Idempotency to use `user_id` and `product_id` fields from our incoming payload as our idempotency key.
286-
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.
264+
**We want** to use `user_id` and `product_id` fields as our idempotency key. If we were to treat the entire request as our idempotency key, a simple HTTP header change would cause our function to run again.
287265

288266
???+ tip "Deserializing JSON strings in payloads for increased accuracy."
289267
The payload extracted by the `event_key_jmespath` is treated as a string by default.
@@ -472,6 +450,29 @@ You can customize attribute names when instantiating `RedisCachePersistenceLayer
472450
--8<-- "examples/idempotency/src/customize_persistence_layer_redis.py"
473451
```
474452

453+
### Common use cases
454+
455+
#### Batch integration
456+
457+
You can can easily integrate with [Batch](batch.md){target="_blank"} with the [idempotent_function decorator](#idempotent_function-decorator) to handle idempotency per message/record in a given batch.
458+
459+
???+ "Choosing an unique batch record attribute"
460+
In this example, we choose `messageId` as our idempotency key since we know it'll be unique.
461+
462+
Depending on your use case, it might be more accurate [to choose another field](#choosing-a-payload-subset-for-idempotency) your producer intentionally set to define uniqueness.
463+
464+
=== "Integration with Batch Processor"
465+
466+
```python title="integrate_idempotency_with_batch_processor.py" hl_lines="3 16 19 25 27"
467+
--8<-- "examples/idempotency/src/integrate_idempotency_with_batch_processor.py"
468+
```
469+
470+
=== "Sample event"
471+
472+
```json title="integrate_idempotency_with_batch_processor_payload.json" hl_lines="4"
473+
--8<-- "examples/idempotency/src/integrate_idempotency_with_batch_processor_payload.json"
474+
```
475+
475476
### Idempotency request flow
476477

477478
The following sequence diagrams explain how the Idempotency feature behaves under different scenarios.
Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from aws_lambda_powertools import Logger
2-
from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType
1+
import os
2+
3+
from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response
34
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord
45
from aws_lambda_powertools.utilities.idempotency import (
56
DynamoDBPersistenceLayer,
@@ -8,13 +9,11 @@
89
)
910
from aws_lambda_powertools.utilities.typing import LambdaContext
1011

11-
logger = Logger()
1212
processor = BatchProcessor(event_type=EventType.SQS)
1313

14-
dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
15-
config = IdempotencyConfig(
16-
event_key_jmespath="messageId", # see Choosing a payload subset section
17-
)
14+
table = os.getenv("IDEMPOTENCY_TABLE")
15+
dynamodb = DynamoDBPersistenceLayer(table_name=table)
16+
config = IdempotencyConfig(event_key_jmespath="messageId")
1817

1918

2019
@idempotent_function(data_keyword_argument="record", config=config, persistence_store=dynamodb)
@@ -25,13 +24,9 @@ def record_handler(record: SQSRecord):
2524
def lambda_handler(event: SQSRecord, context: LambdaContext):
2625
config.register_lambda_context(context) # see Lambda timeouts section
2726

28-
# with Lambda context registered for Idempotency
29-
# we can now kick in the Bach processing logic
30-
batch = event["Records"]
31-
with processor(records=batch, handler=record_handler):
32-
# in case you want to access each record processed by your record_handler
33-
# otherwise ignore the result variable assignment
34-
processed_messages = processor.process()
35-
logger.info(processed_messages)
36-
37-
return processor.response()
27+
return process_partial_response(
28+
event=event,
29+
context=context,
30+
processor=processor,
31+
record_handler=record_handler,
32+
)

0 commit comments

Comments
 (0)