AWS AppSync + GraphQL + DynamoDB: Building Objects from a Single Table from Multiple Table Entries

Maxwell Russell
5 min readJun 20, 2018

--

Photo by Sasha Lebedeva on Unsplash

I’ve really been playing around with AppSync/noSQL since discovering it. As a developer I love having control of my tools, and I have the personal goal of attempting to understand why and how things are built to be used. AppSync provides a nice simple tool to test and learn. It also provides a nice level of documented complication that allows me to think through different ways.

This desire for discovery led me through another trove of information in Amazon’s sometimes overwhelming docs. Starting with this video:

Design Patterns using Amazon DynamoDB

Even without a deep practical knowledge of relational databases I found this video to be a very concise and explicative. One of the main takeaways was that I may have been thinking about things the wrong way.

“You should maintain as few tables as possible in a DynamoDB application. Most well designed applications require only one table.”

AWS Docs

Why did this stick out so much? Well, I recently wrote an article about adding multiple items into multiple DynamoDB databases simultaneously, found here. This was a great exploration into batch tools and cross table resolvers but I found myself asking a few different questions.

  1. If I need to search based on a value in table A, how do I return information from table B with GraphQL?
  2. If I need to search a single query based on two fields that are shared across tables, why am I not utilizing a single table? I.e. Table A has a “title” field, and table B has a “title field.
  3. Is there a way to create and return different aspects of a single table in the same graphQL queries?

Making the decision to explore these questions has derailed my project workflow, but overall it has felt beneficial. So here is the final output from last deep dive into DynamoDB + GraphQL + data management.

I will continue to use a Recipe as my mock object.

Data Organization

In the multi-table model I had a table for each part of a recipe. The recipe, the elements of the recipe, and the ingredients.

In moving to a single table model my data structure has changed quite drastically. Note: This is not the final data model. I will likely be making changes to the primary partition key. I’ve also avoided abbreviating for legibility.

I am now using a single primary partition key for all parts of a recipe, utilizing a primary sort key to differentiate between the different parts.

The Recipe has an id:

87971.

All high level info about the recipe is stored into a id’d type:

recipe87971:info1.

Each element is stored in an id’d type:

recipe87971:element1.

Each ingredient is store in an id’d type relative to its parent element:

recipe87971:element1:ingredient1.

All parts of a recipe can be immediately downloaded knowing only the ID of the parent recipe! This differs from doing multi-table lookups in that I am doing a single read request. As these are all key’d adjacent to each other, hypothetically this would take advantage of Amazon’s efficiency.

Schema Definition

The schema definition does not change too drastically between models. One minor change I have noticed is that nested items, living all on a single table now must be referenced in the parent object to be called. Thus my recipe type looks as follows:

type Recipe {
id: ID!
type: ID!
title: String!
category: String
dbtype: String
status: String
elements: [Element]
ingredients: [Ingredient]
}
  • In my previous iteration, ingredients only lived on the Element type.

A larger change in this schema definition is the return of the queries. This is where I more heavily feel the presence of GraphQL. Let’s say I’m scrolling through a list of recipe titles + brief descriptions and then find a recipe I’d like to check out. The user can click on the item, and then by using its ID we can query and compile the data in the way intended.

type RecipeBuild {
id: ID
category: String
elements: [Recipe]
title: String
type: String
status: String
}

Ingredients doesn’t need to be directly exposed because it can be dealt with in our resolver. This works because we aren’t returning a single item when we search by the Recipe’s ID’s. We are finding parts which we can consume as desired.

Mapping Templates

Now that we have the parts we just need to define how we will work with them.

Our request template stays simple, query by ID. If no adjustments were made to the response, we’d get a basic list with an array item for each item with the ID.

request mapping template

We don’t want that though. For the front-end we need to make sure data is organized to be visualized. High level info separated from nested elements. This will allow us to map over each element, as well as each ingredient contained in the elements. Ultimately something like this would be nice:

The basic response for a query is a flat list, but the whole point of rolling up my sleeves and digging into the backend is so that I don’t have to do that on the front end. So after some work, and a bit of Java, I’ve ended up with this nice little data compiler.

There are a few things going on.

  • Each item is looked over, and depending on the its type it is thrown into an array or saved as a variable.
  • High level info is returned as an map attribute
  • Elements are given their own array to be returned as an attribute of the returned map
  • Ingredients are stored in their own array for later iteration
  • Ingredients, based on their elementId are then store in the correct element to be iterated on later. Note: you’ll notice that we aren’t directly exposing the Ingredients array in the map. That’s because it’s a map and works like JSON so we don’t have to.

This response mapping template returns the following when searching for a specific ID of a Pancakes recipe:

Not to shabby eh! This in addition to batch adding the db entries should keep the consume read/write capacities pretty low. I’ll won’t be able to test that until integrating this new data model into my Apollo/React client though. I’ll likely post about that next!

--

--