© 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.SUMM IT
Ten tips and tricks for improving your GraphQL API with AWS AppSyncDaniel GeskeSr. Solutions ArchitectAmazon Web Services
© 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.SUMM IT
Agenda
Schema design
Data design
Using AWS Lambda
Realtime data
Rapid development techniques
Offline data
CI/CD
Operations and monitoring
Testing APIs
Schema governance
© 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.SUMM IT
Schema design
Everything in your API revolves around the schema
Here are a few tips to help build more resilient, evolvable schemas
© 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.SUMM IT
Use long, descriptive names
Why? GraphQL type names are unique
You may proxy multiple services that expose similarly named data
Tip: Use long, descriptive names that adhere to a pattern
For example, you might use <Service><Type> and have names like AWSLambdaFunction
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Input and output patterns
Use a single input type per mutation field.
type Mutation {
# Use a single input field per mutation
createUserPost(input: CreateUserPostInput!): UserPost
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Input and output patterns (advanced)
type Mutation {
# Also consider explicit output types
createUserPost(input: CreateUserPostInput!): CreateUserPostOutput
}
# The query reference allows arbitrary reads after a write
type CreateUserPostOutput {
userPost: UserPost
query: Query
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Input and output patterns (advanced)mutation ReadAfterWrite {
createUserPost(input: { title: “Hello Berlin!” }) {
userPost { title } # Returns the updated object
query { # Perform and arbitrary read after the update in the same req
userFeed {
title
}
}
}
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Pagination patterns
Come up with a pagination pattern that works for you
Think about your pagination requirements
Do you need a cursor per item?
What pagination related data do you need access to?
Do the edges in your graph contain data?
For example, is a friendship accepted or not
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
A simple pagination pattern
type UserPostConnection {
items: [UserPost]
nextToken: String
}
type User {
id: ID!
posts: UserPostConnection
}
type UserPost {
id: ID!
content: String
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
A simple pagination pattern
query SimplePaginatedQuery {
listObjects(limit: 10, nextToken: “…”) {
items {
id
title
}
nextToken # Finished when null
}
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
An advanced pagination pattern# Connections wrap edges.
type UserFriendshipConnection {
edges: [UserFriendshipEdge]
pagination: PageInfo
}
# Edges wrap nodes.
type UserFriendshipEdge {
node: User
cursor: String
isAccepted: Boolean # data on the edge
}
# Wrapper for pagination related data.
type PageInfo {
hasNextPage: Boolean
hasPrevPage: Boolean
count: Int # extra page data
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
An advanced pagination patternquery AdvancedPaginatedQuery {
listObjects(first: 10, after: “…”) { # or last: 10, before: “…”
edges {
node {
id
title
}
cursor
}
pageInfo { hasNextPage hasPrevPage } # Finished when one is false
}
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Use the best database for the job
Amazon DynamoDB: A great primary data store that scales
Amazon Elasticsearch Service: Rich search over large data sets
Amazon Relational Database Service: Store transactional & relational data
Data design
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
GraphQL with Amazon DynamoDB
GraphQL Operation DynamoDB OperationMutation.create<Type> PutItemMutation.update<Type> UpdateItemMutation.delete<Type> DeleteItemQuery.get<Type> GetItemQuery.list<Type> Scan or Query
DynamoDB operations map really well to GraphQL
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Add relational capabilities to NoSQLtype User {
id: ID!
username: String!
tasks: [Task]
}
id userId title1 1 Write Slides2 1 Prep Demo3 3 Study For Exam4 2 Review User Feedback
id username
1 Michael2 Jay3 Zach
type Task {
id: ID!
title: String!
user: User
}
GetItem
Query the Task table’s GSI
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Add relational capabilities to NoSQL
query GetUserAndOwnedPosts {
getUser(id: “1”) {
id
username
tasks {
id
title
}
}
}
id username
1 Michael2 Jay3 Zach
userId id title1 1 Write Slides1 2 Prep Demo3 3 Study For Exam2 4 Review User Feedback
Query a table or index
where userId = $ctx.source.id
GetItem
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
And the inverse
query GetUserAndOwnedPosts {
getTask(id: “1”) {
username
owner {
username
}
}
}
GetItem
where id = $ctx.args.id
GetItem
where id = $ctx.source.userId
id userId title1 1 Write Slides2 1 Prep Demo3 3 Study For Exam4 2 Review User Feedback
id username
1 Michael2 Jay3 Zach
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Adding search to an API
Amazon DynamoDB
Low latency & high availability at any scale
Query patterns must be designed into the database structure
Amazon Elasticsearch Service
Rich DSL for full-text search and analytical queries
Often used as a secondary store for ad-hoc queries and analysis
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
DynamoDB + Amazon ES
DynamoDB
stream
Elasticsearch
index operation
Amazon DynamoDB AWS Lambda Amazon Elasticsearch
Service
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Amazon Elasticsearch Service resolvers
query SearchTasks {
searchTasksByTitle(
title: “Demo”
) {
items {
id
title
}
}
}
id userId title1 1 Write Slides2 1 Prep Demo3 3 Study For Exam4 2 Review User Feedback
GET /tasks/_search{
"query": {"match" : {
”title" : ”$ctx.args.title" }
} }
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Storing files
Amazon Simple Storage Service (Amazon S3)
A low latency, high-availability blob storage service
World-wide CDNs offer improved performance for mobile & web
Use AWS AppSync to store pointers to files in Amazon S3
Use S3 SDKs directly from the client for faster upload & download
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Storing files
type User {
id: ID!
username: String!
profilePicture: S3Object
}
type S3Object {
bucket: String
key: String
region: String
}
input S3ObjectInput {
bucket: String!
key: String!
region: String!
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Saving a filemutation CreateUser {
createUser(input: {
username: “janedoe”,
profilePicture: {
bucket: “unique-bucket-name”,
key: “path/to/file.png”,
region: “us-west-2” }
}) {
id
location { bucket key region }
}
}
AWS AppSync Amazon DynamoDB
Amazon Simple Storage Service (S3)
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Reading filesquery GetUser {
getUser(id: “1”) {
id
location {
bucket
key
region
}
}
}
AWS AppSync Amazon DynamoDB
Amazon Simple Storage Service (S3)
© 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.SUMM IT
Leveraging AWS Lambda
Add business logic to an API without deploying servers
Connect any data source, internal or external
Tip: Use a standard event structure when resolving fields with Lambda
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Use a standard event structure
type User {
id: ID!
username: String!
socialFriends: [SocialUser]
}
{
Arguments: $ctx.args,
TypeName: “User”,
FieldName: “socialFriends”,
Source: $ctx.source,
Identity: $ctx.identity
}
AWS Lambda
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Use a standard event structure
type Query {
pingLambda: Boolean
}
{
Arguments: $ctx.args,
TypeName: “Query”,
FieldName: “pingLambda”,
Source: $ctx.source,
Identity: $ctx.identity
}
AWS Lambda
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Should I use a single Lambda function everywhere?const =
=>
=>
const
const
return await
AWS Lambda
type User {
id: ID!
username: String!
socialFriends: [SocialUser]
}
type Query {
pingLambda: Boolean
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Should I use many Lambda functions?
type User {
id: ID!
username: String!
socialFriends: [SocialUser]
}
type Query {
pingLambda: Boolean
}
// Implement a specific User.socialFriends function
// Implement a specific Query.pingLambda function
AWS Lambda
AWS Lambda
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Should I use a single Lambda function everywhere?
Using a single function
Can benefit from container warming
One code base for all of your resolvers can be easier to manage
Using many functions
Greater separation of concerns
Deploy resolvers independently
Split ownership of resolver code to many teams
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Real-time data
GraphQL subscriptions enable real-time communication
AWS AppSync doubles as a pub-sub broker via MQTT over WebSockets
Common question:
How to authorize subscriptions?
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
GraphQL subscriptions
AWS AppSync subscriptions are reactions to mutations.
Subscribe to any mutation without additional configuration.
type Mutation {
publish: Message
}
type Subscription {
subscribe: Message
@aws_subscribe(mutations: [“publish”])
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Authorizing subscriptions
Naïve subscription implementations are subject to fan-out problems
If there are 1M connected clients, how do I authorize & route messages to each client?
1M connected clients * 1M messages = 1 trillion publishes
Two-part solution:
Authorize subscriptions at connect time
Have the topic names themselves transparently encode comparisons
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Authorizing subscriptions
type Subscription {
onCreateMessage(chatRoomId: ID!): Message
@aws_subscribe(mutations: [“createMessage”])
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Subscription queries open connections
subscription AuthorizedSubscription {
onCreateMessage(chatRoomId: “1”) {
id
chatRoomId
content
}
}
chatRoomId
Amazon DynamoDB
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Subscription queries open connectionssubscription AuthorizedSubscription {
onCreateMessage(chatRoomId: “1”) {
…
}
}
subscription AuthorizedSubscription {
onCreateMessage(chatRoomId: “2”) {
…
}
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Mutations publish to topics
mutation CreateMessage {
createMessage(input: {…}) {
id
chatRoomId
content
}
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Authorizing subscriptions
Subscriptions are no longer subject to fan-out problems
Publishes are no longer dependent on the # of connected clients.
32 (max) topics * 1M messages = 32M (max) publishes.
This is now a manageable problem.
The solution (recap):
Authorize subscriptions at connect time.
Have the topic names themselves transparently encode any comparisons.
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Rapid development techniques
AWS Amplify CLI generates infrastructure via AWS CloudFormation
Leverage the GraphQL transform to quickly build out data models
Use the AWS Amplify framework and codegen to build client apps
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
AWS Amplify CLI
$ amplify add auth
$ amplify add storage
$ amplify add api
$ amplify push
Add an Amazon Cognito User Pool
Create and secure an Amazon S3 bucket
Add an AWS AppSync API
Deploy via AWS CloudFormation
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Rapidly build scalable, data-driven applications
Leverages a new open-source project called the GraphQL Transform
Declaratively define your application’s data model using GraphQL SDL
Check your schema.graphql into git for easy version control
Transforms your data model into a CloudFormation document that implements it
Powered by GraphQL directives
Comes with a set of pre-built transformers
@model, @auth, @connection, @versioned, and @searchable
AWS Amplify + AWS AppSync
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
The @model transformer
# schema.graphql
type Post @model {
id: ID!
title: String!
}
$ amplify add api
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
And voila!AWS Cloud
AWS AppSync
Post Table
createPost
updatePost
deletePost
Mutation Post Table Data Source
Schema
type Mutation {
createPost(...): Post
updatePost(...): Post
deletePost(...): Post
}
type Query {
getPost(...): Post
listPosts(...): Post
}
type Subscription {
onCreatePost(...): Post
onUpdatePost(...): Post
onDeletePost(...): Post
}
type Post {
id: ID!
title: String
createdAt: String
updatedAt: String
}
Resolvers Data Sources
Amazon DynamoDB
IAM Role ARN
getPost
listPosts
Query
Post Table ARN
type Post @model {
id: ID!
title: String!
}
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
@authtype Post @model @auth(rules: [
{ allow: owner }
]) {
id: ID!
title: String!
owner: String
}
AWS Cloud
AWS AppSync
createPost
updatePost
deletePost
Mutation Post Table Data Source
getPost
listPosts
Query
Schema
type Mutation {
createPost(...): Post
updatePost(...): Post
deletePost(...): Post
}
type Query {
getPost(...): Post
listPosts(...): Post
}
type Subscription {
onCreatePost(...): Post
onUpdatePost(...): Post
onDeletePost(...): Post
}
Resolvers Data Sources
Amazon DynamoDB
Post Table ARN
Role Post Table
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
@connection
type Post @model {
id: ID!
title: String!
comments: [Comment!]!
@connection(name: “PostComments”)
}
type Comment @model {
id: ID!
content: String!
post: Post
@connection(name: “PostComments”)
}
AWS Cloud
AWS AppSync
createPost
updatePost
deletePost
Mutation Post Data Source
getPost
listPosts
Query
Schematype Mutation {
createPost(...): Post
updatePost(...): Post
deletePost(...): Post
createComment(...): Comment
updateComment(...): Comment
deleteComment(...): Comment
}
type Query {
getPost(...): Post
listPosts(...): Post
getComment(...): Comment
listComment(...): Comment
}
type Subscription {
onCreatePost(...): Post
onUpdatePost(...): Post
onDeletePost(...): Post
onCreateComment(...): Comment
onUpdateComment(...): Comment
onDeleteComment(...): Comment
}
Resolvers Data Sources
Amazon DynamoDB
Post Table ARN
Role
createComment
updateComment
deleteComment
getComment
listComment
post
Comment
comment
Post
Comment Data Source
Comment Table ARN
Role Comment Table
Post Table
gsi-PostComments
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Querying with @connectionquery GetPostAndComments {
getPost(id: ”1”) {
id
title
comments(limit: 10, nextToken: “...”) {
items {
id
content
}
nextToken
}
}
}
gsi-PostComments
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
AWS Amplify codegen
$ amplify add codegen
$ amplify codegen
# Generate queries & native types from your GraphQL API
# for iOS, Android, TypeScript, and Flow
© 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.SUMM IT
Offline and delta sync
Hydrate offline cache from main entity table
Only synchronize the “delta” when coming back online
Reconnection, exponential back off, and retries handled by client
Base query, subscription, and delta query work together
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
CreatePost1 (x)CreateDelta1 (x)
createPost(input: CreatePostInput!): Post
mutation add{createPost(Name:”Nadia” Title:“Hello World” ){
idNameTitle
}}
ID Name Title … timestamp expdate
1 Nadia Hello World 1541884315162 1541884320
2 Pancho I’m Sleepy 1541884359527 1541884364
3 Shaggy Running in the park
1541884404015 1541884409
ID Name Title …
1 Nadia Hello World
2 Pancho I’m Sleepy
3 Shaggy Running in the park
Delta TablePost Table
Schema
Pipeline resolver
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
(Scan/Query, optional paginate, etc.)
Query list{listPosts{
items {idNameTitle
}}
}
ID Name Title … timestamp expdate
1 Nadia Hello World 1541884315162 1541884320
2 Pancho I’m Sleepy 1541884359527 1541884364
3 Shaggy Running in the park
1541884404015 1541884409
ID Name Title …
1 Nadia Hello World
2 Pancho I’m Sleepy
3 Shaggy Running in the park
Delta TablePost Table
Unit resolver
listPosts : [Post]Schema
listDeltas(lastSync: AWSTimestamp!) : [DeltaPost]
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
lastSync <= timestamp AND expdate > NOW
Query list{listDeltas(lastSync:1541884330){
items {idNameTitle
}}
}
ID Name Title … timestamp expdate
1 Nadia Hello World 1541884315162 1541884320
2 Pancho I’m Sleepy 1541884359527 1541884364
3 Shaggy Running in the park
1541884404015 1541884409
ID Name Title …
1 Nadia Hello World
2 Pancho I’m Sleepy
3 Shaggy Running in the park
Delta TablePost TableConditional Scan/Query
Unit resolver
listPosts : [Post]Schema
listDeltas(lastSync: AWSTimestamp!) : [DeltaPost]
© 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.SUMM IT
CI/CD with AWS Amplify Console
Workflow for serverless web apps
Manage multiple environments based on feature branches in git
Atomic deployments via Lambda@Edge
Connect to GitHub, Bitbucket, GitLab, and AWS CodeCommit
Simple web hosting with custom domains
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Multiple environments and team workflows
$ amplify env add
// develop mysandbox feature branch
$ amplify push
// move changes to dev environments/branches
$ git checkout dev
$ amplify env checkout dev
$ git merge mysandbox
$ amplify push
$ git push -u origin dev
© 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.SUMM IT
Monitor operational and business metrics
Enable Amazon CloudWatch logs in your AWS AppSync API
APIs come with CloudWatch metrics out of the boxSet alarms on 4xx, 5xx, and latency
$ amplify api console
$ amplify add analytics Powerful client-side monitoring with Amazon Pinpoint
Use the AWS Amplify Analytics category
© 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.SUMM IT
Audit resolvers
Pipeline resolvers to interact with multiple data sources
Made up of many “functions” (not the same as Lambda functions)
Create an “Audit” function to report information about a resolver
Attach to all resolvers you want to audit
Create custom alerts, reports, metrics
© 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.SUMM IT
Test APIs
Integration Tests
Trigger integration tests after AWS Amplify console deployments
Run tests in AWS Lambda and trigger using CloudWatch events
Canaries
Try out CloudWatch scheduled events + AWS Lambda
Generate test suites and fuzz APIs
Introspect your APIs schema to generate test suites automatically
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Schema governance
Centralized schema governance
Have one team manage the schema for the whole organization
Easier than decentralized governance but puts one team on the spot
Decentralized schema governance
AWS AppSync APIs currently require a single schema document
Build client tools to combine schemas from multiple teams into a single document
GraphQL type extensions allow teams to manage their own sub graph
Thank you!
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.
Daniel [email protected]@btx94
SUMM IT © 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved.