You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We have a client-server web application.
The server side is built using AWS SAM with an API Gateway and multiple lambda functions. The server side laso executes an asynchronous pipeline.
We want to prevent cases where a duplicated message is sent to the backend and triggers the pipeline multiple time.
We also want to notify the user if a duplication occurred, and hence his request was not processed, this is the main issue.
Solution/User Experience
Add an X-Idempotent: True header to the response.
Alternative solutions
A hook for changing the response (e.g. adding a field).
Thanks for opening your first issue here! We'll come back to you as soon as we can.
In the meantime, check out the #python channel on our AWS Lambda Powertools Discord: Invite link
Interesting - always interesting to hear more about different solution needs. The complexity around this is also that the idempotent handler runs as a Python decorator either on the lambda_handler or other function in the call chain and has zero knowledge of where it fits or where the event came from, which should be maintained.
We must also be clear that Idempotent responses are not just about API responses, so feel this is not a standard library feature that Powertools should provide "out of the box" in a canned way, as indicated by the initial request.
I think a mechanism for "intercepting" the idempotent response is useful and would provide @royassis with the capability to implement the customisation for his use case. This also provides future customisation opportunities for anyone. How the response is sent back is entirely up to the developer within the context of their solution, which is where control needs to be placed for any customisation of an idempotent response (in developer-maintained code).
Here is an example of what I am thinking using Powertools API Gateway resolver and an Idempotent Intercept at the API method level, which makes sense for an API use case (Note: I know an idempotent decorator for API methods is not really a thing today - but it should be and would be perfect for this use):
idempotent_api() decorator is created and works (just thinking about it right now) - I see this as a useful thing and applying idempotency at the API resolution layer is easy to reason about if you have a monolithic API
idempotency interceptor - would require an internal decorator to intercept idempotency response and a change to idempotency internally to return the payload AND whether the result was read from the Cache. This shouldn't break anything as the idempotency internal interceptor would hide this fact from existing use-cases.
idempotency itself may need a newer class to unwrap API Response Objects for storing in the PersistenceProvider.
Haven't forgotten, our security enhancements are taking longer than I expected (to give you a glimpse of the complexity), plus COVID took a hit on me (back now 🙌).
Adding help wanted as we'd appreciate anyone taking a stab at this - @royassis feel free to give it a try and we can help refine it.
What we seem to be after:
A mechanism to call an user-defined function for requests that are idempotent (think Cache: Hit -> hook)
Optionally a new route parameter or decorator to more easily make a route idempotent and include a header as originally asked
I say, optionally, because it's only a matter of time until someone needs something else, and eventually it'll bloat in complexity and cognitive load to remember many parameters.
A hook-like would be useful to give full flexibility for customers to do as they please, and it'd only be called IF the transaction is idempotent - making it suitable for all sorts of use cases.
What we don't want
We should not modify the idempotent response of a decorated method. I think this is a better job for a deserializer (cc @ran-isenberg) in case you want to deserialize the JSON serialized idempotent response into something that better match your method return signature (e.g., Idempotency, Dataclass, Custom, etc.). Happy to hear counter arguments and learn from it too, specially how tricky it'd be from the typing system.
@heitorlessa I was thinking of adding a JSON serializer/deserializer optional arguments to the config class. That way I would have total control of the way it is saved to the database and loaded back from. What do you think?
resurfacing this potentially to the next iteration cycle -- first order is to get the new Parameters/Secrets Put and AppSync Batch invoke long-standing ones.
Would like to add my use for this feature in case it helps with the development of this.
I have a statemachine that invokes a lambda that does a lookup and in the next step, it sends an email with SES. In order to utilize the idempotency utility, I would need some way to modify the lambda response, so I could add a choice step in my statemachine to decide to send the email or end the execution.
Something like the proposed idea above that can intercept the response and have access to the Record status would work great. @idempotent(persistence_store=persistence_layer, ... response_intercept=payment_intercept)
Then I can just look for the Record status in the response from my lambda to decide whether to continue execution or end.
Hello everyone! I'm updating this issue with some decisions we made regard the Developer Experience and request flow.
Response hooks
We are introducing support for hooks in the Idempotency utility. Customers can now create a function and pass it as the hook in the IdempotencyConfig. This hook will be invoked every time a request is idempotent. If the request is not idempotent, the hook will not execute.
This hook will be able to access both: the response from the invocation and the DataRecord from the Idempotency Store. This means customers can inject new keys into the response or utilize attributes from the DataRecord to log information or create metrics, for example.
Persistent Store
This hook is compatible with any Persistent Store. Currently, we support DynamoDB and Redis for this implementation.
Handling exceptions
Customers are responsible for handling exceptions when using Response hooks. During the implementation of this new feature, we considered the possibility of implementing soft failure or raising warnings to ignore hook execution. For instance, this approach could prevent customers from having exceptions in the hook, thereby avoiding potential malfunctions in the Lambda function execution. However, we opted against this approach because it could result in unexpected behaviors and potentially breaking the contract that customers expect for Response hooks. Let me provide some context with an example.
Consider a scenario where a customer creates a hook to inject a specific key into the response when the operation is idempotent and wants to use it to trigger an alert for third-party integration. The code might appear like this:
defmy_response_hook(response: Dict, idempotent_data: DataRecord) ->Dict:
# Return inserted Header data into the Idempotent Responseresponse["x-idempotent-key"] =idempotent_data.idempotency_keyimagine_an_exception_here()
# expiry_timestamp could be None so include if setexpiry_timestamp=idempotent_data.expiry_timestampifexpiry_timestamp:
expiry_time=datetime.datetime.fromtimestamp(int(expiry_timestamp))
response["x-idempotent-expiration"] =expiry_time.isoformat()
# Must return the response herereturnresponsedynamodb=DynamoDBPersistenceLayer(table_name="IdempotencyTable")
config=IdempotencyConfig(response_hook=my_response_hook)
@idempotent_function(data_keyword_argument="order", config=config, persistence_store=dynamodb)defprocess_order(order: dict) ->dict: ...
deflambda_handler(event: dict, context: LambdaContext):
config.register_lambda_context(context) # see Lambda timeouts sectionprocess_order=process_order(event.get("order"))
ifprocess_order["x-idempotent-key"] isnotNone:
trigger_some_sns_alert()
If we choose to ignore the hook execution exception and only raise a warning instead of propagating the exception, customers may encounter difficulties in debugging. They may struggle to understand why the if process_order["x-idempotent-key"] is not None: condition is failing and why the x-idempotent-key doesn't exist in the response.
Documentation
The documentation will be updated to inform customers on the best practices for using Response hooks. Additionally, it will feature an example demonstrating how to utilize Response hooks and effectively handle exceptions.
We welcome any suggestions or ideas for improvements regarding this matter.
Thank you to everyone who contributed their ideas.
Activity
boring-cyborg commentedon Apr 25, 2023
Thanks for opening your first issue here! We'll come back to you as soon as we can.
In the meantime, check out the #python channel on our AWS Lambda Powertools Discord: Invite link
heitorlessa commentedon Apr 25, 2023
cc @walmsles as I'm sure you'll have ideas on DX here ;)
leandrodamascena commentedon Apr 25, 2023
Original discussion: #2158
walmsles commentedon Apr 25, 2023
Interesting - always interesting to hear more about different solution needs. The complexity around this is also that the idempotent handler runs as a Python decorator either on the lambda_handler or other function in the call chain and has zero knowledge of where it fits or where the event came from, which should be maintained.
We must also be clear that Idempotent responses are not just about API responses, so feel this is not a standard library feature that Powertools should provide "out of the box" in a canned way, as indicated by the initial request.
I think a mechanism for "intercepting" the idempotent response is useful and would provide @royassis with the capability to implement the customisation for his use case. This also provides future customisation opportunities for anyone. How the response is sent back is entirely up to the developer within the context of their solution, which is where control needs to be placed for any customisation of an idempotent response (in developer-maintained code).
Here is an example of what I am thinking using Powertools API Gateway resolver and an Idempotent Intercept at the API method level, which makes sense for an API use case (Note: I know an idempotent decorator for API methods is not really a thing today - but it should be and would be perfect for this use):
The above implies:
heitorlessa commentedon May 1, 2023
got some security enhancements I need to prioritize first, but adding this to be looked after in the next iteration (next week).
Thank you everyone!!
heitorlessa commentedon Jun 8, 2023
Haven't forgotten, our security enhancements are taking longer than I expected (to give you a glimpse of the complexity), plus COVID took a hit on me (back now 🙌).
Adding
help wantedas we'd appreciate anyone taking a stab at this - @royassis feel free to give it a try and we can help refine it.What we seem to be after:
I say, optionally, because it's only a matter of time until someone needs something else, and eventually it'll bloat in complexity and cognitive load to remember many parameters.
A hook-like would be useful to give full flexibility for customers to do as they please, and it'd only be called IF the transaction is idempotent - making it suitable for all sorts of use cases.
What we don't want
We should not modify the idempotent response of a decorated method. I think this is a better job for a deserializer (cc @ran-isenberg) in case you want to deserialize the JSON serialized idempotent response into something that better match your method return signature (e.g., Idempotency, Dataclass, Custom, etc.). Happy to hear counter arguments and learn from it too, specially how tricky it'd be from the typing system.
ran-isenberg commentedon Jun 9, 2023
@heitorlessa I was thinking of adding a JSON serializer/deserializer optional arguments to the config class. That way I would have total control of the way it is saved to the database and loaded back from. What do you think?
9 remaining items
heitorlessa commentedon Feb 21, 2024
resurfacing this potentially to the next iteration cycle -- first order is to get the new Parameters/Secrets Put and AppSync Batch invoke long-standing ones.
TonySherman commentedon Feb 21, 2024
Would like to add my use for this feature in case it helps with the development of this.
I have a statemachine that invokes a lambda that does a lookup and in the next step, it sends an email with SES. In order to utilize the idempotency utility, I would need some way to modify the lambda response, so I could add a choice step in my statemachine to decide to send the email or end the execution.
Something like the proposed idea above that can intercept the response and have access to the Record status would work great.
@idempotent(persistence_store=persistence_layer, ... response_intercept=payment_intercept)Then I can just look for the Record status in the response from my lambda to decide whether to continue execution or end.
walmsles commentedon Mar 31, 2024
Just want to mention that there are 2 parts to this issue from the discussions.
The second part - I am considering the best way to integrate and will do a POC in the coming days.
leandrodamascena commentedon Apr 4, 2024
Hello everyone! I'm updating this issue with some decisions we made regard the Developer Experience and request flow.
Response hooks
We are introducing support for hooks in the Idempotency utility. Customers can now create a function and pass it as the hook in the IdempotencyConfig. This hook will be invoked every time a request is idempotent. If the request is not idempotent, the hook will not execute.
Manipulating response
This hook will be able to access both: the response from the invocation and the
DataRecordfrom the Idempotency Store. This means customers can inject new keys into the response or utilize attributes from the DataRecord to log information or create metrics, for example.Persistent Store
This hook is compatible with any Persistent Store. Currently, we support DynamoDB and Redis for this implementation.
Handling exceptions
Customers are responsible for handling exceptions when using Response hooks. During the implementation of this new feature, we considered the possibility of implementing soft failure or raising warnings to ignore hook execution. For instance, this approach could prevent customers from having exceptions in the hook, thereby avoiding potential malfunctions in the Lambda function execution. However, we opted against this approach because it could result in unexpected behaviors and potentially breaking the contract that customers expect for
Response hooks. Let me provide some context with an example.Consider a scenario where a customer creates a hook to inject a specific key into the response when the operation is idempotent and wants to use it to trigger an alert for third-party integration. The code might appear like this:
If we choose to ignore the hook execution exception and only raise a warning instead of propagating the exception, customers may encounter difficulties in debugging. They may struggle to understand why the
if process_order["x-idempotent-key"] is not None:condition is failing and why thex-idempotent-keydoesn't exist in the response.Documentation
The documentation will be updated to inform customers on the best practices for using Response hooks. Additionally, it will feature an example demonstrating how to utilize Response hooks and effectively handle exceptions.
We welcome any suggestions or ideas for improvements regarding this matter.
Thank you to everyone who contributed their ideas.
leandrodamascena commentedon Apr 4, 2024
Hi @walmsles! After we merge PR #4037, I will open another issue to implement the Response hooks in the Event Handler.
github-actions commentedon Apr 4, 2024
This issue is now closed. Please be mindful that future comments are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.