Initiation aux services web amazon

Introduction :

Si vous ne vivez pas dans une grotte, Amazon vous évoque sûrement quelque chose, car c’est l’une des 4 entreprises les plus puissantes du monde de l’internet, connu sous l’acronyme GAFA (Google, Apple, Facebook, Amazon).

Amazon propose aussi des solutions cloud AWS (Amazon web services), comme azure de Microsoft ou Google cloud pour Google.

Avant de parler des différents services web d’Amazon et de comment les mettre en place, nous allons d’abord parler de 2 concepts qui ont été créés pour répondre à une problématique :

Comment développer, tester et déployer une application sans avoir à configurer ou gérer des serveurs ?

Cela paraît utopique de se dire qu’on peut juste se concentrer sur la partie développement de notre solution, et de ne plus entendre parler de configuration de serveur, maintenance, etc. Mais c’est bien sur réel et cela grâce à 2 concepts clés.

Architectures serverless :

On peut penser que serverless veut dire “sans serveur” mais ce n’est pas le cas, c’est juste que les serveurs ne sont plus gérés par le client, mais par le fournisseur.

Concrètement, c’est le fait d’allouer des ressources dynamiquement, vous pourrez donc augmenter ou réduire vos ressources à votre guise, il suffit juste de le demander.

On est plus dans la logique suivante :

Je loue un serveur pour une période x.

Mais plutôt :

je ne paye que pour les ressources que j’utilises.

Vous pourrez vous dire qu’il n’ y a pas plus normal que de payer “que” pour les ressources qu’on a utilisé, mais c’est rarement le cas, si vous louez par exemple un serveur pour une durée d’un an, et que durant les soirs vous n’avez presque pas de trafic, vous aurez quand même payé pour cette période.

Mise à part le côté financier, qui n’est bien entendu pas négligeable, vous aurez aussi tout a gagné en souplesse, car ce type d’architecture vous permet de gagner en scalabilité, et par conséquent, votre application ne sera jamais en manque de ressources quand vous traverserez une période avec une hausse en demande.

Architecture As code :

C’est beau tout ça mais comment le développeur va-t-il spécifier la ressource dont il a besoin ? la réponse est toute simple ça sera avec du code. Il vous suffit de décrire vos besoins dans un fichier. Ce dernier une fois envoyé dans le cloud sera transformé en votre stack.

L’avantage est clair, le développeur pourra se concentrer sur ce qu’il connait le mieux c’est à dire “coder”.

Les services web de base d’Amazon

Bucket S3 :

C’est le premier service web d’Amazon, qui a été lancé en 2006 et comme son nom l’indique, vous pouvez l’imaginer comme étant un seau dans lequel vous aller mettre à peu près tout.

Il est caractérisé par sa haute disponibilité et scalabilité, mais aussi par sa sécurité qui fait de lui une solution fiable pour le stockage de fichiers.

Exemple :

Upload d’un fichier sur s3 en js :

const AWS = require(« aws-sdk »);

var s3 = new AWS.S3();
// fileToUpload : your file in base64
// bucket : your bucket name in S3
// pathInBucket : the path where you would stock your file in the bucket
exports.uploadFileToS3 = (fileToUpload, bucket, pathInBucket) => {
return new Promise((resolve, reject) => {
let decodedImage = Buffer.from(fileToUpload, « base64 »);

var params = {
Body: decodedImage,
Bucket: bucket,
Key: pathInBucket
};

s3.upload(params, function(err, data) {
if (err) {
console.log(`Err uplodaeFile ${err}`);
reject({
code: 500,
message: err
});
} else {
// return the location of the file in s3
resolve(data.Location);
}
});
});
};

Récupérer un fichier sur s3

const AWS = require(« aws-sdk »);
var s3 = new AWS.S3();

exports.getFileFromS3 = (bucket, s3Path) => {
var params = { Bucket: bucket, Key: s3Path };
return new Promise((resolve, reject) => {
s3.getObject(params, function(err, data) {
if (!err) {
let originalFile = new Buffer(data.Body.data, « base64 »);
resolve(originalFile);
} else {
reject(err);
}
});
});
};

API Gateway :

l’API gateway est un service web d’Amazon qui vas nous permettre de créer, publier et sécuriser une API.

Elle se positionne en frontal et reçoit les requêtes HTTP, puis traite chacune d’elles selon les règles prédéfinies, comme le déclenchement d’une lambda par exemple.

API gateway va nous permettre d’avoir une architecture en microservices, et cela en cachant la structure interne et en exposant un seul point d’entrée pour notre api.

Les lambdas :

Ne vous inquiétez pas, on ne va pas parler de mathématique, mais plutôt d’un service web Amazon trés répandu, il va nous permettre la mise en place d’une architecture dite serverless”.

Concrètement une lambda est une fonction qui va répondre à un besoin bien précis, c’est tout à fait normal car une fonction est censée faire qu’une seul chose!

Une classe ou une méthode doit faire une seule chose, elle doit la faire bien et ne faire qu’elle

Pour déclencher une lambda nous avons une panoplie de choix, les plus utilisés sont les suivants :

  • API gateway
  • dynamodb
  • bucketsS3
  • SNS,SQS, etc.

La structure d’une lambda :

exports.lambdaHandler = (event, context, callback) => {       
                
  const response = {       
                
    statusCode: 200,       
                
    body: JSON.stringify(`Azul !`)       
                
  };       
                
  callback(null, response);       
                
};

La structure d’une lambda :
On doit déclarer une fonction qui va recevoir 3 objets : event, context et callback, et cette méthode sera le point d’entrée de la lambda.

l’event va nous permettre de récupérer plusieurs informations importantes :

  • Les paramètres dans le path
  • Le header, body de la requête
  • La methode http utilisée

Callback : on l’utilise pour renvoyer une réponse.

Nous avons vu API geteway qui nous permet de déclencher une lambda qui va exécuter un certain nombre de traitements, on va passer maintenant au moyen de stocker nos infos.

Dynamodb :

C’est une base de données NoSql (not only sql) orientée documents, et contrairement au model relationnel on a pas de structure rigide à
suivre, à part un Id pour identifier nos document.

En mélangeant les lambdas, API Gateway et dynamodb on peut arriver à quelque chose de très intéressant :

On va implémenter juste en-dessous un exemple complet, qui respecte cette archi, mais avant cela on doit parler d’un autre service aussi important.

CloudFormation :

C’est grâce à ce service que nous allons décrire les ressources qui seront crées par Amazon, cela se fait de manière très simple à travers un fichier YAML ou JSON.

Les avantages de coudFormation :

  • Tout est code, rien ne se fait manuellement.
  • La
    création de notre stack se fait de manière rapide et automatique, il
    suffit d’envoyer notre fichier sur S3 pour que la stack soit crée.

Par contre une question se pose :

Aprés avoir crée une application, faut-il relancer le processus à chaque fois que je désire tester une nouvelle fonctionnalité ?

Cette question est pertinente car de un on perd beaucoup de temps à faire tout cela, et de deux les ressources sur AWS sont payantes à l’utilisation, et vue que le temps c’est de l’argent..


Serverless applications Model (SAM)

C’est un framework qui vient répondre à la problématique précédente, à savoir comment développer et tester des applications severless localement.

SAM est une extension du service cloudFormation qui permet la description des ressources mais pas leur exécution localement. Pour rajouter cette fonctionnalité SAM s’appuie essentiellement sur docker, en créant un container qui simule l’environnement AWS.

On va passer aux commandes de base pour utiliser SAM. commençons par son installation :

pip install --upgrade pip
pip install --upgrade setuptools
pip install --upgrade aws-sam-cli

Pour packager notre code, et l’envoyer dans S3 :

sam package --template-file template.yaml --output-template-file ./serverless-output.yaml --s3-bucket bucketName

Explication :

  • template.yaml : représente le template de notre projet
  • Apres — s3 : on specifie le nom de notre bucket S3
  • serverless-output.yaml : c’est le fichier qui sera crée après le package, il sera utilisé ultérieurement pour le deployement

Déployer :

aws cloudformation deploy --template-file serverless-output.yaml --stack-name stack-article --capabilities CAPABILITY_IAM

Explication :

  • Comme indiqué ci-dessus, on spécifie le fichier serverless-output.yaml renvoyé par le package.
  • après — stack-name on indique le nm de notre stack, ici c’est stack-article

Pour lancer notre api localement :

sam local start-api --skip-pull-image

On peut aussi verifier la validité de notre template avec :

sam validate

on doit se positionner dans le dossier où se trouve notre fichier.

Cas Pratique :

On va créer une lambda qui sera déclenchée par l’appel à la methode GET sur un endpoint.

On aura l’organisation suivante :

Notre fichier template :

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  eaprofile
  Sample SAM Template
Globals:
  Function:
    Timeout: 30

Resources:
  ApiGatewayExample:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      DefinitionBody:
        swagger: "2.0"
        info:
          title:
            Ref: AWS::StackName
          description: My API getway exapmle
          version: 1.0.0
        paths:
          /user/{user}:
            get:
              x-amazon-apigateway-integration:
                httpMethod: post
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaExampleGet.Arn}/invocations
          /user:
            post:
              x-amazon-apigateway-integration:
                httpMethod: post
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaExamplePost.Arn}/invocations

  LambdaExampleGet:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: nodejs8.10
      Handler: index.lambdaHandler
      CodeUri: lambda-exemple-get
      FunctionName: LambdaExampleGet
      Environment:
        Variables:
          DYNAMODB_TABLE: !Ref ExampleTable
      Policies:
        - AWSLambdaBasicExecutionRole
        - AmazonDynamoDBReadOnlyAccess 

  LambdaExamplePost:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: nodejs8.10
      Handler: index.lambdaHandler
      CodeUri: lambda-exemple-post/
      FunctionName: LambdaExamplePost
      Environment:
        Variables:
          DYNAMODB_TABLE: !Ref ExampleTable
      Policies:
        - AWSLambdaBasicExecutionRole
        - AmazonDynamoDBFullAccess

  ExampleTable:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1

  ConfigLambdaPermissionForApiGetwayToInvokeLambdaExampleGet:
    Type: "AWS::Lambda::Permission"
    DependsOn:
      - ApiGatewayExample
      - LambdaExampleGet
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref LambdaExampleGet
      Principal: apigateway.amazonaws.com

  ConfigLambdaPermissionForApiGetwayToInvokeLambdaExamplePost:
    Type: "AWS::Lambda::Permission"
    DependsOn:
      - ApiGatewayExample
      - LambdaExamplePost
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref LambdaExamplePost
      Principal: apigateway.amazonaws.com

Nous avons déclaré 4 type de ressources :

  • Lambda
  • API gateway
  • base dynamodb
  • Permissions

Les permissions :

Amazon permet une gestion de sécurité très flexible, on est sur du “security as code”, au
départ nos ressources ne peuvent presque rien faire, puis on doit leur
affecter des accès suivant leur champs d’actions, le but est de leur
donner juste ce qu’il faut pour qu’elle effectuent leurs traitements.

Dans
notre cas on a donné le droit d’accésen lecture sur dynamodb pour notre
lambda qui fait un GET lambda (49–51) et un accés total celle qui
effectue un POST (63–65).

Amazon
nous permet d’aller très loin pour tout ce qui concerne la gestion de
la sécurité, dans notre exemple on peut même spécifier à notre lambda
quelle table manipuler par exemple.

Pour ceux qui veulent approfondir : https://aws.amazon.com/fr/iam/

API gateway : on
l’a défini entre la ligne 12 et 37 et cela en déclarant une ressource
avec un type AWS::Serverless::Api. Puis on a précisé quelles sont les
endpoints qui seront capturés par cette gateway et cela en utilisant le
mot clé paths(23), nous en avons déclaré 2, et chaqu’un déclanche une
lambda :

  1. /user/{user} : il est associé à la lambda “LambdaExamplePost”, vous pouvez le voir dans la ligne 31
  2. /user : associer à la lambda “LambdaExampleGet”, (ligne 37).

Avant de commancer la partie qui traite des lambdas on doit faire un pull de 2 image docker :

La première concerne notre environnement de dev nodejs :

docker pull lambci/lambda:nodejs8.10

La deuxième concerne notre base dynamodb :

docker pull amazon/dynamodb-local

On doit biensur la lancer avec :

docker run -p 8000:8000 amazon/dynamodb-local

Lambda :

Passons maintenant à la partie la moins barbante, c’est a dire le code !
On va commencer par la lambda qui nous permet de créer un user :

Nous avons besoin d’un client dynamodb afin de nous y connecter :

const AWS = require("aws-sdk");
const awsRegion = process.env.AWS_REGION || "us-east-2";
var makeDynamodbClient;
var client;
var options = {
  region: awsRegion
};
if (process.env.TEST) {
  options.endpoint = "https://localhost:8000";
  client = new AWS.DynamoDB(options);
}

if (process.env.AWS_SAM_LOCAL) {
  options.endpoint = "https://dynamodb:8000";
}

makeDynamodbClient = () => {
  return new AWS.DynamoDB.DocumentClient(options);
};

module.exports = { makeDynamodbClient, client };

j’ai crée un simple fichier qui nous permet d’ajouter et de récupérer un user sur notre base :

const TABLENAME = process.env.DYNAMODB_TABLE || "ExampleTable";
const { makeClient } = require("./clientDynamoDB");
const clientDynamoDB = makeClient();

const getUser = id => {
  var params_for_search = {
    TableName: TABLENAME,
    KeyConditionExpression: "#id = :idValue",
    ExpressionAttributeNames: {
      "#id": "id"
    },
    ExpressionAttributeValues: {
      ":idValue": id
    }
  };

  return new Promise((resolve, reject) => {
    clientDynamoDB.query(params_for_search, function(err, data) {
      if (err) {
        reject(err);
      } else {
        if (data.Items.length == 0) {
          resolve(data.Items);
        } else {
          resolve(data.Items);
        }
      }
    });
  });
};

const addUser = user => {
  var profileToAdd = {
    TableName: TABLENAME,
    Item: user
  };

  return new Promise((resolve, reject) => {
    clientDynamoDB.put(profileToAdd, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(true);
      }
    });
  });
};

module.exports = { getUser, addUser };

Notre lambda qui permet de créer un user :

const crud = require("./crud");

exports.lambdaHandler = (event, context, callback) => {
    console.log("****** loading lambdaPost");
    console.log("****** event.body : "+JSON.stringify(event.body));

  const user = JSON.parse(event.body);
  console.log(`the user to add : ${JSON.stringify(user)}`);

  crud
    .addUser(user)
    .then(response => {
      if (response) {
        sendResponse(
          200,
          { message: "user added successfuly", user: user },
          callback
        );
      }
    })
    .catch(err => {
      sendResponse(500, `err ${err} failed to add ${user}`, callback);
    });
};

function sendResponse(statusCode, message, callback) {
  const response = {
    statusCode: statusCode,
    body: JSON.stringify(message)
  };
  callback(null, response);
}

Récupérer un user :

const crud = require("./crud");
exports.lambdaHandler = (event, context, callback) => {
  const id = event.pathParameters.user;
  crud
    .getUser(id)
    .then(response => {
      console.log("response : "+response);
      sendResponse(200, response, callback);
    })
    .catch(err => {
      console.error(`get failed ${err}`);
    });
};

function sendResponse(statusCode, message, callback) {
  const response = {
    statusCode: statusCode,
    body: JSON.stringify(message)
  };
  callback(null, response);
}

Comme
indiqué ci-dessus l’objet event nous permet de récupérer plusieurs
informations, dans notre cas on a récupéré l’ id utilisé dans le path
lors de son invocation.

Pour renvoyer une réponse on utilise la méthode callback, dans la quelle on lui passe un JSON qui contient un status, un body et optionnellement un header.

Pour tester nos lambda on doit d’abord lancer notre API :

Nous allons créer un simple test pour l’ajout et la récupération d’un user :

Test de création d’un user :

var request = require("supertest");
var expect = require("chai").expect;
var host = process.env.HOST || "https://localhost:3000";

describe("Test create user", () => {
  const userToAdd = {
    id: "1",
    name: "Amine",
    pseudo: "Volk"
  };
  it("Should create user", done => {
    request(`${host}`)
      .post("/user")
      .send(userToAdd)
      .expect(200)
      .expect(res => {
        expect(res.body).deep.eq({
          message: "user added successfuly",
          user: userToAdd
        });
      })
      .end(done);
  });
});

Après exécution on aura :

Nous allons récupérer le user que nous avons crée précédemment :

var request = require("supertest");
var expect = require("chai").expect;
var host = process.env.HOST || "https://localhost:3000";

describe("Test retreave user", () => {
  const userToFetch = {
    id: "1",
    name: "Amine",
    pseudo: "Volk"
  };
  it("Should retreave user", done => {
    request(`${host}`)
      .get("/user/1")
      .expect(200)
      .expect(res => {
        expect(res.body[0]).deep.eq(userToFetch);
      })
      .end(done);
  });
});

Dans
la ligne 16 j’ai utilisé body[0] car dynamodb renvoi les résultats dans
un tableau, et comme on fait un get sur un seul élément on prend le
premier.

On aura le resultat suivant :

Voilà,
on arrive à la fin de cette présentation, dans laquelle j’ai essayé de
vous donner une vue globale sur quelques web service d’Amazon, j’éspere
qu’elle vous a plu, je vous dis chaw 😉

ci-dessous le lien vers le repo Github :

https://github.com/AmineVolk/Initiation-aux-services-web-Amazon

full stack developer LinkedIn | Github