Skip to content

Feature request: GraphQL API Event Handler #1166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 of 2 tasks
FilipPyrek opened this issue Nov 14, 2022 · 10 comments
Open
1 of 2 tasks

Feature request: GraphQL API Event Handler #1166

FilipPyrek opened this issue Nov 14, 2022 · 10 comments
Assignees
Labels
confirmed The scope is clear, ready for implementation event-handler This item relates to the Event Handler Utility feature-request This item refers to a feature request for an existing or new utility help-wanted We would really appreciate some support from community for this one

Comments

@FilipPyrek
Copy link

Use case

We discussed it here on Twitter with @dreamorosi and the outcome was that Lambda Powertools for Python already has this feature and this feature request is to re-implement similar feature also in Lambda Powertools for TypeScript.

The problem in our case is that when we use lambda in combination with GraphQL's nested resolvers feature (and mainly AppSync's implementation of nested resolvers) the lambda cold starts begin to stack-up which leads to couple of seconds of aggregated cold starts of all nested resolvers which is very unpleasant.

Solution/User Experience

GraphQL API handler in Lambda Powertools for Python has a resolver library which does the nested resolution inside a single lambda function and instead of creating the abstraction on infrastructure level, as AppSync does, it creates it on source code level. This resolution can be done thanks to informations from $ctx.info.

This way lambda resolvers would be only for top-level Mutation/Query fields and the rest would be done inside lambda function(s).

And it will reduce any cold starts to minimum. (= cold start of single lambda function)

Alternative solutions

1. Query all the data and return it all inside Query/Mutation top level field resolver lambda functions - this way we loose advantage of GraphQL where clients can select what part of data they are interested in, so we can query only the relevant data on backend. 

2. Create simple custom nasty resolver based on `if` statements to distinguish what fields the client is requesting - very nasty source code.

3. Try all possible optimisations to reduce the cold starts. That can be very hard and even in some cases teams can hit a limit where it's not possible to optimize any further..

Acknowledgment

@FilipPyrek FilipPyrek added the triage This item has not been triaged by a maintainer, please wait label Nov 14, 2022
@dreamorosi dreamorosi added need-customer-feedback Requires more customers feedback before making or revisiting a decision feature-request This item refers to a feature request for an existing or new utility discussing The issue needs to be discussed, elaborated, or refined and removed triage This item has not been triaged by a maintainer, please wait labels Nov 14, 2022
@dreamorosi
Copy link
Contributor

Hi @FilipPyrek thank you for opening this feature request!

As I said on Twitter, I think this is an interesting feature that we should consider adding to this library at some point.

At the moment we are focused on implementing Parameters and Idempotency, as well as finishing Lambda Layers and investigating ESM support.

I have added this feature request to the "Ideas" bucket and added the need-customer-feedback label. With this label we would like other readers to consider adding their use case as well as a 👍 to the issue. We'll use these datapoints during our next prioritisation.

@github-actions

This comment was marked as outdated.

@github-actions github-actions bot added the pending-close-response-required This issue will be closed soon unless the discussion moves forward label Feb 28, 2023
@FilipPyrek
Copy link
Author

This issue has not received a response in 2 weeks. If you still think there is a problem, please leave a comment to avoid the issue from automatically closing.

Still relevant

@dreamorosi dreamorosi removed the pending-close-response-required This issue will be closed soon unless the discussion moves forward label Feb 28, 2023
@dreamorosi
Copy link
Contributor

Apologies @FilipPyrek. Yesterday I set up some new automation to handle issues and this was caught in the crossfire by mistake.

Agree that the issue should stay open. I am working on a fix to the automation.

@github-actions

This comment was marked as off-topic.

@github-actions github-actions bot added the pending-close-response-required This issue will be closed soon unless the discussion moves forward label Mar 25, 2023
@FilipPyrek

This comment was marked as outdated.

@dreamorosi dreamorosi added need-more-information Requires more information before making any calls and removed pending-close-response-required This issue will be closed soon unless the discussion moves forward need-customer-feedback Requires more customers feedback before making or revisiting a decision labels Mar 25, 2023
@sthulb sthulb moved this from Backlog to Ideas in Powertools for AWS Lambda (TypeScript) Jun 19, 2023
@travishaby
Copy link

travishaby commented Aug 15, 2023

Very excited at the prospect of this feature! Have seen a few bad implementations of event routing for AppSync handlers out there and at my company, so it would be really great to have an open source one provided by AWS to standardize on so we can stop re-inventing the wheel 😄

@dreamorosi dreamorosi removed the need-more-information Requires more information before making any calls label Jan 30, 2025
@dreamorosi dreamorosi added the revisit-in-3-months Blocked issues/PRs that need to be revisited label Feb 10, 2025
@dreamorosi
Copy link
Contributor

dreamorosi commented May 15, 2025

We have added this item to our roadmap and we are looking to launch it before end of the year.

I am posting here a comment from #3885 to keep the lineage of the issue and people's reactions - the original message was written by @leandrodamascena:

Use case

Customers can use AWS Lambda with AWS AppSync to resolve GraphQL fields through two types of integrations:

  1. Direct Resolvers – In this approach, no Velocity Template Language (VTL) or JavaScript code is used to transform the payload before invoking the Lambda function. The Lambda receives the full, unmodified GraphQL request context directly from AppSync.

  2. Custom Resolvers – This method involves using VTL or JavaScript to modify or transform the request and/or response payloads before the Lambda function is invoked.

While Powertools for AWS can work in some cases with custom resolvers, it is recommended to use Powertools only with direct resolvers. This is because custom resolvers often modify or remove fields in the payload that Powertools depends on to function correctly. Since there is no guaranteed way for customers to ensure these necessary fields remain intact when using custom resolvers, relying on direct resolvers provides a more reliable and consistent integration and avoid Powertools's maintainers trying to identifying issues created by custom integrations.

Payload

Among other fields that I removed just to simplify the example, the payload that AppSync sends to Lambda is something like this:

{
  "arguments": {
    "id": "my identifier"
  },
  "identity": {...},
  "source": null,
  "request": {...},
  "prev": null,
  "info": {
    "selectionSetList": [
      "id",
      "field1",
      "field2"
    ],
    "selectionSetGraphQL": "{\n  id\n  field1\n  field2\n}",
    "parentTypeName": "Mutation",
    "fieldName": "createSomething",
    "variables": {}
  },
  "stash": {}
}

Registering routes

When resolving an event, Powertools needs to examine the fieldName/parentTypeName fields, being fieldName mandatory and parentTypeName optional when creating the routing strategy. In Powertools Python it's something like this:

with fieldName

@app.resolver(field_name="listLocations")
def get_locations(name: str, description: str = "") -> List[Location]: 
    return [{"name": name, "description": description}]

with fieldName and parentTypeName

@app.resolver(type_name="Query", field_name="listLocations")
def get_locations(name: str, description: str = "") -> List[Location]: 
    return [{"name": name, "description": description}]

Transforming arguments into function variables

When working with GraphQL queries like the one below, customers may want to access the post_id variable to query their database or anything related to that. So AppSync sends the Payload with the arguments field and the corresponding name and values, it is recommended to inject those arguments as a function variable.

query

query MyQuery {
  getPost(post_id: "2") {
    id
    title
  }
}

payload

{
"arguments": {
    "post_id": "2"
  },
...
}

resolver

@app.resolver(type_name="Query", field_name="getPost")
def get_locations(post_id: int) -> List[Post]:  # match GraphQL Query arguments
    #do stuff
    return [{"name": name, "description": description}]

Resolver not found

Unlike AppSync events, when the resolver is not found, Powertools should throw a ResolverNotFound exception because the backend of a GraphQL API (Lambda in this case) is expected to perform some operation and return a result to the API (AppSync).

Single and batch resolvers

Customers who want to solve the N+1 problem can use batch resolvers to receive multiple events in the same payload and then resolve them. The keys difference between single resolvers and batch resolvers are:

1/ The return always must be a list and the list of results must match the size and order of the request payload entries so that AWS AppSync can match the results accordingly.
2/ Aggregation - Similar to AppSync events, customers may want to process the entire batch at once or iterate over all items in the batch.
3/ Throw on exception - Customers should have the option to throw an exception or not during batch processing. When customers choose not to throw an exception, Powertools should return None for failed items.

Since Powertools TypeScript already has the whole strategy for registering and resolving routes for AppSync Events, I think the adaptation would be to change the payload fields and deal with batch resolvers, but the other things are more or less the same. I'm more than happy to do a session with you to demonstrate how it works in Powertools Python.


In addition to the above, I would like to also propose to align the methods to a similar experience to what's already present in AppSync Events and REST Event Handlers and have:

@app.query(field_name="getPost")
def get_post(post_id: int) -> List[Post]:  # match GraphQL Query arguments
    #do stuff
    return [{"name": name, "description": description}]

@app.mutation(field_name="putPost")
def put_post(post_content: str, post_title: str) -> Post:  # match GraphQL Query arguments
    #do stuff

@app.subscribe(field_name="PostChange")
def get_locations() -> boolean:
    #do stuff

In terms of concrete plans for the implementation - we can most likely use the same code architecture as the one under packages/event-handler/appsync-events but handle the routing and request/response according to AppSync GraphQL requirements and using the implementation in Powertools for AWS (Python).

@dreamorosi dreamorosi added help-wanted We would really appreciate some support from community for this one confirmed The scope is clear, ready for implementation and removed discussing The issue needs to be discussed, elaborated, or refined revisit-in-3-months Blocked issues/PRs that need to be revisited labels May 15, 2025
@dreamorosi dreamorosi moved this from Ideas to Backlog in Powertools for AWS Lambda (TypeScript) May 15, 2025
@dreamorosi dreamorosi marked this as a duplicate of #3885 May 15, 2025
@dreamorosi dreamorosi added the event-handler This item relates to the Event Handler Utility label May 15, 2025
@arnabrahman
Copy link
Contributor

I am willing to contribute.

@dreamorosi
Copy link
Contributor

Hi @arnabrahman, that's amazing - thank you!

I'll assign the issue to you.

I expect this to be a significant amount of work, so I'd suggest you do an initial assessment and then let me know if you have any questions. If not, we can iterate in the PRs as usual.

It's also ok to split the work in multiple PRs if you think it makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed The scope is clear, ready for implementation event-handler This item relates to the Event Handler Utility feature-request This item refers to a feature request for an existing or new utility help-wanted We would really appreciate some support from community for this one
Projects
Development

No branches or pull requests

4 participants