Part One
Author's Note: Since this article series was first drafted, AWS announced support for JavaScript resolvers. While I am excited to begin using JS resolvers, the tooling - most notably the serverless-appsync-plugin - has yet to support it fully. Look for new posts in the near future where I will show you how to set up and use JS resolvers. Until then, I think the following content still has value and I hope it helps you.
What For?
Why should you bother to learn VTL for AppSync? I asked myself this question last fall and came up with a short list of answers:
I don't know how to use VTL resolvers and, if I did, then I can evaluate whether I should use them.
Limit concurrent Lambda executions
Cut down on response times; no cold starts (allegedly)
Segment my application into independently deployable pieces
That last bullet, the one about independently deployable pieces, was actually my starting point. The AppSync project I was working on was growing monolithic. It was taking an increasingly long time to deploy and my feedback cycle for changes was terrible. I decided to begin splitting out domain services into their own deploys. Using VTL and HTTP Resolvers, I could use AppSync as a router to fetch the data it needed from these new domain services (a micro-services architecture).
Before (roughly):
After (roughly):
The following is a condensation of the process using a fictional E-Commerce company that I'll call "Big Shopper."
The Setup
Big Shopper's basic backend architecture starts with an AppSync Backend-for-Frontend (BFF) that needs to do basic CRUD operations on the resource Product
. AppSync will handle user authentication (via Cognito) and all the GraphQL goodness (projection, schema enforcement, subscriptions, etc.) while the Products Service (an independently-deployed REST service) is the system of record for all things Product
. All we need to do is figure out how AppSync talks to the Products Service. Oh, did I mention that the Products Service endpoints will all be IAM protected? Yeah, we've got to handle that, too.
For those new to AppSync, it is an AWS-managed GraphQL service. GraphQL uses Resolvers to fulfill queries. In AppSync, a resolver can be one of a few AWS services: DynamoDB, RDS, OpenSearch, or Lambda. Additionally, the resolver can be set up to send HTTP requests. Our goal is to use HTTP resolvers to connect AppSync to our Product REST service.
What will we need to do to pull this off? At a high level, all we need is to take the AppSync arguments/input and map it to an HTTP call. We also need to be able to sign these requests with AWS Signature Version 4. I'm going to break our approach into three broad steps:
Make a simple, no-auth, HTTP GET request at a route using variables coming from AppSync arguments.
Make a simple, no-auth HTTP POST (or PUT) request that shows how to prepare and pass a body.
Enable v4-sig signing for the above resolvers and set up the proper permissions for AppSync to execute the Product Service API.
Along the way, I'll show how to send query strings, custom headers, and path parameters. We'll also learn how to map responses for AppSync and return errors on unexpected status codes. And to do all the above, we'll get proficient in VTL basics.
To get there, I'll be using the Serverless Framework and the Serverless AppSync Plugin to handle all the deployments. However, I won't be walking you through a full setup of those tools. I'll be counting on your familiarity with A) creating a simple REST service and B) creating a simple AppSync service. You will not need to know much more than the "hello-world" versions of either to find value in this tutorial, so get comfortable with the basics of those first.
Basic Routing
Let's assume our Product
REST API has the following path structure for fetching a product, with no authorization just yet:
/products/{productId}
Further, we are going to segment our multi-tenant service by storeId
which will be passed in as a header, like so:
x-custom-store-id: abc123
Thus, our Big Shopper AppSync getProduct
endpoint will require two input properties: storeId
and productId
. As a result, it will return an object of type Product
. Set these up as necessary in your GraphQL types file.
Let's start with setting up our first AppSync HTTP resolver. In your dataSources
section of your serverless-appsync-plugin
definition, add an entry that looks something like this:
- type: HTTP
name: ProductsService
config:
endpoint: 'your-api-gtwy-base-url'
Then, add a property to your Query object for getProduct
and an entry in your mapping resolvers and an entry in mappingTemplates
that looks something like this:
- type: Query
field: getProduct
dataSource: ProductsService
That links the getProduct
property in your Query object with the "ProductsService" data source we just created. So far, so good, but - how are we going to get the input data from AppSync into the path parameters of API Gateway? If you guessed "this is where we will use VTL" you are correct! Next week in part two, we'll look at just enough VTL basics to get us started.
Stay tuned…