Maximize Performance with AWS AppSync Direct Integrations

Maximize Performance with AWS AppSync Direct Integrations

With Easy-to-Follow Coding Demos

[generated with AI] AWS AppSync simplifies the creation and management of GraphQL APIs by connecting data sources to resolvers through functions, enhancing front-end and back-end collaboration. This article explores the advantages of using AppSync’s direct integration capabilities, showcasing examples with DynamoDB, OpenSearch, and HTTP data sources to improve performance and reduce costs by eliminating unnecessary AWS Lambda usage. Leveraging direct integrations not only optimizes operations but also simplifies architecture and promotes reusability within the API.

Introduction

AWS AppSync is a serverless services that makes it very easy to build and maintain GraphQL APIs. It works by connecting “data sources” (such as lambda) to the “resolvers” through “functions”. Each function has one data source associated and also executes logic.

With AppSync, we can define an entire API schema and business logic in a single place, allowing for front-end and back-end developers to collaborate more efficiently. The following image shows the high-level of a typical AppSync API architecture.

Typical architecture for an appsync API, in the left side shows different frontend clients like mobile, web and others. In the middle it shows the AppSync API and to the right it shows data sources which the api interacts with

Image that shows the architecture of a graphql API in AWS AppSync. Source: Own Creation

Each resolver function in an appsync API can be written in javascript or VTL (Velocity Template Language). In this blog post we’ll be focusing on javascript resolvers as they are more popular. It’s not a full-feature javascript engine, rather it’s a sub-set of javascript that allows to execute some simple logic in conjunction with the resolver’s data source execution.

However, It’s common to see developers defaulting to use AWS Lambda as a data source for graphql operations that may not need one. AWS Lambda is a fantastic serverless compute that uses the Functions-as-a-Service model (FaaS) to execute code. While the approach works, it introduces latency due to Lambda’s ephemeral execution environment, incurring in cold starts. It also incurs additional costs - specially in high frequency operations.

The following image shows an example of an AppSync’s architecture that can be improved

Image that shows two aws serverless architectures. The first one is using an appsync api which invokes a lambda and then does a call to dynamodb. the second one is from appsync api directly to dynamodb, which doesn't need the lambda and increases performance and saves costs

Image that shows an improvement on performance and cost for appsync api that only interact with a dynamodb database. Source: Own Creation

In this blog post we’ll take a look at AppSync’s direct integration capabilities. Using direct integration may remove a lambda if it’s not needed. This results in higher performance and lower costs. It can also help simplify an architecture and at the same time, having reusable resolver functions promotes re-usability within the API.

Example 1: DynamoDb

DynamoDb Is a serverless NoSQL database service offered by AWS. It’s main advantage is that it provides fast and predictable performance regardless of scale. DynamoDb is an ideal database choice for serverless application.

In data-driven APIs, the most common operations are CRUD operations: Create, Read, Update and Delete operations to a database. So using the direct integration with appsync and dynamodb is a very common use case. The following is a sample snippet of a resolver function that interacts with a dynamodb table.

export function request (ctx) {
  const { args } = ctx;
  const { blogPost } = args;

  // Any validation logic or transformation/mapping logic

  return {
    operation: 'UpdateItem',
    key: util.dynamodb.toMapValues({ BlogPostId: blogPost.id }),
    update: {
      expression: 'SET #BlogPost = :blogPost',
      expressionNames: { '#BlogPost': 'BlogPost' },
      expressionValues: {
        ':blogPost': util.dynamodb.toDynamoDB(blogPost),
      }
    },
  };
}

export function response (ctx) {
  const { result } = ctx;
  // ... any transformation/mapping logic, etc

  return result.Attributes.BlogPost;
}

Code snippet that shows an AppSync Javascript Function Resolver which directly interacts with a dynamodb table. Source: Own Creation (Gist Link)

Notice in the code that the resolver has capabilities of mapping, validating and even doing a bit of computation. So the most common use cases around CRUD operations can be covered in pure function resolvers without needing a lambda in between.

Example 2: Open Search

Amazon Open Search is a managed service that helps run open search (and elastic search) clusters at scale. It shines in analytics, monitoring and click streams.

With AWS AppSync, we can use integrate directly from a resolver function to perform a query in open search, as shown in the following code snippet.

import { util } from '@aws-appsync/utils';

// Searches for posts in the OpenSearch index
export function request(ctx) {
  const { args } = ctx;
  const { searchTerm } = args;

  // ... Any other logic to build the query

  return {
    operation: 'GET',
    path: '/post/_search',
    params: {
      body: {
        from: 0,
        size: 20,
        query: {
          match: {
            content: searchTerm || '',
          },
        },
      },
    },
  };
}


export function response(ctx) {
  const { result, error } = ctx;

  // Handle errors
  if (error) {
    util.error(error.message, error.type);
  }

  // Extract and return search hits from the response
  const results = result.hits.hits.map(hit => hit._source);

  // ... Any other business logic to process the results

  return results;
}

Code snippet that shows an AppSync Javascript Function Resolver which directly interacts with an open search cluster. Source: Own Creation

Same as with the Example 1, notice there is room for validation/mapping/error handling in the resolver function itself. Using this direct integration method without having a lambda in between to execute complex search queries will likely saves costs and increase performance.

Example 3: HTTP call

There is a Data Source for AppSync called HTTP. With this Data Source, we specify an endpoint, then in the resolver we can include query parameters, body, path, HTTP method and headers. This is useful when we’re fetching data from third-party APIs, microservices or even other AWS resources that don’t have a direct integration.

The following shows an example of an external API that verifies the validity of an email from the input.

import { util } from '@aws-appsync/utils';

export function request (ctx) {
  const { args, env: { VERIFY_EMAIL_API_KEY = null } } = ctx;

  if (!VERIFY_EMAIL_API_KEY) util.error('No email verification api key provided', 'ValidateEmailError', null, null);

  // ... any other additional logic for validation/mapping/preparation

  return {
    method: 'GET',
    params: {
      query: {
        email: args.email,
        apikey: VERIFY_EMAIL_API_KEY,
      }
    },
    resourcePath: `/verify-email`
  };
}

export function response (ctx) {
  const {
    result,
    error,
  } = ctx;

  if (error) {
    // error handling
  }

  const { body } = result;
  if (!body) util.error("Email is not valid", 'ValidateEmailError', null, null);

  // Any additional logic or checks

  return {
    isValid: true,
  };
}

Code snippet that shows an AppSync Javascript Function Resolver which directly interacts with a verification email third party endpoint. Source: Own Creation

Notice in the code snippet that we can specify different details like the HTTP method GET, query parameters and path.

It is also possible to interact with other AWS resources that don’t have a direct integration with AppSync using this “HTTP Data Source”. For example it’s possible to add a message to an SQS queue using the direct integration with HTTP. AppSync will sign the request of the HTTP method using the current AppSync execution role.

Summary

We explored how can AppSync integrate with services such as DynamoDb and OpenSearch, and even with HTTP endpoints. These direct integrations, compared with an architecture that involves using lambdas, allow to reduce costs and enhance performance. It can also simplify the architecture of the GraphQL API. This approach also promotes reusability as the resolvers may be used in other graphql operations.

💡
Evaluate your current serverless architecture and identify opportunities to leverage AppSync direct integrations

References