0

Serverless AWS CI/CD - Projeto para o seu portifólio

#Serverless #AWS #JavaScript
Leonardo Oliveira
Leonardo Oliveira

CRUD serverless computing na AWS


Olá pessoal! Gostaria de compartilhar o meu aprendizado com vocês.

Desta vez, é sobre fazer uma API serverless na AWS com endpoints para criar, recuperar, atualizar e deletar dados do banco. Neste exemplo, vamos armazenar dados de feriados, como nome e data. Para isso, vamos criar as funções Lambda e utilizar o DynamoDB para armazenar os dados. Vamos aproveitar o GitHub para fazer o deploy e integração contínua (CI/CD). A linguagem que vamos usar é JavaScript com Node.js.

"Node.js é um software de código aberto, multiplataforma, baseado no interpretador V8 do Google e que permite a execução de códigos JavaScript fora de um navegador web" (Wikipedia).

No final deste "tutorial", você terá um projeto, novo, no seu portifólio do GitHub.


Pré-requisitos


Para este tutorial, você precisará do seguinte:

- Conta na AWS e no GitHub
- Última versão LTS do Node.js instalado
- Visual Studio Code (VSCode) ou outro editor de código da sua preferência
- Postman para testar os endpoints


Siga os Passos


Precisamos criar apenas 3 arquivos para concluir o projeto, e mais 1 para o CI/CD (Continuous Integration / Continuous Delivery ou Deployment)


Passo 1: Criando o diretório do projeto


  • Crie um diretório no local de sua preferência em seu computador.
  • Abra o diretório criado com o VSCode. Na barra de menu, clique em "Terminal", "New Terminal"



  • No Terminal, digite o comando: "npm init" e preencha as informações solicitadas. No final, você terá gerado o arquivo package.json com um conteúdo semelhante a este:
{
 "name": "api-crud-serverless",
 "version": "1.0.0",
 "description": "Meu CRUD serverless",
 "main": "index.js",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1"
 },
 "keywords": [],
 "author": "Seu nome",
 "license": "ISC"
}


Passo 2: Criando o repositório no GitHub


Crie o seu repositório no GitHub e faça a integração com o diretório do seu projeto. Utilize o terminal, no VSCode, usando os comandos a seguir:

echo "## <Nome do seu projeto>" >> README.md
git init
git add README.md
git commit -m "Projeto iniciado"
git branch -M main
git remote add origin git@github.com:<seu login no GitHub>/<nome do seu repositório>.git
git push -u origin main

Passo 3: Instalando as dependências do projeto


Ainda no terminal, instale as dependências do projeto através do comando abaixo:

yarn add @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb serverless-iam-roles-per-function

Passo 4: Não versione a pasta "node_modules"


Com a instalação das dependências no passo anterior, foi criada a pasta "node_modules". O conteúdo dela não precisa ser armazenado no GitHub. Por isso, vamos criar o arquivo ".gitignore" para que, ao fazer o "push" das alterações no projeto, o conteúdo dela não seja versionado. Você pode utilizar o Terminal do VSCode ou o Menu para criar o arquivo.

Se preferir usar o Terminal, certifique-se de que você esteja na raíz do projeto e digite o comando:

echo "/node_modules" >> .gitignore

Se preferir usar o Menu, crie um arquivo na raiz do projeto com o conteúdo: /node_modules


Passo 5: Criando o arquivo "serverless.yml"


Vamos criar o arquivo "serverless.yml" na raiz do projeto, com o conteúdo abaixo. Fique à vontade para personalizá-lo, mas tome cuidado com erros de digitação, pois eles podem impedir o funcionamento das suas funções. Caso você não queira fazer uma customização, apenas copie as linhas abaixo e cole no seu arquivo. Depois, salve e faça um novo commit no seu repositório.

#service: é o nome da pilha que será criado no Cloud Formation da AWS
#use um nome fácil de ser identificado
service: api-crud-serverless
​
provider:
 name: aws
 runtime: nodejs14.x
 stage: dev
 region: us-east-1
 lambdaHashingVersion: 20201221
 environment:
 DYNAMODB_TABLE_NAME: ${self:custom.feriadosTableName}
​
custom:
 feriadosTableName: feriados-table-${self:provider.stage}
​
plugins:
​
 - serverless-iam-roles-per-function
​
functions:
 createFeriado:
 handler: api.createFeriado
 name: create-feriado
 memorySize: 128 #em Megabytes
 timeout: 5 #tempo em segundos
 events:
  - http:
    path: feriado
    method: POST
 iamRoleStatements:
  - Effect: "Allow"
   Action:
    - "dynamodb:PutItem"
   Resource: !GetAtt FeriadosTable.Arn
 getFeriado:
 handler: api.getFeriado
 name: get-feriado
 memorySize: 128 #em Megabytes
 timeout: 5 #tempo em segundos
 events:
  - http:
    path: feriado/{id}
    method: GET
 iamRoleStatements:
  - Effect: "Allow"
   Action:
    - "dynamodb:GetItem"
   Resource: !GetAtt FeriadosTable.Arn
 updateFeriado:
 handler: api.updateFeriado
 name: update-feriado
 memorySize: 128 #em Megabytes
 timeout: 5 #tempo em segundos
 events:
  - http:
    path: feriado/{id}
    method: PUT
 iamRoleStatements:
  - Effect: "Allow"
   Action:
    - "dynamodb:UpdateItem"
   Resource: !GetAtt FeriadosTable.Arn
 deleteFeriado:
 handler: api.deleteFeriado
 name: delete-feriado
 memorySize: 128 #em Megabytes
 timeout: 5 #tempo em segundos
 events:
  - http:
    path: feriado/{id}
    method: DELETE
 iamRoleStatements:
  - Effect: "Allow"
   Action:
    - "dynamodb:DeleteItem"
   Resource: !GetAtt FeriadosTable.Arn
 getAllFeriados:
 handler: api.getAllFeriados
 name: get-all-feriados
 memorySize: 128 #em Megabytes
 timeout: 5 #tempo em segundos
 events:
  - http:
    path: feriados
    method: GET
 iamRoleStatements:
  - Effect: "Allow"
   Action:
    - "dynamodb:Scan"
   Resource: !GetAtt FeriadosTable.Arn
​
resources:
 Resources:
 FeriadosTable:
  Type: AWS::DynamoDB::Table
  Properties:
   TableName: ${self:custom.feriadosTableName}
   AttributeDefinitions:
    - AttributeName: id
     AttributeType: S
       KeySchema:
         - AttributeName: id
          KeyType: HASH
           ProvisionedThroughput:
              ReadCapacityUnits: 1
              WriteCapacityUnits: 1

Salve o arquivo e faça um novo commit no seu repositório.


Passo 6: Criando o arquivo de configuração do banco de dados


Crie o arquivo "db.js", na raiz do projeto e adicione o conteúdo abaixo:

const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
​
const client = new DynamoDBClient({});
​
module.exports = client;

Salve o arquivo e faça um novo commit no seu repositório.


Passo 7: Criando a API


Crie o arquivo "api.js", na raiz do projeto, e adicione o conteúdo abaixo:

const db = require("./db");
​
const { 
 GetItemCommand, 
 PutItemCommand, 
 DeleteItemCommand, 
 ScanCommand, 
 UpdateItemCommand } = require("@aws-sdk/client-dynamodb");
​
const { marshall, unmarshall } = require("@aws-sdk/util-dynamodb");
​
const createFeriado = async (event) => {
 const response = { statusCode: 201 };
​
 try {
   const body = JSON.parse(event.body);
   const params = {
     TableName: process.env.DYNAMODB_TABLE_NAME,
     Item: marshall( body || { }),
   };
  
   const createResult = await db.send(new PutItemCommand(params));
  
   response.body = JSON.stringify({
     message: "Feriado criado com sucesso!",
     createResult,
   });    
 } catch (error) {
   console.error(error);
   response.statusCode = 500;
   response.body = JSON.stringify({
     message: "Falha ao criar o feriado!",
     errorMsg: error.message,
     errorStack: error.stack,
   });
 }  
 return response;
}
​
const getFeriado = async (event) => {
 const response = { statusCode: 200 };
​
 try {
   const params = {
     TableName: process.env.DYNAMODB_TABLE_NAME,
     key: marshall({ id: event.pathParameters.id }),
   };
  
   const { Item } = await db.send(new GetItemCommand(params));
  
   console.log({ Item });
   response.body = JSON.stringify({
     message: "Feriado recuperado com sucesso!",
     data: (Item) ? unmarshall(Item) : { },
     rawData: Item,
   });
 } catch (error) {
   console.error(error);
   response.statusCode = 500;
   response.body = JSON.stringify({
     message: "Falha ao recuperar o feriado!",
     errorMsg: error.message,
     errorStack: error.stack,
   });
 }  
 return response;
}
​
const updateFeriado = async (event) => {
 const response = { statusCode: 200 };
​
 try {
   const body = JSON.parse(event.body);
   const objKeys = Object.keys(body);
   const params = {
     TableName: process.env.DYNAMODB_TABLE_NAME,
     Key: marshall({ id: event.pathParameters.id }),
     UpdateExpression: `SET ${objKeys.map((_, index) => `#key${index} = :value${index}`).join(", ")}`,
     ExpressionAttributeNames: objKeys.reduce((acc, key, index) => ({
       ...acc,
       [`#key${index}`]: key,
     }), { }),
     ExpressionAttributeValues: marshall(objKeys.reduce((acc, key, index) => ({
       ...acc,
       [`:value${index}`]: body[key],
     }), { })),
   }
  
   const updateResult = await db.send(new UpdateItemCommand(params));
  
   response.body = JSON.stringify({
     message: "Feriado atualizado com sucesso!",
     updateResult
   })
 } catch (error) {
   console.error(error);
   response.statusCode = 500;
   response.body = JSON.stringify({
     message: "Falha ao atualizar o feriado!",
     errorMsg: error.message,
     errorStack: error.stack,
   });
 }  
 return response;
}
​
const deleteFeriado = async (event) => {
 const response = { statusCode: 200 };
​
 try {
   const params = {
     TableName: process.env.DYNAMODB_TABLE_NAME,
     Key: marshall({ id: event.pathParameters.id }),      
   }
  
   const deleteResult = await db.send(new DeleteItemCommand(params));
  
   response.body = JSON.stringify({
     message: "Feriado excluído com sucesso!",
     deleteResult,
   });
 } catch (error) {
   console.error(error);
   response.statusCode = 500;
   response.body = JSON.stringify({
     message: "Falha ao excluir o feriado!",
     errorMsg: error.message,
     errorStack: error.stack,
   });  
 }
 return response;
}
​
const getAllFeriados = async (event) => {
 const response = { statusCode: 200 };
​
 try {
   const { Items } = await db.send(new ScanCommand({ TableName: process.env.DYNAMODB_TABLE_NAME }));
  
   response.body = JSON.stringify({
     message: "Todos os feriados foram recuperados com sucesso!",
     data: Items.map((item) => unmarshall(item)),
     Items,
   });
 } catch (error) {
   console.error(error);
   response.statusCode = 500;
   response.body = JSON.stringify({
     message: "Falha ao recuperar todos os feriados!",
     errorMsg: error.message,
     errorStack: error.stack,
   });
 }  
 return response;
}
​
module.exports = {
 getFeriado,
 createFeriado,
 updateFeriado,
 deleteFeriado,
 getAllFeriados,
};

Atenção: Qualquer erro de digitação pode fazer com que o seu projeto não funcione. Caso não queira fazer qualquer customização, copie o conteúdo e cole no seu arquivo para garantir o funcionamento.

Salve o arquivo e faça um novo commit no seu repositório.

Pronto! Com estes três arquivos (serverless.yml, db.js e api.js), o projeto está completo. Nos próximos passos vamos prepara-lo para o deploy na AWS.


Passo 8: Configurando CI/CD no GitHub


Agora, vamos configurar o projeto para que o deploy seja feito automaticamente na AWS.

Vamos precisar das credenciais de um usuário na AWS que tenha as seguintes permissões:

  • AmazonDynamoDBFullAccess
  • AmazonAPIGatewayAdministrator
  • IAMFullAccess
  • AmazonS3FullAccess
  • AWSLambda_FullAccess
  • CloudWatchLogsFullAccess
  • AWSCloudFormationFullAccess


Atenção: Use essas permissões apenas para teste. Não é uma boa prática utilizar as permissões acima, com privilégio total. O Ideal é você customizar as permissões do usuário para cada serviço listado.

Crie o usuário, caso ainda não tenha um. Vamos precisar da "Access key ID" e da "Secret access key".


Passo 9: Criando os secrets no GitHub


Agora vamos até o repositório, no site do GitHub, configurar as variáveis de ambiente para interação com a AWS.

Acesse o repositório do seu projeto e clique em "Settings"


Clique em "Secrets", no menu vertical, à esquerda da página.


Clique em "New Repository Secret", à direita da página.


No campo "Name", digite: AWS_ACCESS_KEY_ID, e em "Value, cole a "Access key ID" do usuário que você criou na AWS.


Clique no botão "Add Secret", e depois, clique novamente em "New Repository Secret". Digite AWS_SECRET_ACCESS_KEY no campo "Name", e em "Value, cole o "Secret access key".


IMPORTANTE: certifique-se de que você copiou e colou os secrets sem espaços no início e no final.


Pronto! O GitHub já está configurado para fazer o deploy na nuvem.


Passo 10: Criando o arquivo de configuração de deploy no VSCode


Vamos voltar ao VSCode para inserir as instruções de deploy do GitHub:

  • crie o diretório ".github".
  • dentro de ".github", crie o subdiretório "workflows"
  • dentro de "workflows", crie o arquivo "main.yml" com o seguinte conteúdo:
name: CRUD API Deploy DynamoDB
​
# Controla quando a ação será executada. Dispara o fluxo de trabalho ao fazer "push" ou "pull request", mas apenas na branch "main"
on:
 push:
 branches: [ main ]
​
# A execução de um fluxo é feita de um ou mais "jobs" que podem ser executados sequencialmente ou em paralelo
jobs:
 # Este fluxo contém um único job chamado "deploy"
 deploy:
 name: deploy
​
 runs-on: ubuntu-latest
  
 # Os "Steps" representam uma sequência de tarefas que serão executadas como parte do "job"
 steps:
 - uses: actions/checkout@v2
  
 - uses: actions/cache@v2
  with:
   path: '**/node_modules'
   key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
  
 - name: Use Node.js 14.x
  uses: actions/setup-node@v1
  with:
   node-version: 14.x
  
 - name: Install dependencies
  run: yarn install
  
 - name: serverless deploy
  uses: serverless/github-action@master
  with:
   args: deploy
  env:
   AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
   AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Atenção: Qualquer erro de digitação pode fazer com que o seu projeto não funcione. Caso não queira fazer qualquer customização, copie o conteúdo e cole no seu arquivo para garantir o funcionamento.

Ao fazer o próximo commit, o GitHub iniciará o deploy instantâneo na AWS. Você pode acompanhar o deploy na página do seu projeto no Github. Para isso, você vai clicar em "Actions":


Verifique se todas as etapas do deploy foram bem sucedidas.


Passo 11: Testando os "endpoints"


Depois do deploy bem sucedido, é hora de testar os endpoints. Você precisará da url base criada pela AWS. Você pode pegar a url base e os endpoints no resumo do deploy do GitHub, na etapa "serverless deploy":

Você pode obter os endpoints no console da AWS, também, através do serviço "Api Gateway".

Agora é só abrir o Postman e iniciar os testes.


Conclusão

Você criou um CRUD serverless utilizando apenas três arquivos:

  • serverless.yml
  • db.js
  • api.js

E com um arquivo, main.yml, você configurou o deploy automático através do GitHub.


Este tutorial traz um exemplo bem simples de CRUD serverless com JavaScript, e claro, não tem o objetivo de esgotar o assunto. Aqui você está vendo apenas a ponta do iceberg. Recomendo o curso "Criando uma aplicação Serverless na AWS" ministrado por Cassiano Peres, expert da DIO.


Para o caso de não ter conseguido concluir algum dos passos, me coloco a disposição para ajudar. Se preciso for, escrevo um artigo complementar para tirar as dúvidas.

Espero ter conseguido ajudá-lo no início da sua caminhada Serverless.

Se esse conteúdo foi útil para você, deixe um comentário e clique na "setinha" para sinalizar que este tutorial pode ajudar outros colegas também.

Obrigado pela sua leitura!

0
7

Comentários (0)

Apaixonado por tecnologia e movido à desafios!

Brasil