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,091