skip to Main Content

I am creating an AppSync api with the following schema:

schema {
  query: Query
}

type Query {
  getUser(id: String): User!
}

type User {
  DDBpk: String!
  DDBsk: String!
  DDBproperty1: String!
  DDBproperty2: String!

  emailAddress: String!
  emailAddressVerified: Boolean!
  CreateDate: String
}

Now the first 4 fields all come from a dynamo db table that is called from a resolver attached to the getUser Query but the last three fields come from aws-cognito. Now I have a lambda data source that gets all three of these fields but if I attach it to each field It calls the cognate api three times, which is very wasteful, is there a way to resolve all three fields with one resolver to prevent this?

4

Answers


  1. In AppSync, resolvers and fields in a GraphQL schema have 1:1 relationships.

    If you have data sources on each field, they will be triggered independently.

    Looking at the User type, we can attach data sources in a few ways:

    1) Attach data source in each field of User

    This approach is what you are doing at the moment. You need 7 data sources to resolve the User entity.

    2) Group fields in a different type and add a resolver to it

    You can attach a data source to a type with the fields you want:

    type UserDetails {
      emailAddress: String!
      emailAddressVerified: Boolean!
      CreateDate: String
    }
    
    type User {
      DDBpk: String!
      DDBsk: String!
      DDBproperty1: String!
      DDBproperty2: String!
    
      UserDetails: UserDetails!
    }
    

    With the schema above, you attach a data source to User.UserDetails that returns all the fields from UserDetails. You are reducing the number of data sources to 5.

    Notes: if you resolve everything in User.UserDetails, no other query can use UserDetails, because there’s no data source attached to the fields of the type.

    You may need to pay attention to which queries x fields use the UserDetails type.

    3) Resolve everything in the main getUser

    The resolver attached to getUser can return the complete response. You don’t need to attach a resolution for each field.

    The $ctx includes all information you need to query multiple databases/services from within the resolver.

    Pseudo-code:

    // inside the resolver
    if (ctx.info.selectionSetList.includes("DDBpk")) {
       const item = await ddb.get({ pk: ctx.args.DDBpk })
    }
    

    The same Notes from item 2) apply to this approach.

    Login or Signup to reply.
  2. yes instead of binding schema to dynamo and lambda you can simply combine them and get data from lambda directly i.e
    you can use a resolver to fetch all three fields from AWS Cognito and return them in a single response to the client :

    Define a new type that includes all three fields from AWS Cognito:

    1. Define a new type that includes all three fields from AWS Cognito:
    type UserWithCognito {
      emailAddress: String!
      emailAddressVerified: Boolean!
      CreateDate: String
    }
    
    1. Update your getUser query to return the new UserWithCognito type instead of User:
    type Query {
      getUser(id: String): UserWithCognito!
    }
    
    1. Create a new resolver for the getUser query that fetches the user data from both DynamoDB and AWS Cognito, and combines them into a single response. You can use a Lambda function as the data source for this resolver.
    const AWS = require('aws-sdk');
    const ddb = new AWS.DynamoDB.DocumentClient();
    const cognito = new AWS.CognitoIdentityServiceProvider();
    
    exports.handler = async (event) => {
      const userId = event.arguments.id;
      
      // Fetch user data from DynamoDB
      const ddbResponse = await ddb.get({
        TableName: 'your-dynamodb-table-name',
        Key: { DDBpk: userId, DDBsk: 'user' }
      }).promise();
      
      // Fetch user data from AWS Cognito
      const cognitoResponse = await cognito.adminGetUser({
        UserPoolId: 'your-cognito-user-pool-id',
        Username: userId
      }).promise();
      
      // Combine DynamoDB and Cognito data into a single response
      const response = {
        emailAddress: cognitoResponse.UserAttributes.find(attr => attr.Name === 'email').Value,
        emailAddressVerified: cognitoResponse.UserAttributes.find(attr => attr.Name === 'email_verified').Value === 'true',
        CreateDate: ddbResponse.Item?.CreateDate
      };
      
      return response;
    };
    

    Note: Ignore the syntax there might be some change required.

    Login or Signup to reply.
  3. The issue you have is that each resolver is called once per field. If you want to reduce the number of calls you basically have two options :

    • Batch resolvers
    • Pipeline resolvers

    Batch Lambda resolvers won’t help in that case because only batches with the same field name.

    One way to resolve multiple fields from multiple datasources with AppSync is by using Pipeline Resolvers.
    It will call the two resolvers sequentially, allowing you to use multiple datasources.

    It basically work like this :

    request: BeforePipeline.vtl
    functions:
     - request: beforeResolver.vtl
       resolver: firstresolver
       response: afterResolver.vtl
     - request: beforeResolver.vtl
       resolver: secondresolver
       response: afterResolver.vtl
    response: AfterPipeline.vtl
    
    Login or Signup to reply.
  4. Basically you can’t, you should use a pipeline resolver attached to the query:
    https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html

    In this way you can fetch dynamo data in the first function, store the result received in the response in the context stash, and then retrieve the cognito data in the second function.

    At the end you can return the complete User in the response mapping template.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search