GraphQL in production within Minutes with Serverless

Nik Graf
Sebastian Siemssen

https://swapi.co/

GET api/people/1

{
      "name": "Luke Skywalker",
      "height": "1.72 m",
      "mass": "77 Kg",
      "hair_color": "Blond",
      "skin_color": "Caucasian",
      "eye_color": "Blue",
      "birth_year": "19 BBY",
      "gender": "Male",
      "homeworld": "http://swapi.co/api/planets/1/",
      "films": [
          "http://swapi.co/api/films/1/",
          "http://swapi.co/api/films/2/",
          "http://swapi.co/api/films/3/"
      ],
      "species": [
          "http://swapi.co/api/species/1/"
      ],
      "vehicles": [
          "http://swapi.co/api/vehicles/14/",
          "http://swapi.co/api/vehicles/30/"
      ],
      "starships": [
          "http://swapi.co/api/starships/12/",
          "http://swapi.co/api/starships/22/"
      ],
      "created": "2014-12-09T13:50:51.644000Z",
      "edited": "2014-12-10T13:52:43.172000Z",
      "url": "http://swapi.co/api/people/1/"
}

GET api/films/1

GET api/films/2

GET api/films/3

Often we are overfetching and underfetching at the same time causing bloated payloads and additional roundtrips

Often we are overfetching and underfetching at the same time causing bloated payloads and additional roundtrips

We can solve that

GET api/persons-with-films/1

GET api/persons/1?include-films

GET api/persons/1?include=films,...

App #1

App #2

App #3

App #4

Multiple apps

App #1

App #2

App #3

App #4

Multiple versions of multiple apps

Complexity and code debt spiraling out of control

A data querying language

Runs on arbitrary code ...

Backed by a schema ...

Based on a type system ...

Making it fully introspective ...

It runs on arbitrary code.

It is completely agnostic of your storage layer and can potentially support any backend architecture.

Anatomy of a GraphQL Server

GraphQL

Your application code

Database

Internal APIs

Remote APIs

Type definitions

Evolving the server–client relationship

Server publishes possibilities

Client specifies concrete data requirements

Schema

Each schema is an arbitrarily nested hierarchy of type definitions.

Introspection

Exposing your own schema through the type system.

Serverless

So what is Serverless?

  • Serverless Architectures
  • Serverless Conference
  • Serverless Framework
  • Serverless Inc.
  • Serverless Application Model

Serverless Architectures

Serverless architectures refer to applications that significantly depend on third-party services (knows as Backend as a Service or "BaaS") or on custom code that's run in ephemeral containers (Function as a Service or "FaaS"), the best known vendor host of which currently is AWS Lambda. By using these …

Mike Roberts
https://martinfowler.com/articles/serverless.html

Function as a unit of application logic

How does it work?

  • New instance on every invocation *
  • Events trigger a function

* (somewhat)

Isn't that limiting?

Hell yeah! 🙃


Just let me code and focus on the product!

History

Machines in

Datacenters

Virtualization

in the Cloud

Containers

in the Cloud

Serverless

in the Cloud

Benefits

  • 📈 Auto scaling
  • 💵 Pay per execution
  • 🎪 Event Driven

Common use cases

  • 🕸 Web Applications
  • 📱 Mobile & IoT Backends
  • 💹 Data Processing
  • 🤖 Chatbots

Some limitations prevent certain use cases.

Providers

  • AWS Lambda
  • Google Cloud Functions
  • Microsoft Functions
  • IBM Openwhisk
  • Auth0 Webtasks
  • IronIO IronFunctions

Hard Numbers

  • Thomson Reuter processes 4000 requests/second
  • Finra processes half a trillion validations of stock trades daily
  • Vevo can handle spikes of 80x normal traffic

Challenges

  • Monitoring
  • Cold starts
  • Development flow
  • Application architecture
  • Service Discovery
  • Service Communication

Deployment Options

  • AWS User Interface
  • API calls (Serverless Framework v0, Claudia, Zappa)
  • CloudFormation (Serverless Framework v1, SAM)

Lambda via the UI

Lambda via an API call

 var params = {
  Code: {}, 
  Description: "", 
  FunctionName: "MyFunction", 
  Handler: "souce_file.handler_name",
  MemorySize: 128, 
  Publish: true, 
  Role: "arn:aws:iam::123456789012:role/service-role/role-name",
  Runtime: "nodejs4.3", 
  Timeout: 15, 
  VpcConfig: {}
 };
 lambda.createFunction(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data);
 });

Lambda via CloudFromation

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "The AWS CloudFormation template for this Serverless application",
  "Resources": {
    "ServerlessDeploymentBucket": {
      "Type": "AWS::S3::Bucket"
    },
    "IamRoleLambdaExecution": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "/",
        "RoleName": {
          "Fn::Join": [
            "-",
            [
              "serverless-simple-http-endpoint",
              "dev",
              "us-east-1",
              "lambdaRole"
            ]
          ]
        }
      }
    },
    "IamPolicyLambdaExecution": {
      "Type": "AWS::IAM::Policy",
      "DependsOn": [
        "IamRoleLambdaExecution"
      ],
      "Properties": {
        "PolicyName": {
          "Fn::Join": [
            "-",
            [
              "dev",
              "serverless-simple-http-endpoint",
              "lambda"
            ]
          ]
        },
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream"
              ],
              "Resource": "arn:aws:logs:us-east-1:*:*"
            },
            {
              "Effect": "Allow",
              "Action": [
                "logs:PutLogEvents"
              ],
              "Resource": "arn:aws:logs:us-east-1:*:*"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "IamRoleLambdaExecution"
          }
        ]
      }
    },
    "CurrentTimeLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/serverless-simple-http-endpoint/dev/1484930732222-2017-01-20T16:45:32.222Z/serverless-simple-http-endpoint.zip"
        },
        "FunctionName": "serverless-simple-http-endpoint-dev-currentTime",
        "Handler": "handler.endpoint",
        "MemorySize": 1024,
        "Role": {
          "Fn::GetAtt": [
            "IamRoleLambdaExecution",
            "Arn"
          ]
        },
        "Runtime": "nodejs4.3",
        "Timeout": 6
      },
      "DependsOn": [
        "IamPolicyLambdaExecution",
        "IamRoleLambdaExecution"
      ]
    },
    "CurrentTimeLambdaVersionZ1TELpJKRofoApVATkwyg3d6mnjiRs3VIERorWczoY": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "CurrentTimeLambdaFunction"
        },
        "CodeSha256": "Z1TELpJKRofoApVATkwyg3d6mnjiRs3VIERorW/czoY="
      }
    },
    "ApiGatewayRestApi": {
      "Type": "AWS::ApiGateway::RestApi",
      "Properties": {
        "Name": "dev-serverless-simple-http-endpoint"
      }
    },
    "ApiGatewayResourcePing": {
      "Type": "AWS::ApiGateway::Resource",
      "Properties": {
        "ParentId": {
          "Fn::GetAtt": [
            "ApiGatewayRestApi",
            "RootResourceId"
          ]
        },
        "PathPart": "ping",
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        }
      }
    },
        "Roles": [
          {
            "Ref": "IamRoleLambdaExecution"
          }
        ]
      }
    },
    "CurrentTimeLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/serverless-simple-http-endpoint/dev/1484930732222-2017-01-20T16:45:32.222Z/serverless-simple-http-endpoint.zip"
        },
        "FunctionName": "serverless-simple-http-endpoint-dev-currentTime",
        "Handler": "handler.endpoint",
        "MemorySize": 1024,
        "Role": {
          "Fn::GetAtt": [
            "IamRoleLambdaExecution",
            "Arn"
          ]
        },
        "Runtime": "nodejs4.3",
        "Timeout": 6
      },
      "DependsOn": [
        "IamPolicyLambdaExecution",
        "IamRoleLambdaExecution"
      ]
    },
    "CurrentTimeLambdaVersionZ1TELpJKRofoApVATkwyg3d6mnjiRs3VIERorWczoY": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "CurrentTimeLambdaFunction"
        },
        "CodeSha256": "Z1TELpJKRofoApVATkwyg3d6mnjiRs3VIERorW/czoY="
      }
    },
    "ApiGatewayRestApi": {
      "Type": "AWS::ApiGateway::RestApi",
      "Properties": {
        "Name": "dev-serverless-simple-http-endpoint"
      }
    },
    "ApiGatewayResourcePing": {
      "Type": "AWS::ApiGateway::Resource",
      "Properties": {
        "ParentId": {
          "Fn::GetAtt": [
            "ApiGatewayRestApi",
            "RootResourceId"
          ]
        },
        "PathPart": "ping",
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        }
      }
    },
    "ApiGatewayMethodPingGet": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "HttpMethod": "GET",
        "RequestParameters": {},
        "ResourceId": {
          "Ref": "ApiGatewayResourcePing"
        },
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        },
        "AuthorizationType": "NONE",
        "Integration": {
          "IntegrationHttpMethod": "POST",
          "Type": "AWS_PROXY",
          "Uri": {
            "Fn::Join": [
              "",
              [
                "arn:aws:apigateway:",
                {
                  "Ref": "AWS::Region"
                },
                ":lambda:path/2015-03-31/functions/",
                {
                  "Fn::GetAtt": [
                    "CurrentTimeLambdaFunction",
                    "Arn"
                  ]
                },
                "/invocations"
              ]
            ]
          }
        },
        "MethodResponses": []
      }
    },
    "ApiGatewayDeployment1484930732234": {
      "Type": "AWS::ApiGateway::Deployment",
      "Properties": {
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        },
        "StageName": "dev"
      },
      "DependsOn": [
        "ApiGatewayMethodPingGet"
      ]
    },

    "CurrentTimeLambdaPermissionApiGateway": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "CurrentTimeLambdaFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Join": [
            "",
            [
              "arn:aws:execute-api:",
              {
                "Ref": "AWS::Region"
              },
              ":",
              {
                "Ref": "AWS::AccountId"
              },
              ":",
              {
                "Ref": "ApiGatewayRestApi"
              },
              "/*/*"
            ]
          ]
        }
      }
    }
  },
  "Outputs": {
    "ServerlessDeploymentBucketName": {
      "Value": {
        "Ref": "ServerlessDeploymentBucket"
      }
    },
    "CurrentTimeLambdaFunctionArn": {
      "Description": "Lambda function info",
      "Value": {
        "Fn::GetAtt": [
          "CurrentTimeLambdaFunction",
          "Arn"
        ]
      }
    },
    "CurrentTimeLambdaFunctionQualifiedArn": {
      "Description": "Current Lambda function version",
      "Value": {
        "Ref": "CurrentTimeLambdaVersionZ1TELpJKRofoApVATkwyg3d6mnjiRs3VIERorWczoY"
      }
    },
    "ServiceEndpoint": {
      "Description": "URL of the service endpoint",
      "Value": {
        "Fn::Join": [
          "",
          [
            "https://",
            {
              "Ref": "ApiGatewayRestApi"
            },
            ".execute-api.us-east-1.amazonaws.com/dev"
          ]
        ]
      }
    }
  }
}

                    Framework

# serverless.yml
service: serverless-simple-http-endpoint

frameworkVersion: ">=1.1.0 <2.0.0"

provider:
  name: aws
  runtime: nodejs4.3

functions:
  currentTime:
    handler: handler.endpoint
    events:
      - http:
          path: ping
          method: get
// handler.js
module.exports.endpoint = (event, context, callback) => {
  const time = new Date().toTimeString();
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: `Hello, the current time is ${time}.`,
    }),
  };

  callback(null, response);
};

Serverless Architecture
❤️
GraphQL

import {
  GraphQLObjectType,
  GraphQLString,
} from 'graphql';

const MeetupType = new GraphQLObjectType({
  name: 'Meetup',
  description: 'A gathering of people',
  fields: () => ({
    name: {
      type: GraphQLString,
      description: 'The name of the meetup',
    },
    description: {
      type: GraphQLString,
      description: 'The long description of the meetup',
    },
  }),
});

export default MeetupType;
// schema.js
const QueryType = new GraphQLObjectType({
  name: 'Query',
  fields: () => ({
    meetups: {
      type: new GraphQLList(MeetupType),
      resolve: () => endpoints.getMeetups(),
    }
  }),
});

export default new GraphQLSchema({
  query: QueryType,
});
// handler.js
const handle = require('./schema');

module.exports.graphql = (event, context, callback) => {
  handle(event.body.query, event.body.variables)
    .then((response) => callback(null, response))
    .catch((error) => callback(error));
};
// getMeetups.js
import fetch from 'node-fetch';

const url = 'https://api.meetup.com/viennajs/events?photo-host=secure
&sig_id=12607916&sig=d06a49439dafad0b17b5a275f882cc1f79711430';

export default () => fetch(url).then((res) => res.json());

Questions

GraphQL + Serverless

By Sebastian Siemssen

GraphQL + Serverless

  • 3,103