TP5 - Faire une API REST 100% serverless
Le but de ce TP est de mettre en place une API REST pour gĂ©rer des tĂąches dâune to-do liste en utilisant uniquement des services serverless proposĂ©s par AWS. Cela ne va pas ĂȘtre plus compliquĂ© pour vous (voire mĂȘme cela risque dâĂȘtre plus facile). Comme votre application sera 100% serverless, vous nâallez plus utiliser Terraform, mais AWS SAM (Serverless Application Model). Comme Terraform, SAM est une solution IaC, et va permettre de dĂ©finir lâarchitecture de votre application comme du code. Cette fois-ci, ce ne sera pas du code Python, mais un simple YAML (YAML Ainât Markup Language), un format clĂ©-valeur proche du JSON, mais oĂč lâindentation est importante.
Le but du TP est de mettre en place une API REST pour gĂ©rer des tĂąches dâune to-do liste. Pendant la sĂ©ance, vous ne ferez que la partie crĂ©ation dâune tĂąche. Ă vous de terminer si vous le souhaitez.
Un hello world avec SAM
Pour commencer le TP, placez-vous dans le rĂ©pertoire oĂč vous souhaitez travailler, puis exĂ©cutez la commande sam init
. SĂ©lectionnez lâoption AWS Quick Start Templates
, puis choisissez le premier template disponible. Lorsque vous ĂȘtes invitĂ© Ă choisir un runtime, sĂ©lectionnez Python. Refusez lâutilisation de X-ray si demandĂ©, puis donnez un nom Ă votre projet. Une fois le projet tĂ©lĂ©chargĂ©, un dossier sera apparu avec lâarborescence suivante (certaines parties sont omises) :
đŠtest
⣠đevents
â â đevent.json
⣠đhello_world
â ⣠đapp.py
â ⣠đrequirements.txt
â â đ__init__.py
⣠đtemplate.yaml
â đ__init__.py
Le fichier template.yaml
contient lâinfrastructure de votre code, le dossier hello_world
va contenir le code de votre lambda (ce dossier peut ĂȘtre renommĂ©) et event.json
va contenir un évÚnement pour tester votre application.
Le fichier qui nous intéresse particuliÚrement en ce moment est le fichier template.yaml
. Voici son contenu (certaines parties sont omises):
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Outputs:
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
Vous constatez quâil y a 3 parties dans votre fichier et Ă©videmment chacune a son rĂŽle:
- Globals permet de définir des configurations de maniÚre globale. Ici, le timeout des fonctions lambda du template est de 3 secondes.
-
Ressources est la partie oĂč lâinfrastructure est dĂ©finie. Actuellement, ce template dĂ©finit :
- Une fonction lambda, câest ce que veut dire la ligne 10. Son code est dans le dossier hello-world, le handler est la fonction lambda_handler du fichier app.py, et la version de python de la fonction est 3.9.
- Une API Gateway. Câest moins clair mais câest ce que veut dire la ligne 19. Comme la lambda est dĂ©clenchĂ©e par une API Gateway, elle va ĂȘtre créée mĂȘme si elle nâest pas formellement dĂ©finie. Le code de la lambda sera dĂ©clenchĂ© par un appel GET sur le chemin /hello.
- Outputs permet de rĂ©cupĂ©rer facilement des valeurs qui ne sont connues quâune fois le template dĂ©ployĂ©. Ici lâURL pour dĂ©clencher la fonction, lâidentifiant AWS de la lambda et de son rĂŽle.
Comme vous utilisez des labs AWS Academy, ce code ne fonctionne pas tel quel. En effet, ce code crĂ©e automatiquement un rĂŽle pour la lambda, sauf que câest impossible sur un lab. Ajoutez aprĂšs Handler
la ligne Role: !Sub arn:aws:iam::${AWS::AccountId}:role/LabRole
pour mettre le bon rÎle à votre lambda. Supprimez également les 3 derniÚres lignes du template.
Maintenant que cela est fait, il est temps de déployer le template ! Exécutez sam deploy --guided
et répondez aux questions dans le terminal.
- Stack Name : répondez ce que vous souhaitez
- AWS Region : us-east-1
- Confirm changes before deploy : y
- Allow SAM CLI IAM role creation : n
- Capabilities [[âCAPABILITY_IAMâ]]: validez avec entrĂ©e
- Disable rollback [y/N]: répondez ce que vous souhaitez
- HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
- Validez les prompts suivants avec entrée
AprÚs quelques instants, la liste des ressources à déployer va apparaßtre dans votre terminal
Operation | LogicalResourceId | ResourceType | Replacement |
---|---|---|---|
| HelloWorldFunctionHelloWorldPermissionProd | AWS::Lambda::Permission | N/A |
| HelloWorldFunction | AWS::Lambda::Function | N/A |
| ServerlessRestApiDeployment47fc2d5f9d | AWS::ApiGateway::Deployment | N/A |
| ServerlessRestApiProdStage | AWS::ApiGateway::Stage | N/A |
| ServerlessRestApi | (AWS::ApiGateway::RestApi) | N/A |
Comme câest un premier dĂ©ploiement, vous avez seulement des opĂ©rations Add
. Votre template tout simple est en train de créer :
- Une fonction lambda (AWS::Lambda::Function) et une politique de sécurité associée (AWS::Lambda::Permission)
- Une API Gateway de type Rest (AWS::Lambda::Function), avec un stage (AWS::ApiGateway::Stage) le tout accessible sur internet (AWS::ApiGateway::Deployment)
Validez les changements et aprĂšs quelques instants, un bloc de sortie va apparaĂźtre dans le terminal. Copiez/collez lâURL de lâAPI et vĂ©rifiez quâelle fonctionne bien.
đ FĂ©licitations, vous venez de crĂ©er votre premier API REST 100% serverless sur AWS ! Avec un peu de travail, vous pourriez redĂ©ployer tout votre projet informatique de 2A.
Ajouter une base de données DynamoDB
DynamoDB est une base de donnĂ©es serverless, il est donc possible de lâajouter dans le template. Dans la partie Resources du template ajoutez :
DynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: user
AttributeType: S
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: user
KeyType: HASH
- AttributeName: id
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
Et dĂ©ployez Ă nouveau votre template. Vous allez voir que seule la nouvelle ressource va ĂȘtre dĂ©ployĂ©e.
Maintenant, il faut arriver Ă faire le lien entre la base DynamoDB et la fonction lambda. Pour rendre le code le plus souple possible, le nom de la table ne va pas ĂȘtre hardcodĂ© dans la fonction, mais mis dans ses variables dâenvironnement. Dans les propriĂ©tĂ©s de votre fonction, ajoutez les lignes :
Environment:
Variables:
DYNAMO_TABLE: !Ref DynamoDBTable
Ces lignes vont faire que lors du dĂ©ploiement, une variable dâenvironnement DYNAMO_TABLE
va ĂȘtre créée, et quâelle va avoir pour valeur le nom de la table. Comme le nom est gĂ©nĂ©rĂ© dynamiquement, on va passer une rĂ©fĂ©rence Ă la ressource DynamoDB (!Ref DynamoDBTable
) et SAM va dynamiquement injecter le nom.
Déployez le template, allez sur la page du service AWS Lambda, puis sur la lambda créée par le template, puis configuration
et Variables d'environnement
et vérifiez que tout est bon.
Le code de la lambda
Pour le moment, vous avez seulement fait la partie infrastructure de lâapplication, il est temps de regarder un peu le code. Quand AWS SAM a gĂ©nĂ©rĂ© le template de base, il a créé un dossier hello_world
. Ce dossier contient le code de la fonction lambda. Quand AWS SAM va déployer le code de la lambda, il cherche le contenu de la variable codeUri
et dĂ©ploie lâintĂ©gralitĂ© du dossier. Si le dossier contient un fichier requirements.txt
, SAM va installer les dépendances pour la lambda. Si vous regardez le fichier app.py
, il contient la méthode lambda_handler()
, qui pour le moment ne renvoie quâune rĂ©ponse prĂ©dĂ©finie.
-
Dans le
template.yml
, changez le nom de la ressourceHelloWorldFunction
enPostTaskFunction
(attention, vous devez mettre à jour la derniÚre ligne du template également). -
Changez dans les Ă©vĂ©nements de la fonction le nom de lâAPI (HelloWorld -> Task), et sa propriĂ©tĂ© (/hello -> task, get -> post).
-
Créez un dossier
PostTask
et faites que votre lambda pointe vers ce dossier. -
Implémentez la fonction
lambda_handler()
qui va poster un commentaire dans la base DynamoDB. Une requĂȘte HTTP POST permet de crĂ©er une ressource, les Ă©lĂ©ments de la ressource seront dans lebody
de la requĂȘte. Voici un exemple de body :
{
"user" : "Rémi",
"taskTitle" : "Corriger le TP noté",
"taskBody" : "Tout est dans le titre",
"taskDueDate" : "18/05/2023"
}
Pour vous aider :
-
Pour rĂ©cupĂ©rer le contenu du corps de la requĂȘte, utilisez
body = json.loads(event['body'])
(pensez Ă importer le package json, ce package est dans les packages de base de Python, pas besoin de lâajouter dans le requirements.txt). La variable body est un dictionnaire Python. -
Chaque tùche aura un identifiant unique généré avec la fonction
uuid.uuid4()
(il faut importer uuid, câest un package dans la distribution Python de base). -
Voici un exemple de code pour Ă©crire un objet dans DynamoDB. Ce code nâest pas Ă reprendre tel quel ! Vous trouverez le nom de la table dans les variables dâenvironnement de votre lambda.
import boto3
# Get the service resource.
dynamodb = boto3.resource('dynamodb')
# Get the table.
table = dynamodb.Table('users')
# Put item to the table.
table.put_item(
Item={
'username': 'janedoe',
'first_name': 'Jane',
'last_name': 'Doe',
'age': 25,
'account_type': 'standard_user',
}
)
đ§ââïžBien que nâĂ©tant pas par dĂ©faut dans python, boto3 est par dĂ©faut dans les lambdas
-
Une fois lâajout fait, faites retourner Ă votre lambda une dictionnaire sâinspirant de celui-ci :
{ "statusCode": 200, "body": votre objet task }