question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature request: support BatchInvoke when using AppSyncResolver

See original GitHub issue

Use case

Currently, an event is expected to be of type Dict[str,Any]. When using ‘BatchInvoke’ in a direct lambda resolver, the event becomes type List[Dict[str,Any]]. Hence, it is not possible to route the event to the expected resolver.

A use case is given by the official dev guide. Without the ability to use ‘BatchInvoke’ users will run into n + 1 problems, when using appsync-GraphQl.

Solution/User Experience

I am not 100% sure how a good solution would look like. Maybe it would be possible to use the AppSyncResolverEvent to handle one individual event, or a list of events. Given the described use case, the events should usually only diverge in the source.

Alternative solutions

No response

Acknowledgment

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
cponfick-bhscommented, Jul 18, 2022

hey @cponfick-bhs thank you for raising this, it makes sense. We’d need to do some digging and experiment with a few UX - would you be able to share a sample event with batch resolver?

Sure, I can construct an example. Assume the following GraphQl-Schema:

type Query{
   getProducts(): [Product]
}

type Product {
   name: String
   comments: [Comment!]
}

type Comment {
   text: String!
   userName: String!
}

The resolvers are attached as follows:

  • ParentTypeName: Query and FieldName: getProducts
  • ParentTypeName: Product and FieldName: comments

When using the usual Invoke, Appsync creates one event for the Query:

{
       "arguments": {},
       "identity": {
           "claims": {},
           "defaultAuthStrategy": "ALLOW",
           "groups": null,
           "issuer": "",
           "sourceIp": [""],
           "sub": "",
           "username": ""
       },
       "source": null,
       "request": {
           "headers": {},
           "domainName": null
       },
       "prev": null,
       "info": {
           "selectionSetList":["EVERYTHING"],
           "fieldName": "getProducts",
           "parentTypeName": "Query",
           "variables": {}
       },
       "stash": {}
   }

and for every product, an additional event and lambda invocation for the comments:

{
        "arguments": {},
        "identity": {
            "claims": {},
            "defaultAuthStrategy": "ALLOW",
            "groups": null,
            "issuer": "",
            "sourceIp": [""],
            "sub": "",
            "username": ""
        },
        "source": {THE FIELDS OF THE PRODUCT},
        "request": {
            "headers": {},
            "domainName": null
        },
        "prev": null,
        "info": {
            "selectionSetList":["EVERYTHING"],
            "fieldName": "comments",
            "parentTypeName": "Product",
            "variables": {}
        },
        "stash": {}
    }

Hence, for every Product an additional lambda invoke is executed.

When using “BatchInvoke”, the first event remains the same, but the calls that return the comments for each product are “merged” into one lambda call with a list of events.

[{
        "arguments": {},
        "identity": {
            "claims": {},
            "defaultAuthStrategy": "ALLOW",
            "groups": null,
            "issuer": "",
            "sourceIp": [""],
            "sub": "",
            "username": ""
        },
        "source": {THE FIELDS OF THE PRODUCT 0},
        "request": {
            "headers": {},
            "domainName": null
        },
        "prev": null,
        "info": {
            "selectionSetList":["EVERYTHING"],
            "fieldName": "comments",
            "parentTypeName": "Product",
            "variables": {}
        },
        "stash": {}
    },
{
        "arguments": {},
        "identity": {
            "claims": {},
            "defaultAuthStrategy": "ALLOW",
            "groups": null,
            "issuer": "",
            "sourceIp": [""],
            "sub": "",
            "username": ""
        },
        "source": {THE FIELDS OF THE PRODUCT 1},
        "request": {
            "headers": {},
            "domainName": null
        },
        "prev": null,
        "info": {
            "selectionSetList":["EVERYTHING"],
            "fieldName": "comments",
            "parentTypeName": "Product",
            "variables": {}
        },
        "stash": {}
    },
{
        "arguments": {},
        "identity": {
            "claims": {},
            "defaultAuthStrategy": "ALLOW",
            "groups": null,
            "issuer": "",
            "sourceIp": [""],
            "sub": "",
            "username": ""
        },
        "source": {THE FIELDS OF THE PRODUCT 2},
        "request": {
            "headers": {},
            "domainName": null
        },
        "prev": null,
        "info": {
            "selectionSetList":["EVERYTHING"],
            "fieldName": "comments",
            "parentTypeName": "Product",
            "variables": {}
        },
        "stash": {}
    }]

Appsync now expects a list of responses associated with the corresponding sources. The fields of the events depend on the ResponseMappingTemplate used for the resolver.

@heitorlessa I hope this helps a bit.

Work Around

Currently, I work around this issue using the following AppSyncResolverEvent:

class MyAppSyncResolverEvent(appsync.AppSyncResolverEvent):
    def __init__(self, data: Union[dict, list]):
        if isinstance(data, dict):
            super().__init__(data)
        else:
            super().__init__(data[0])
            self._sources = [event.get('source', {}) for event in data]

    @property
    def arguments(self) -> Dict[str, Any]:
        if isinstance(self._data, dict):
            return self["arguments"]
        else:
            return {}

    @property
    def sources(self) -> List[Dict[str, Any]]:
        return self._sources

I must admit that this is not clean, but for now it works.

0reactions
thenamanpatwaricommented, Dec 21, 2022

+1 We had to build to a similar class as created by @cponfick-bhs

Read more comments on GitHub >

github_iconTop Results From Across the Web

Resolver mapping template reference for Lambda
BatchInvoke instructs AWS AppSync to batch requests for the current GraphQL field. operation is required. For Invoke , the resolved request mapping template ......
Read more >
The Benefits of Implementing Pre-Resolver Caching - Aircall
If you're using BatchInvoke , you should disable it and support both BatchInvoke and the unit one during your next rollout.
Read more >
Serverless Appsync Plugin
Deploy AppSync API's in minutes using this Serverless plugin. ... maxBatchSize: # maximum number of requests for BatchInvoke operations.
Read more >
GraphQL API - AWS Lambda Powertools for Python
A custom AppSync Resolver to bypass the use of Apache Velocity Template ... typing import List import requests from requests import Response ...
Read more >
Newest 'aws-appsync-resolver' Questions - Stack Overflow
I'm trying to mutate an object with the help of AppSync console. when i execute my query it executes without any error but...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found