Since its announcement at re:Invent 2017, AWS AppSync has caused me to rethink my preferred approach to building mobile backends. While REST is well-understood, I often found it less flexible than I would like, particularly when designing consistent and efficient communication between client and server. Add the need to manage offline user data and conflicts and I would often find myself in a hot mess.
AppSync is a managed GraphQL service from AWS that helps tackle these challenges. In addition to addressing offline data and conflict resolution, AppSync also introduces flexibility via GraphQL and the capability to integrate with a variety of data sources.
In this post and accompanying sample code, my goal was to build a fully-functional AWS AppSync-driven backend for a conference management app (“Session Manager”). The app will deliver end users a set of features that allow booking sessions, building a personalized schedule, and searching for sessions. As part of that work, I also wanted to:
To get started, let’s define the GraphQL schema for the Session Manager API:
Our schema is primarily concerned with the
Session type as the API mostly retrieves and manipulates session data, both public and user-specific. Of note is the use of
@aws_auth(cognito_groups: ["Editors"]), this will limit access of the associated mutations to users in the Editors group in the application’s Cognito User Pool.
AppSync currently supports three data sources: Amazon DynamoDB, Amazon Elasticsearch Service, and AWS Lambda (though Lambda can provide access to a wide variety of other options, such as RDS or ElastiCache). Our Session Manager API currently makes use of DynamoDB (two tables: user schedules and sessions) and Elasticsearch. The CloudFormation template included in the sample project defines these resources and associated AppSync Data Source configuration. For example, we can define the DynamoDB sessions table as a Data Source as follows:
Additionally, AppSync offers a data source of type NONE that can be used with local resolvers to simply forward data to the response mapping template. We’ll make use of a local resolver shortly.
Resolvers are where you will find some of the magic of AWS AppSync. Resolvers allow you to attach data (types or fields), queries, and mutations to configured data sources. Each resolver is compose of (1) a data source, (2) a request mapping template, and (3) a response mapping template. A request template defines how incoming or contextual data is passed to the data source. In a single request template, for example, you can include a pagination token (request parameter) and Cognito identifier (identity context). The response template maps the result from the data source to the response to be sent to the client.
AppSync resolvers are built using the Apache Velocity Template Language (VTL). This means that a resolver template can include logic to transform data, iterate over collections, filter results, and more. AppSync also provides a broad array of utility functions that can be helpful in validation, transforming data, and dealing with dates.
Each AppSync Data Source communicates through operations unique to the data source. For example, communicating with a DynamoDB data source is similar to interacting with DynamoDB via an AWS SDK.
Per my earlier stated goals, I have defined all Session Manager resources using CloudFormation. For example, the DynamoDB resolver for the
allSessions query is as follows:
If you are familiar with DynamoDB, the request mapping should look relatively familiar. We are performing a DynamoDB
scan and paginating the result. The response mapping filters and transforms the result. Note that
$util are the useful AppSync utility functions mentioned earlier.
Like a DynamoDB resolver, an Elasticsearch resolver feels very similar to interfacing with Elasticsearch itself. For example, our search resolver:
To review the remaining resolvers associated with the Session Manager API, check out the complete CloudFormation template. Although slightly slower to roll out updates, I found managing resolvers in CloudFormation useful as I was able to peruse the full set in a single file.
One of the most important requirements of Session Manager is that is allows users to register for sessions. The
userSchedule query allows the currently logged in user to retrieve his list of registered sessions. The resolver for the
userSchedule query is slightly different than our other resolvers in that (1) both the query and the
UserSchedule type cooperate to build the response, (2)
UserSchedule fields are resolved independently, and (3) the
Sessions field is filled using a multi-step batch resolver.
When resolving the
userSchedule query, AppSync will first execute the resolver attached to the query:
Next, AppSync will resolve the
UserSchedule return type associated with the query. As noted (and shown) above, the fields in the
UserSchedule type are resolved separately. The User field uses our aforementioned local resolver to pass through data available in the identity context (from Cognito):
Sessions attribute meanwhile is a more complex, DynamoDB Batch resolver. In this case, the resolver collects the unique identifiers of the sessions the logged in user has registered for to build a
BatchGetItem query. The registered session list was captured as part of the
userSchedule query resolver. We use VTL to build a list of
SessionId values and then perform the query, retrieving and responding with a collection of
Sessions. Note that our CloudFormation template for both the request and response mappings make use of string substitution to insert the name of the DynamoDB table resource (
Revisiting, we first retrieve a listing of the the logged in user’s registered sessions (from the DynamoDB user schedule table) and then use DynamoDB’s batch query mechanism to retrieve the details about those registered sessions. That result is paired with data available in AppSync’s identity context to return the complete
UserSchedule result. This example demonstrates how to build a more complex resolution workflow, needing only a few lines of simple VTL code and an understanding of our data sources.
As part of this project, I have included a Makefile that makes it easy to deploy the Session Manager API for your own testing. The repository README contains detailed instructions as well as suggestions for testing. AWS AppSync is currently available in the following AWS Regions: us-east-1 (N. Virginia), us-east-2 (Ohio), us-west-2 (Oregon), eu-west-1 (Ireland), ap-northeast-1 (Tokyo), and ap-southeast-2 (Sydney).
I hope you have found this article useful and enjoy working with AWS AppSync as much as I have.