AWS AppSync: pass arguments from parent resolver to children
Asked Answered
I

6

16

In AWS AppSync, arguments send on the main query don't seem to be forwarded to all children resolvers.

type Query {
  article(id: String!, consistentRead: Boolean): Article
  book(id: String!, consistentRead: Boolean): Book
}

type Article {
  title: String!
  id: String!
}

type Book {
  articleIds: [String]!
  articles: [Article]!
  id: String!
}

when I call:

query GetBook {
  book(id: 123, consistentRead: true) {
    articles {
      title
    }
  }
}

the first query to get the book receives the consistentRead param in $context.arguments, but the subsequent query to retrieve the article does not. ($context.arguments is empty)

I also tried articles(consistentRead: Boolean): [Article]! inside book but no luck.

Does anyone know if it's possible in AppSync to pass arguments to all queries part of the same request?

Imponderable answered 26/9, 2018 at 7:14 Comment(1)
This workaround using request headers work with or without pipeline https://mcmap.net/q/734283/-aws-appsync-pass-arguments-from-parent-resolver-to-children it's ugly but it's the only solution I know that allows passing information to ALL sub-resolversJustis
P
16

It is possible to pass arguments from parent to child via the response. Let me explain ...

AppSync has several containers inside $context:

  • arguments
  • stash
  • source

arguments and stash are always cleared before invoking a child resolver as evident from these Cloudwatch logs:

At the very end of the parent execution - arguments and stash data are present.

{
    "errors": [],
    "mappingTemplateType": "After Mapping",
    "path": "[getLatestDeviceState]",
    "resolverArn": "arn:aws:appsync:us-east-1:xxx:apis/yyy/types/Query/fields/getLatestDeviceState",
    "context": {
        "arguments": {
            "device": "ddddd"
        },
        "prev": {
            "result": {
                "items": [
                    {
                        "version": "849",
                        "device": "ddddd",
                        "timestamp": "2019-01-29T12:18:34.504+13:00"
                    }
                ]
            }
        },
        "stash": {"testKey": "testValue"},
        "outErrors": []
    },
    "fieldInError": false
}

and then at the very beginning of the child resolver - arguments and stash are always blank.

{
"errors": [],
"mappingTemplateType": "Before Mapping",
"path": "[getLatestDeviceState, media]",
"resolverArn": "arn:aws:appsync:us-east-1:yyy:apis/xxx/types/DeviceStatePRODConnection/fields/media",
"context": {
    "arguments": {},
    "source": {
        "items": [
            {
                "version": "849",
                "device": "ddddd",
                "timestamp": "2019-01-29T12:18:34.504+13:00"
            }
        ]
    },
    "stash": {},
    "outErrors": []
},
"fieldInError": false
}

Workaround 1 - get the argument from the previous result.

In the example above device is always present in the response of the parent resolver, so I inserted

#set($device = $util.defaultIfNullOrBlank($ctx.args.device, $ctx.source.items[0].device))

into the request mapping template of the child resolver. It will try to get the ID it needs from the arguments and then fall back onto the previous result.

Workaround 2 - add the argument to the parent response

Modify your parent resolver response template to include the arguments:

{
    "items": $utils.toJson($context.result.items),
    "device": "${ctx.args.device}"
}

and then retrieve it in the request mapping template of the child the same way as in the first workaround.

Predominate answered 30/1, 2019 at 3:52 Comment(2)
@Imponderable - What solution did you find? Please, check this as the correct answer if you agree. This area is still very badly documented in AWS. This may help others save time.Predominate
To add to this, by default cloudwatch logs aren't activated on appsync. Once you've activated it and "Field resolver log level" is set to ALL, you can clearly see the contents of the context object that the documents fail to address in detail. From there you can do many other things.Shun
B
8

To achieve availability across all related resolvers (nested or those collection-entity related) for me was fine Workaround 2 (tnx Max for such a good answer) but just for child resolvers. In another case when I needed to resolve entities from collection query (contains other fields besides entity) property added to response mapping template wasn't available anymore. So my solution was to set it to request headers:

##Set parent query profile parameter to headers to achieve availability accross related resolvers.
#set( $headers = $context.request.headers )
$util.qr($headers.put("profile", $util.defaultIfNullOrBlank($context.args.profile, "default")))

And read this value from your nested/other request mapping templates:

#set($profile = $ctx.request.headers.profile)

This makes the parent argument available wherever I need it between related resolvers. In your case, it would be 'device' and some default value or without that part if not needed.

Baccy answered 25/9, 2019 at 7:36 Comment(4)
Good find! But it looks like a hack.. I'm not sure modifying request headers is an intended AWS feature.. But thanks to this workaround I can propagate values to sub-resolversJustis
See github.com/aws/aws-appsync-community/issues/…Justis
Glad it helps. I was desperate to achieve this but couldn't find better workaround yet, so hope AWS will implement appropriate way to handle it soon.Baccy
Beauty of this answer is that it will work not only for Children but for Grand Children as well :) .. And that too without passing arguments from One layer to other. Just brilliant!Henshaw
A
0

Add this to BookQuery Response Mapping Template

#set( $book = $ctx.result )
#set($Articles = []);
#foreach($article in $book.articles)
    #set( $newArticle = $article )
    $util.qr($newArticle.put("bookID", $book.id))
    $util.qr($Articles.add($newArticle))
#end
$util.qr($book.put("articles", $Articles))
$util.toJson($book)

Now, every article will have bookID

Alexandrina answered 2/2, 2020 at 6:28 Comment(0)
D
-1

You should be able to find consistentRead in $context.info.variables ($context.info.variables.consistentRead): https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html#aws-appsync-resolver-context-reference-info

Delafuente answered 1/12, 2020 at 14:1 Comment(0)
D
-3

You don't need to pass arguments to sub-query. Base on your schema and use-case, I think you can adjust your schema like below to have a relationship between Author and Book

type Author {
    # parent's id
    bookID: ID!
    # author id
    id: ID!
    name: String!
}

type Book {
    id: ID!
    title: String!
    author: [Author]!
}

type Mutation {
    insertAuthor(bookID: ID!, id: ID!, name: String!): Author
    insertBook(id: ID!, title: String!): Book
}

type Query {
    getBook(id: ID!): Book
}

- Create table Author with Author.bookID as a primary key and Author.id as a sort key
- Create table Book with Book.id as a primary key

Then, you have to attach a resolver for Book.author

enter image description here

And here is a resolver for insertAuthor mutation

{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        "bookID" : $util.dynamodb.toDynamoDBJson($ctx.args.bookID),
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
    },
    "attributeValues" : {
        "name" : $util.dynamodb.toDynamoDBJson($ctx.args.name)
    }
}

And when you do query getBook you will get a list of author that has the same book id as below

enter image description here

Dexamyl answered 26/9, 2018 at 12:24 Comment(0)
M
-4

Simply in the child use $ctx.source.id where id is the parameter you need reference from the parent.

Moeller answered 9/5, 2019 at 23:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.