AWSserverless

AppSync – first jumps in at the deep end – part 1

By 02/15/2021No Comments

The last weeks of 2020 led us to a new project and I will certainly tell you all about it once we finishing it, but right now I’d like to walk you through the things I’ve learned playing with some new, at least for me, AWS toys. Whenever we start a new project there’s always a brainstorm. Should we take a well-known and familiar path or maybe follow our hearts and search for something new? What is the right choice?

Here in Chaos Gears, we strongly believe in the “Own less, build more” motto when it comes to cooperating with our clients. This time we kept a serverless mindset and deliberately decided to explore and rely on the AWS AppSync ecosystem that differs a little bit from our previous projects delivered with API Gateway (REST API calls). As you might have guessed, the reason behind that was, partly, our curiosity regarding GraphQL, but more importantly, the type of application which perfectly fitted, sometimes quite bizarre, GraphQL approach.

TL:TR

 I’ve decided to split the article into smaller parts and shed more light on:

  • AWS way of the GraphQL implementation via AppSync service
  • Resolvers – their types, engine, reason behind implementing them
  • Data Sources (DynamoDb in our example)
  • Serverless Framework code example
  • Query example with proper VTL mapping templates
  • Mutation example with proper VTL mapping templates
  • Cognito User Pools authorization in our schema

What is AWS AppSync all about?

 AWS AppSync is nothing more than a fully managed, serverless service based on the GraphQL. Apart from giving you the out-of-the-box entry point for your GraphQL operations, it also provides the integration with data sources, Lambda functions, security features, and logging. Real-time data synchronization (possible via subscriptions) and offline capabilities are available not only for Web clients but also for the Mobile ones.

Basic AppSync features

 “We all know the infrastructure-as-code benefits, but people still use the console to explore new AWS services”. I’ve heard that sentence many times from our clients. It’s how they take their first steps towards understanding an idea behind a particular service. So, for those who never had time for the AppSync section, here are some noteworthy console screenshots:

Select “Settings” section:

After scrolling down to the “Default authorization providers” (picture below) you’ve got four options you can choose from:

  • AWS IAM
  • OpenID Connect
  • API Key
  • Amazon Cognito User Pool

A single GraphQL API can be configured to deliver private data using more than one authorization type (default and additional ones). For example, you can configure your GraphQL API to authorize some schema fields using API key, and the rest through Amazon Cognito User Pools or any other available option:

While I’m writing this article, we’re testing two methods: API key (as a temporary default one) and Cognito User Pools (as an additional one). So, in the following sections of the article, I’ll be referring to those two. Before diving deeper into the authorization methods and their impact on our GraphQL actions, let’s take a look at the remaining part of AWS AppSync console “Settings”:

Remember I mentioned the out-of-the-box logging feature? Here it is, with three exclusive levels of logging:

Apart from that, we can choose between WAF and X-Ray integration, and last but not least, tagging.

All the aforementioned features can be coded with a Serverless Framework and one of its plugins: serverless-appsync-plugin (https://github.com/sid88in/serverless-appsync-plugin).

plugins:
 - serverless-appsync-plugin

custom:
 appSync:
   name: ${self:custom.app}-${self:service}-${self:custom.stage}
   authenticationType: API_KEY
   apiKeys:
     - name: iotapp # name of the api key
       description: API key
       expiresAfter: 30d # api key life time
   additionalAuthenticationProviders:
     - authenticationType: AMAZON_COGNITO_USER_POOLS
       userPoolConfig:
         awsRegion: eu-central-1
         userPoolId: eu-central-1_iotapp
         #appIdClientRegex: # optional
   logConfig:
     loggingRoleArn: { Fn::GetAtt: [AppSyncLogging, Arn] } # Where AppSyncLogging is a role with CloudWatch Logs write access
     level: ALL # Logging Level: NONE | ERROR | ALL
     excludeVerboseContent: false

Resolvers

 — What for —

Simply put, resolvers are the connectors between GraphQL and a data source (like, for example, Dynamodb or Elasticsearch) that we’ve selected to use. I’ll leave the data sources for later. Right now, let me explain what these resolvers are and how to use them depending on your case.

Resolvers translate the incoming GraphQL requests into instructions for the backend data source and also do a translation from the data source in question back into a GraphQL response return to the request initiator. Essentially, the communication is handled through the parameters or operations that are unique to the data source.

Since we are talking about the resolvers’ translations, let’s analyze what they are built on.

 — Velocity —

AWS AppSync uses Velocity, a Java-based template engine, for mapping template rendering. The engine is responsible for executing following templates:

request velocity mapping template – translating a GraphQL request into a format that the data source can understand.

response velocity mapping template –  translating results of the resolver back into GraphQL.

 — VTL —

Velocity Templates are written in VTL (Velocity Template Language). With VTL we can dynamically include integration with different data sources, adding custom authorization or validations, and even implementing error handling. Moreover, really handy statements like conditionals, loops, maps, and lists are also supported.

 — Types —

Unit Resolvers – include a request and response template only. They are fine for single operations on items from a single data source.

Pipeline resolvers – one or more functions launched in a sequential order. Their most significant value, compared to “unit resolvers”, lies in before and after templates, where the former one performs before the first function hits the data source and the latter maps field output type to GraphQL after the whole sequence of functions ends. That gives us a powerful tool for the backed actions.

Ok, now that Resolvers seem more familiar, let’s jump back to AWS and see “Unit Resolvers” in action. Below, you can find a snippet of my already-defined GraphQL schema (no worries, I’ll show you how to build it via Serverless Framework in the next part of the series).

In the first part of the Schema, I left only “type Device”. It is simply a representation of the object I’d like to fetch from the backend service, and what fields it has. That’s where the scalar types come in: they represent the leaves of the query. Besides obvious field scalar types, like “Strings”, there are some new and worth mentioning:

NOTE 1: AWS AppSync comes with a set of predefined scalars, including a set of reserved types starting with an AWS prefix, like the one I used in my schema. AWS AppSync does not support custom scalar types.

NOTE 2: “!” next to the field means that it is non-nullable, meaning that GraphQL service promises to always give you a value when you query this field.

ID – represents a unique identifier, serialized in the same way as a String, but not intended to be human‐readable.

AWSDateTime – represents a valid extended ISO 8601 DateTime string.

DeviceStatusEnum – that’s pretty cool because you can define your own scalars containing only validated fields.

enum DeviceStatusEnum {
   online
   offline
   error}
scalar AWSDateTime
scalar AWSTimestamp
scalar AWSEmail
scalar AWSDate
scalar ID

User, Place – inserted into arrays represents the other sets of objects I’d like to fetch within a query, declared in a schema similar to the “Device”.

Next to the schema section, you will see the Resolvers part that allows you to define your resolver functions. Now, let’s see how it works live.

First, let me walk you through the code (schema, serverless.yml, mapping templates), and then I will present you the results in the console. To be more precise, I’d like to achieve a returned list of Device objects queried from a Dynamodb based on the GSI. I will finish this part of the series by showing you:

  • a necessary part of my repo
  • a necessary part of my schema file
  • yml – file appsync config part
  • AWS console result after deploying a serverless CF stack

Just a snippet from my repo structure to make it clearer:

├── mapping-templates

│   └── getDevices-request-template.vtl

│   └── getDevices-response-template.vtl

├── schema

│   └── schema.gql

├── serverless.yml

schema.gql file:

type Query {
      getDevices(value: String, gsi: String, scanIndexForward: Boolean, limit: Int, nextToken: String): PaginatedDevices
}
type PaginatedDevices {
   items: [Device]
   nextToken: String
}
type Device {
   id: ID!
   d_status: DeviceStatusEnum
   version: String
   startDate: String
   users: [User]
   places: [Place]
   serialNumber: String
   description: String
   deviceGroupId: ID
   createdAt: AWSDateTime!
   updatedAt: AWSDateTime!
}

Quick note: Here’s where AppSync shines brightly when compared to the API Gateway. You don’t have to implement response validation in your application code. Without response validation, it’s easy to return more data than you intended to simply by mistake. It doesn’t mean that you cannot control the response data with API Gateway, but believe me, it’s tiresome labour. With GraphQL (schema snippet above) there is no extra work involved. What’s not included in the type definition will not be returned.

serverless.yml file (appsync mapping template part for our example):

appSync:
  mappingTemplates:
    - dataSource: Devices
      type: Query
      kind: UNIT
      field: getDevices
  schema: ./schema/schema.gql

I didn’t cover template file mapping intentionally because I’d like to explain it in the next part of my series. For the time being, you have to believe me that I’ve successfully launched a Serverless stack (via Serverless Framework). Voila! AWS console Appsync/Schema section reflecting a new defined Resolver for a field “getDevices” in a type “Query”, returning the “PaginatedDevices” object.

Ok, but what’s really behind the wall of those mystic resolvers. You’ll have to wait carefully, my dear reader, as we will start the next part by taking a look at the code from:

├── mapping-templates

│   └── Query,getDevices.request..vtl

│   └── Query.getDevices.response.vtl

Roll up your sleeves. Part 2 is coming.