rygo6
rygo6

Reputation: 1989

Map different Sort Key responses to Appsync Schema values

So here is my schema:

type Model {
    PartitionKey: ID!
    Name: String
    Version: Int
    FBX: String
    # ms since epoch
    CreatedAt: AWSTimestamp
    Description: String
    Tags: [String]
}

type Query {
    getAllModels(count: Int, nextToken: String): PaginatedModels!
}

type PaginatedModels {
    models: [Model!]!
    nextToken: String
}

I would like to call 'getAllModels' and have all of it's data, and all of it's tags be filled in.

But here is the thing. Tags are stored via sort keys. Like so

PartionKey | SortKey
Model-0    | Model-0
Model-0    | Tag-Tree
Model-0    | Tag-Building

Is it possible to transform the 'Tag' sort keys into the Tags: [String] array in the schema via a DynamoDB resolver? Or must I do something extra fancy through a lambda? Or is there a smarter way to do this?

Upvotes: 0

Views: 903

Answers (1)

mparis
mparis

Reputation: 3683

To clarify, are you storing objects like this in DynamoDB:

{ PartitionKey (HASH), Tag (SortKey), Name, Version, FBX, CreatedAt, Description }

and using a DynamoDB Query operation to fetch all rows for a given HashKey.

Query #PartitionKey = :PartitionKey

and getting back a list of objects some of which have a different "Tag" value and one of which is "Model-0" (aka the same value as the partition key) and I assume that record contains all other values for the record. E.G.

[
  { PartitionKey, Tag: 'ValueOfPartitionKey', Name, Version, FBX, CreatedAt, ... },
  { PartitionKey, Tag: 'Tag-Tree' },
  { PartitionKey: Tag: 'Tag-Building' }
]

You can definitely write resolver logic without too much hassle that reduces the list of model objects into a single object with a list of "Tags". Let's start with a single item and see how to implement a getModel(id: ID!): Model query:

First define the response mapping template that will get all rows for a partition key:

{
    "version" : "2017-02-28",
    "operation" : "Query",
    "query" : {
        "expression": "#PartitionKey = :id",
        "expressionValues" : {
            ":id" : {
                "S" : "${ctx.args.id}"
            }
        },
        "expressionNames": {
          "#PartitionKey": "PartitionKey" # whatever the table hash key is
        }
    },
    # The limit will have to be sufficiently large to get all rows for a key
    "limit": $util.defaultIfNull(${ctx.args.limit}, 100)
}

Then to return a single model object that reduces "Tag" to "Tags" you can use this response mapping template:

#set($tags = [])
#set($result = {})
#foreach( $item in $ctx.result.items )
    #if($item.PartitionKey == $item.Tag)
      #set($result = $item)
    #else
        $util.qr($tags.add($item.Tag))
    #end
#end
$util.qr($result.put("Tags", $tags))
$util.toJson($result)

This will return a response like this:

{
  "PartitionKey": "...",
  "Name": "...",
  "Tags": ["Tag-Tree", "Tag-Building"],
}

Fundamentally I see no problem with this but its effectiveness depends upon your query patterns. Extending this to the getAll use is doable but will require a few changes and most likely a really inefficient Scan operation due to the fact that the table will be sparse of actual information since many records are effectively just tags. You can alleviate this with GSIs pretty easily but more GSIs means more $.

As an alternative approach, you can store your Tags in a different "Tags" table. This way you only store model information in the Model table and tag information in the Tag table and leverage GraphQL to perform the join for you. In this approach have Query.getAllModels perform a "Scan" (or Query) on the Model table and then have a Model.Tags resolver that performs a Query against the Tag table (HK: ModelPartitionKey, SK: Tag). You could then get all tags for a model and later create a GSI to get all models for a tag. You do need to consider that now the nested Model.Tag query will get called once per model but Query operations are fast and I've seen this work well in practice.

Hope this helps :)

Upvotes: 3

Related Questions