Search code examples
swiftamazon-web-servicesgraphqlamazon-dynamodbaws-amplify

How to make data in different DynamoDB Tables relational?


I am currently following along with the AWS Amplify docs and I am using the default blog GraphQL schema to try and create relational Dynamo DB tables.

The blog model tables show up in DynamoDB and I can upload info to them I just don't know how to make them relational. The model types are Blog , Post , and Comment . For example, after uploading a post to its DynamoDB table, how can I link an uploaded comment to that same post? In my Swift code I try to attempt to do so but to no avail.

I also do not understand the syntax List<Comment>.init() and it probably should not be there but gives no errors. Maybe that is the cause of my problem.

Creating a post:

     let blog = Blog(name: "UserBlog", posts: List<Post>.init())
        let posts = Post(title: "Testing out AWS", blog:blog, comments: List<Comment>.init())
        _ = Amplify.API.mutate(request: .create(posts)) { event in
            switch event {
            case .success(let result):
                switch result {
                case .success(let post):
                        print("Successfully created the post: \(post)")
                case .failure(let graphQLError):
                    print("Failed to create graphql \(graphQLError)")
                }
            case .failure(let apiError):
                print("Failed to create a todo", apiError)
           
            }
        }

Output from debug console

Successfully created the post: Post(id: "83D71F16-6B0D-453A-A163-AABF484CE527", title: "Testing out AWS", blog: nil, comments: nil)

Then after creating the comment for that post with this code

  let blog = Blog(name: "UserBlog", posts: List<Post>.init())
        let posts = Post(title: "Testing out AWS", blog:blog, comments: List<Comment>.init())
        let comments = Comment(content: "It worked", post: posts)
        _ = Amplify.API.mutate(request: .create(comments)) { event in
            switch event {
            case .success(let result):
                switch result {
                case .success(let comment):
                        print("Successfully created the comment: \(comment)")
                case .failure(let graphQLError):
                    print("Failed to create graphql \(graphQLError)")
                }
            case .failure(let apiError):
                print("Failed to create a todo", apiError)
           
            }
        }

Output from debug console

Successfully created the comment: Comment(id: "85395F8B-C8C2-4ACB-8FC5-DAEFC2728C32", content: Optional("It worked"), post: nil)

And finally after fetching from the tables using this code

        let post = Post.keys
           let predicate = post.title == "Testing out AWS"
           _ = Amplify.API.query(request: .list(Post.self, where: predicate)) { event in
               switch event {
               case .success(let result):
                   switch result {
                   case .success(let post):
                       print("Successfully retrieved list of posts: \(post)")

                   case .failure(let error):
                       print("Got failed result with \(error.errorDescription)")
                   }
               case .failure(let error):
                   print("Got failed event with error \(error)")
               }
           }

Output from debug console

Successfully retrieved list of posts: [testAWS.Post(id: "83D71F16-6B0D-453A-A163-AABF484CE527", title: "Testing out AWS", blog: nil, comments: nil)]

How can I link the comment to the post so when I query it the comment will show instead of nil

My Schema:

type Blog @model {
  id: ID!
  name: String!
  posts: [Post] @connection(name: "BlogPosts")
}
type Post @model {
  id: ID!
  title: String!
  blog: Blog @connection(name: "BlogPosts")
  comments: [Comment] @connection(name: "PostComments")
}
type Comment @model {
  id: ID!
  content: String
  post: Post @connection(name: "PostComments")
}

Solution

  • You should save each one of your models if you haven't done so, for example, I see you have created a blog, then a post with the blog. You'll need to save the blog, and then save the post, otherwise the blog does not exist in DynamoDB. It looks correct for Post and Comment since you are saving the post and then the comment (containing the post for association).

    The data is persisted but isn't being returned to your client. Currently the builder that is being used to create the GraphQLRequest .list(Post.self, where: predicate) will only generate the selection set to return the Post, but not the optional list of connected comments. This is by default a walk-depth of 1. Currently Amplify for Android does this differently and there is an existing Issue open in the repo to track whether this should be updated to have a walk depth of 2 by default. https://github.com/aws-amplify/amplify-ios/issues/681

    One workaround here is to create your own custom GraphQL request with a selection set that contains both the post and the comments. Or a custom GraphQL request for a list query that has a filter parameter that you set to filter for a particular post.id.

    You can see this example here creates a nested selection set with post and comments: https://github.com/aws-amplify/docs/blob/fbe1773bd21476954f379909b7a9a7abf3f02c2a/docs/lib/graphqlapi/fragments/ios/advanced-workflows.md#nested-data

    extension GraphQLRequest {
        static func getPostWithComments(byId id: String) -> GraphQLRequest<JSONValue> {
            let document = """
            query getPost($id: ID!) {
              getPost(id: $id) {
                id
                title
                rating
                status
                comments {
                  items {
                    id
                    postID
                    content
                  }
                }
              }
            }
            """
            return GraphQLRequest<JSONValue>(document: document,
                                             variables: ["id": id],
                                             responseType: JSONValue.self)
        }
    }
    

    Feel free to post your questions as you have here over in the github repo https://github.com/aws-amplify/amplify-ios/issues as it will help us in facilitating the conversation