Introducere

Construiesc API-uri GraphQL într-un mediu Serverless de peste 3 ani. Nici nu-mi pot imagina să lucrez cu API-urile RESTful. Combinați puterea GraphQL cu scalabilitatea AWS Lambda și aveți un server care poate gestiona cantități infinite de trafic.

În acest tutorial, vom construi și implementa un server GraphQL pe AWS Lambda și îl vom accesa printr-un punct final API Gateway. Vom folosi CloudFormation și AWS CLI pentru a implementa toate resursele și codul aplicației AWS.

Ce vom acoperi

  1. Construiți un server GraphQL folosind Apollo
  2. Implementați acel server GraphQL pe Lambda
  3. Utilizați API Gateway pentru a solicita proxy către Lambda
  4. Utilizați CloudFormation pentru a implementa stiva de aplicații în AWS
  5. Configurați Lambda pentru dezvoltare locală.

TL; DR – Puteți obține codul sursă complet pentru aplicație de la Github.

Ce este GraphQL?

GraphQL este un limbaj de interogare pentru descrierea API-urilor folosind un sistem de schemă puternic tastat. Un server GraphQL îndeplinește aceste interogări folosind datele existente. Următoarele sunt câteva dintre principalele avantaje ale utilizării GraphQL.

Interogați doar de ce are nevoie aplicația dvs.

Spre deosebire de API-urile REST, GraphQL permite clienților să interogheze cu precizie și numai ceea ce au nevoie. Serverul îndeplinește cererea clientului returnând doar ceea ce solicită clientul.

GraphQL folosește un sistem puternic tastat

Sistemul puternic tastat GraphQL permite utilizatorilor să introspecteze întreaga schemă. Iar API-ul GraphQL servește ca documentație clară despre capacitățile serverului și vă anunță despre erori în timpul dezvoltării.

Puteți compune interogările dvs. într-o singură cerere

Cu GraphQL, puteți interoga mai multe resurse și puteți obține răspunsuri combinate cu o singură cerere. Cu mai puține solicitări, aplicațiile care utilizează GraphQL au performanțe mult mai rapide.

Ce este AWS Lambda?

AWS Lambda este un serviciu de calcul oferit de AWS care vă permite să rulați codul aplicației fără a fi nevoie să gestionați niciun server. AWS gestionează toate cheltuielile generale, precum infrastructura, securitatea, resursele, sistemul de operare și patch-urile, astfel încât dezvoltatorii să se poată concentra doar pe construirea aplicației.

Să începem…

Configurarea proiectului

Să începem prin crearea unui folder de proiect. Apoi, schimbați-vă în director și inițializați un proiect Node. eu folosesc node 10.x în exemple. Puteți instala versiunea de nod la alegere folosind asdf.

mkdir apollo-server-lambda-nodejs 
cd apollo-server-lambda-nodejs 
yarn init

Apoi, creați un folder care găzduiește tot codul nostru sursă.

mkdir src

În cele din urmă, creați un fișier index în interiorul fișierului src director care servește ca handler lambda.

cd src
touch index.js
Cum sa construiti si sa implementati un server GraphQL in
Inițializați proiectul nodului

Completați fișierul index cu următorul cod.

exports.handler = async () => {  
    return { 
        body: 'Hello from Lambda' 
    };
};

Codul de mai sus este un manipulator Lambda foarte simplu, care va reveni Hello from Lambda când este invocat. Să implementăm mai întâi codul nostru în AWS Lambda.

Pachetează codul aplicației

Înainte de a putea implementa codul nostru în Lambda, trebuie să creăm un zip al aplicației noastre și să-l încărcăm într-o bucket S3. Folosim AWS CLI pentru a crea cupa. Configurați AWS CLI acum urmând acest ghid dacă nu ați făcut deja acest lucru.

Creați o bucket S3 pe care să o folosiți pentru implementarea codului nostru în Lambda. Alegeți un nume unic pentru cupa S3. Numele cupei sunt unice la nivel global în toate regiunile AWS.

aws s3 mb s3://lambda-deploy-asln

Creați o arhivă a aplicației utilizând comanda zip și verificați fișierele din interiorul zip.

zip -rq dist-latest.zip src package.json 
zipinfo dist-latest.zip

Copiați fișierul zip în S3 utilizând comanda AWS CLI.

aws s3 cp dist-latest.zip s3://lambda-deploy-asln/dist-latest.zip

În cele din urmă, utilizați următoarea comandă pentru a verifica dacă fișierul există în S3.

aws s3 ls s3://lambda-deploy-asln
1611248589 256 Cum sa construiti si sa implementati un server GraphQL in
Copiați pachetul de aplicații în S3

Acum, că am implementat aplicația ambalată pe S3, în continuare trebuie să ne configurăm Lambda și API Gateway în AWS. În secțiunea următoare, vom folosi CloudFormation pentru a configura toate resursele AWS necesare.

Configurați AWS lambda cu integrarea proxy gateway API

CloudFormation este un serviciu AWS care ne ajută să scriem infrastructura ca cod. CloudFormation simplifică crearea și gestionarea resurselor aplicației noastre. Să folosim CloudFormation pentru a ne defini stiva.

Creați un fișier numit cloudformation.yml la rădăcina proiectului.

touch cloudformation.yml

Adăugați următorul cod la cloudformation.yml

---
Description: GraphQL server on AWS lambda

Parameters:
  Version:
    Description: Application version number
    Type: String

  BucketName:
    Description: S3 bucket name where the source code lives
    Type: String

Resources:
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: !Ref BucketName
        S3Key: !Sub dist-${Version}.zip
      Handler: src/index.handler
      Description: GraphQL Apollo Server
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: nodejs10.x
      Timeout: 10

  LambdaExecutionRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Policies:
        - PolicyName: "LambdaFunctionPolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
              Resource: "*"

  GraphQLApi:
    Type: 'AWS::ApiGateway::RestApi'
    Properties:
      Name: apollo-graphql-api

  GraphQLApiResource:
    Type: 'AWS::ApiGateway::Resource'
    Properties:
      ParentId: !GetAtt GraphQLApi.RootResourceId
      RestApiId: !Ref GraphQLApi
      PathPart: 'graphql'

  GraphQLApiMethod:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      RestApiId: !Ref GraphQLApi
      ResourceId: !Ref GraphQLApiResource
      AuthorizationType: None
      HttpMethod: POST
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations

  GraphQLApiDeployment:
    Type: 'AWS::ApiGateway::Deployment'
    Properties:
      RestApiId: !Ref GraphQLApi
      StageName: v1
    DependsOn:
      - GraphQLApiResource
      - GraphQLApiMethod

  GraphQLApiPermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: lambda:invokeFunction
      FunctionName: !GetAtt LambdaFunction.Arn
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${GraphQLApi}/*

Outputs:
  ApiUrl:
    Description: Invoke url of API Gateway endpoint
    Value: !Sub https://${GraphQLApi}.execute-api.${AWS::Region}.amazonaws.com/v1/graphql

Știu că se întâmplă multe în acest șablon. Să examinăm codul pas cu pas.

Parametrii șablonului

În primul rând, definim câțiva parametri pe care îi folosim în șablon. Putem trece aceste variabile ca suprascrieri de parametri atunci când implementăm CloudFormation Stack.

Description: GraphQL server on AWS lambda

Parameters:
  Version:
    Description: Application version number
    Type: String

  BucketName:
    Description: S3 bucket name where the source code lives
    Type: String

Funcția Lambda

Ne definim funcția lambda specificând calea de unde ar trebui să extragă codul aplicației. Această găleată este aceeași pe care am creat-o mai devreme.

LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: !Ref BucketName
        S3Key: !Sub dist-${Version}.zip
      Handler: src/index.handler
      Description: GraphQL Apollo Server
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: nodejs10.x
      Timeout: 10

Vrem ca funcția noastră Lambda să poată trimite jurnale de aplicații către AWS CloudWatch. Lambda necesită permisiuni speciale pentru a putea scrie jurnale în CloudWatch. Așadar, creăm un rol care permite scrierea în CloudWatch și îl atribuim funcției Lambda.

LambdaExecutionRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Policies:
        - PolicyName: "LambdaFunctionPolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
              Resource: "*"

Gateway API

De asemenea, dorim ca un punct final HTTP să invoce funcția lambda. API Gateway poate fi utilizat pentru a crea un punct final HTTP. Apoi putem configura API Gateway pentru a proxy toate cererile primite de la client către funcția Lambda și pentru a trimite răspunsul de la Lambda înapoi către client.

În primul rând, creăm un API Gateway RestApi.

GraphQLApi:
    Type: 'AWS::ApiGateway::RestApi'
    Properties:
      Name: apollo-graphql-api

Apoi, creăm o resursă Gateway API, care acceptă solicitările la /graphql.

GraphQLApiResource:
    Type: 'AWS::ApiGateway::Resource'
    Properties:
      ParentId: !GetAtt GraphQLApi.RootResourceId
      RestApiId: !Ref GraphQLApi
      PathPart: 'graphql'

Apoi, configurăm Resursa pentru a accepta cereri POST prin crearea unei Metode Gateway API și apoi o integrăm cu Lambda.

GraphQLApiMethod:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      RestApiId: !Ref GraphQLApi
      ResourceId: !Ref GraphQLApiResource
      AuthorizationType: None
      HttpMethod: POST
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations

În cele din urmă, creăm un API Gateway Deployment care implementează API-ul în etapa specificată.

GraphQLApiDeployment:
    Type: 'AWS::ApiGateway::Deployment'
    Properties:
      RestApiId: !Ref GraphQLApi
      StageName: v1
    DependsOn:
      - GraphQLApiResource
      - GraphQLApiMethod

Permisiunea Lambda / API Gateway

În acest moment, avem atât funcția Lambda cât și gateway-ul API configurate corect. Cu toate acestea, API Gateway are nevoie de permisiuni speciale pentru a invoca o funcție Lambda. Permitem API Gateway să invoce Lambda prin crearea unei resurse Lambda Permission.

GraphQLApiPermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: lambda:invokeFunction
      FunctionName: !GetAtt LambdaFunction.Arn
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${GraphQLApi}/*

În cele din urmă, exportăm URL-ul API la sfârșitul șablonului. Putem folosi această adresă URL pentru a invoca apeluri către Lambda.

Outputs:
  ApiUrl:
    Description: Invoke url of API Gateway endpoint
    Value: !Sub https://${GraphQLApi}.execute-api.${AWS::Region}.amazonaws.com/v1/graphql

Implementați stiva CloudFormation în AWS

Acum că avem șablonul CloudFormation gata, să folosim comanda AWS CLI pentru a-l implementa în AWS.

Rulați următoarea comandă în consola dvs. Asigurați-vă că actualizați BucketName la oricare ar fi numele bucket-ului pe care l-ați creat anterior.

aws cloudformation deploy 
  --template-file ./cloudformation.yml 
  --stack-name apollo-server-lambda-nodejs 
  --parameter-overrides BucketName=lambda-deploy-asln Version=latest 
  --capabilities CAPABILITY_IAM
1611248589 329 Cum sa construiti si sa implementati un server GraphQL in
Implementați stiva CloudFormation în AWS

Este posibil să dureze ceva timp pentru a implementa stiva. Funcția Lambda ar trebui să fie gata să înceapă preluarea solicitărilor la finalizarea implementării.

Verificați API Gateway și Lambda funcționează conform așteptărilor

Acum că am implementat CloudFormation Stack, permiteți-ne să verificăm dacă totul funcționează conform așteptărilor. Avem nevoie de adresa URL Gateway API pentru a trimite o cerere către Funcția noastră Lambda. URL-ul API pe care l-am exportat în șablonul CloudFormation este util aici.

Rulați următoarea comandă pentru a imprima URL-ul API în linia de comandă.

aws cloudformation describe-stacks 
--stack-name=apollo-server-lambda-nodejs 
--query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" 
--output text
1611248590 139 Cum sa construiti si sa implementati un server GraphQL in
Descrieți CloudFormation Stack

Acum, utilizați curl comandă pentru a invoca URL-ul API. Ar trebui să primiți „Hello from Lambda” înapoi de pe server.

curl -d '{}' https://o55ybz0sc5.execute-api.us-east-1.amazonaws.com/v1/graphql
1611248590 984 Cum sa construiti si sa implementati un server GraphQL in
Invocați AWS Lambda

Adăugați un script de implementare pentru o implementare mai ușoară

Este posibil să fi observat că am rulat o grămadă de comenzi pentru a împacheta și a implementa aplicația noastră. Ar fi foarte plictisitor să trebuiască să rulăm acele comenzi de fiecare dată când implementăm aplicația. Să adăugăm un script bash pentru a simplifica acest flux de lucru.

Creați un director numit bin la rădăcina aplicației și adăugați un fișier numit deploy.

mkdir bin 
touch bin/deploy

Înainte de a putea executa scriptul, trebuie să setăm permisiuni de fișiere corecte. Să facem asta executând următoarea comandă.

chmod +x bin/deploy
1611248590 124 Cum sa construiti si sa implementati un server GraphQL in
Creați un script de implementare

În acest moment, structura noastră de directoare ar trebui să arate ca în captura de ecran de mai jos.

1611248590 480 Cum sa construiti si sa implementati un server GraphQL in
Structura actuală a directorului

Adăugați următorul cod în fișier.

#!/bin/bash

set -euo pipefail

OUTPUT_DIR=dist
CURRENT_DIR=$(pwd)
ROOT_DIR="$( dirname "${BASH_SOURCE[0]}" )"/..
APP_VERSION=$(date +%s)
STACK_NAME=apollo-server-lambda-nodejs

cd $ROOT_DIR

echo "cleaning up old build.."
[ -d $OUTPUT_DIR ] && rm -rf $OUTPUT_DIR

mkdir dist

echo "zipping source code.."
zip -rq $OUTPUT_DIR/dist-$APP_VERSION.zip src node_modules package.json

echo "uploading source code to s3.."
aws s3 cp $OUTPUT_DIR/dist-$APP_VERSION.zip s3://$S3_BUCKET/dist-$APP_VERSION.zip

echo "deploying application.."
aws cloudformation deploy 
  --template-file $ROOT_DIR/cloudformation.yml 
  --stack-name $STACK_NAME 
  --parameter-overrides Version=$APP_VERSION BucketName=$S3_BUCKET 
  --capabilities CAPABILITY_IAM

# Get the api url from output of cloudformation stack
API_URL=$(
  aws cloudformation describe-stacks 
  --stack-name=$STACK_NAME 
  --query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" 
  --output text
)

echo -e "n$API_URL"

cd $CURRENT_DIR

OK, să descompunem ce se întâmplă în acest script.

Începem prin definirea unor variabile. Generăm fișierul arhivă în interiorul dist director. Am setat versiunea aplicației la marcajul de timp curent la care rulează scriptul. Folosind marca temporală, ne putem asigura că numărul versiunii este întotdeauna unic și incremental.

#!/bin/bash

set -euo pipefail

OUTPUT_DIR=dist
CURRENT_DIR=$(pwd)
ROOT_DIR="$( dirname "${BASH_SOURCE[0]}" )"/..
APP_VERSION=$(date +%s)
STACK_NAME=apollo-server-lambda-nodejs

Apoi curățăm orice versiuni vechi și creăm o nouă dist director.

echo "cleaning up old build.."
[ -d $OUTPUT_DIR ] && rm -rf $OUTPUT_DIR

mkdir dist

Apoi executăm comanda zip pentru a arhiva codul sursă și dependențele acestuia.

echo "zipping source code.."
zip -rq $OUTPUT_DIR/dist-$APP_VERSION.zip src node_modules package.json

Apoi, copiem fișierul zip în cupa S3.

echo "uploading source code to s3.."
aws s3 cp $OUTPUT_DIR/dist-$APP_VERSION.zip s3://$S3_BUCKET/dist-$APP_VERSION.zip

Apoi implementăm stiva CloudFormation.

echo "deploying application.."
aws cloudformation deploy 
  --template-file $ROOT_DIR/cloudformation.yml 
  --stack-name $STACK_NAME 
  --parameter-overrides Version=$APP_VERSION BucketName=$S3_BUCKET 
  --capabilities CAPABILITY_IAM

În cele din urmă, interogăm CloudFormation Stack pentru a obține adresa URL a API-urilor din CloudFormation Outputs și a o imprima în consolă.

# Get the api url from output of cloudformation stack
API_URL=$(
  aws cloudformation describe-stacks 
  --stack-name=$STACK_NAME 
  --query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" 
  --output text
)

echo -e "n$API_URL"

Implementați în AWS utilizând scriptul de implementare

Să încercăm implementarea folosind scriptul deploy. Scriptul se așteaptă ca variabila S3_Bucket să fie prezentă în mediu. Rulați următoarea comandă pentru a rula implementarea. Când implementarea are succes, scriptul va afișa URL-ul API pe care îl putem folosi pentru a invoca lambda.

S3_BUCKET=lambda-deploy-asln ./bin/deploy
1611248590 147 Cum sa construiti si sa implementati un server GraphQL in
Rulați scriptul de implementare

Pentru a simplifica și mai mult acest lucru, să-l invocăm folosind fire. Adăugați următoarele în package.json.

"scripts": {
  "deploy": "S3_BUCKET=lambda-deploy-asln ./bin/deploy"
}

În continuare putem pur și simplu să alergăm yarn deploy pentru a iniția implementări.

Îmbunătățiți fluxul de lucru cu Lambda local și API Gateway

Am modificat frecvent codul aplicației în timp ce lucram la aplicația noastră. Chiar acum, implementarea în regiunea AWS us-est-1 durează aproximativ 10 secunde. Am o conexiune la internet cu viteză de încărcare de 40 Mb / s.

Timpul de implementare devine mai semnificativ pe măsură ce crește dimensiunea aplicației. Nu trebuie să aștepți 10 secunde sau mai mult pentru a-mi da seama că am făcut o eroare de sintaxă.

Să remediem acest lucru configurând funcția lambda local și invocând-o folosind un punct final API local. AWS SAM CLI ne permite să facem exact asta. Urmați instrucțiunile de pe această pagină pentru a-l instala.

După ce ați terminat, din rădăcina proiectului, rulați următoarea comandă.

sam local start-api --template-file cloudformation.yml
1611248590 929 Cum sa construiti si sa implementati un server GraphQL in
Porniți serverul de dezvoltare locală

Punctul final local este acum disponibil la http: // localhost: 3000. Putem folosi acest punct final pentru a trimite cereri către Lambda locală.

Deschideți un alt terminal și rulați următoarea comandă pentru a trimite o cerere. Ar trebui să vedeți răspunsul din funcția noastră locală Lambda.

curl -d '{}' http://localhost:3000/graphql
1611248590 415 Cum sa construiti si sa implementati un server GraphQL in
Invocați funcția lambda locală

În cele din urmă, adăugați următoarele rânduri în scripts secțiunea din package.json.

"dev": "sam local start-api --template-file cloudformation.yml"

În continuare vom putea rula yarn dev comanda pentru a porni serverul dev.

Configurați serverul GraphQL în Lambda

Fără să mai vorbim, să sărim direct în cod și să construim serverul GraphQL.

Începeți prin instalarea dependențelor. Folosim Server Apollo pentru a construi serverul nostru GraphQL. Apollo Server este o implementare open-source a GraphQL Server.

yarn add apollo-server-lambda graphql

Înlocuiți conținutul src/index.js cu următorul cod.

const { ApolloServer, gql } = require('apollo-server-lambda');

const typeDefs = gql`
  type Query {
    user: User
  }

  type User {
    id: ID
    name: String
  }
`;

const resolvers = {
  Query: {
    user: () => ({ id: 123, name: 'John Doe' })
  }
};

const server = new ApolloServer({ typeDefs, resolvers });

exports.handler = server.createHandler();

Aici, definim o schemă care constă dintr-un tip de utilizator și o interogare de utilizator. Apoi definim un resolver pentru interogarea utilizatorului. Din motive de simplitate, resolverul returnează un utilizator codificat. În cele din urmă, creăm un handler GraphQL și îl exportăm.

Pentru a efectua interogări către serverul nostru GraphQL, avem nevoie de un client GraphQL. Insomnie este clientul meu preferat. Cu toate acestea, orice alt client GraphQL ar trebui să fie bine.

Acum, să executăm o interogare pentru a ne asigura că serverul nostru funcționează conform așteptărilor.

Creați o nouă cerere GraphQL în Insomnia.

1611248590 9 Cum sa construiti si sa implementati un server GraphQL in
Creați o nouă cerere GraphQL
1611248590 358 Cum sa construiti si sa implementati un server GraphQL in
Configurați cererea GraphQL

Adăugați următoarea interogare în corp și trimiteți interogarea la http://localhost:3000. Presupunând că serverul dvs. dev rulează în continuare, ar trebui să vedeți următorul răspuns de la serverul GraphQL.

1611248590 755 Cum sa construiti si sa implementati un server GraphQL in
Efectuați cererea GraphQL către serverul local

Acum, după ce am verificat că totul funcționează bine pe serverul local, rulați următoarea comandă pentru a implementa serverul GraphQL în AWS.

yarn deploy
1611248590 848 Cum sa construiti si sa implementati un server GraphQL in
Implementați serverul în AWS

Adresa URL API este trimisă în consolă după finalizarea implementării. Înlocuiți adresa URL din Insomnia cu cea de la API Gateway. Executați din nou interogarea pentru a o vedea rezolvată.

1611248590 77 Cum sa construiti si sa implementati un server GraphQL in
Efectuați cererea GraphQL către AWS Lambda

rezumat

Felicitări, ați implementat cu succes un server GraphQL în AWS Lambda folosind pur CloudFormation. Serverul poate primi cereri GraphQL de la client și poate returna răspunsul în consecință.

De asemenea, am creat mediul de dezvoltare pentru dezvoltarea locală fără a adăuga multe dependențe.

Dacă ți-a plăcut acest tutorial, te rog să îl împărtășești cu rețeaua ta.