AWS Serverless: Padrões Serverless Implementados (Parte 1 de 2)
Tags: ptbr, serverless, aws, webdev
Published: 2020-02-01 22:46:05

---
title: AWS Serverless: Padrões Serverless Implementados (Parte 1 de 2)
published: true
description: Implementando padrão Serverless baseados no artigo de Jeremy Daly
tags: ptbr, serverless, aws, webdev
cover_image: https://images.unsplash.com/photo-1550645612-83f5d594b671?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1500&q=80
---

Eu acho que a melhor maneira de aprender algo é praticá-lo e tentar explicá-lo, então é isso que vou fazer na próxima série de artigos. Essas postagens serão baseadas no incrível artigo de [Jeremy Daly](https://twitter.com/jeremy_daly) sobre [padrões serverless](https://dev.to/oieduardorabelo/aws-serverless-padroes-de-micro-servicos-serverless-1gb5). Não vou copiar as palavras de Jeremy aqui; portanto, para cada padrão, vá ao artigo acima para ler sua descrição. Vou fornecer uma implementação técnica e mencionarei mais recursos que achei interessantes. Vamos começar!

# Configuração Comum

Todos os projetos terão uma configuração comum, o que é bastante simples. Primeiro, inicialize um projeto Node.js:

```bash
yarn init
```

Em seguida, instale o Serverless Framework como uma dependência de desenvolvedor:

```bash
yarn add serverless --dev
```

E, finalmente, crie um script para implantar o projeto:

```json
"scripts": {
	"deploy": "serverless deploy --aws-profile serverless-local"
}
```

(Supondo que você tenha um perfil local chamado `serverless-local`)

# Padrão 01 - O Serviço Web Simples

[Você pode ler a explicação aqui](https://dev.to/oieduardorabelo/aws-serverless-padroes-de-micro-servicos-serverless-1gb5#01-o-servi%C3%A7o-web-simples).

Para implementar esse padrão, precisamos criar um serviço com uma tabela do DynamoDB e, pelo menos, uma função que obtenha ou defina dados dela. Então, a aparência do `serverless.yml` será assim:

```yml
service: SimpleWebService

plugins:
  - serverless-iam-roles-per-function

provider:
  name: aws
  runtime: nodejs10.x
  region: ${opt:region, self:custom.defaultRegion}

custom:
  defaultRegion: eu-west-1
  tableName: ${self:provider.region}-SimpleWebServiceTable

functions:
  GetItem:
    handler: src/functions/getItem.handler
    events:
      - http:
          method: get
          path: item/{itemId}
    environment:
      tableName: ${self:custom.tableName}
    iamRoleStatements:
      - Effect: Allow
        Action: dynamodb:getItem
        Resource: !GetAtt SimpleWebServiceTable.Arn
  PutItem:
    handler: src/functions/putItem.handler
    events:
      - http:
          method: post
          path: item
    environment:
      tableName: ${self:custom.tableName}
    iamRoleStatements:
      - Effect: Allow
        Action: dynamodb:putItem
        Resource: !GetAtt SimpleWebServiceTable.Arn

resources:
  Resources:
    SimpleWebServiceTable:
      Type: AWS::DynamoDB::Table
      Properties:
        KeySchema:
          - AttributeName: id
            KeyType: 'HASH'
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: 'N'
        BillingMode: PAY_PER_REQUEST
        TableName: ${self:custom.tableName}
```

Precisamos instalar o plugin `serverless-iam-roles-per-function`. Estamos criando uma tabela DyanamoDB e passando o nome para as funções via variável de ambiente. Em cada função, estamos apenas dando as permissões necessárias.

Vamos dar uma olhada na implementação da função `PutItem`:

```tsx
const AWS = require("aws-sdk");
const dynamodb = new AWS.DynamoDB.DocumentClient();

const tableName = process.env.tableName;

module.exports.handler = async (event) => {
  const body = JSON.parse(event.body);
  const id = parseInt(body.id);
  const name = body.name;
  const params = {
    TableName: tableName,
    Item: {
      'id' : id,
      'name' : name
    }
  };
  const resp = await dynamodb.put(params).promise();
  const res = {
    statusCode: 200,
    body: JSON.stringify(resp)
  };
  return res;
};
```

E, finalmente, vamos dar uma olhada na implementação da função `GetItem`:

```tsx
const AWS = require("aws-sdk");
const dynamodb = new AWS.DynamoDB.DocumentClient();

const tableName = process.env.tableName;

module.exports.handler = async (event) => {
  const id = event.pathParameters.itemId;
  const req = {
    TableName: tableName,
    Key: {
        'id': parseInt(id)
      }
  };
  const resp = await dynamodb.get(req).promise();
  const res = {
    statusCode: 200,
    body: JSON.stringify(resp.Item)
  };
  return res;
};
```

[Você pode conferir a solução completa aqui](https://github.com/vgaltes/ServerlessPatterns/tree/master/SimpleWebService).

# Padrão 02 - Webhook Escalável

[Você pode ler a explicação aqui](https://dev.to/oieduardorabelo/aws-serverless-padroes-de-micro-servicos-serverless-1gb5#02-webhook-escal%C3%A1vel).

Nesse padrão, vamos introduzir uma fila SQS entre dois serviços e essa fila terá uma fila de mensagens não entregues caso encontremos algum erro. Então, vamos começar com o arquivo `serverless.yml`:

```yml
service: ScalableWebhook

plugins:
  - serverless-iam-roles-per-function
  
provider:
  name: aws
  runtime: nodejs10.x
  region: ${opt:region, self:custom.defaultRegion}

custom:
  defaultRegion: eu-west-1

functions:
  Flooder:
    handler: src/functions/flooder.handler
    events:
      - http:
          method: post
          path: flooder
    environment:
      queueUrl: !Ref WorkerQueue
    iamRoleStatements:
      - Effect: Allow
        Action: SQS:SendMessage
        Resource: !GetAtt WorkerQueue.Arn
  Worker:
    handler: src/functions/worker.handler
    memorySize: 256
    reservedConcurrency: 5
    events:
      - sqs:
          batchSize: 10
          arn: !GetAtt WorkerQueue.Arn
  DLQReader:
    handler: src/function/dlqReader.handler
    events:
      - sqs:
          batchSize: 10
          arn: !GetAtt ReceiverDeadLetterQueue.Arn

resources:
  Resources:
    WorkerQueue:
      Type: "AWS::SQS::Queue"
      Properties:
        QueueName: "WorkerQueue"
        VisibilityTimeout: 30 # 30 segundos
        MessageRetentionPeriod: 60 # 60 segundos
        RedrivePolicy:
          deadLetterTargetArn: !GetAtt ReceiverDeadLetterQueue.Arn
          maxReceiveCount: 3
    ReceiverDeadLetterQueue:
      Type: "AWS::SQS::Queue"
      Properties:
        QueueName: "WorkerDLQ"
        MessageRetentionPeriod: 1209600 # 14 dias em segundos
```

Este arquivo é um pouco mais complicado que o anterior. As partes interessantes estão na definição da fila, onde estamos definindo as propriedades dela. Vamos explicar agora quais são esses parâmetros, mas eu recomendo fortemente que [você leia este artigo de Jeremy sobre filas SQS](https://www.jeremydaly.com/serverless-consumers-with-lambda-and-sqs-triggers/) e leia todos os comentários também, pois tem informações interessantes por lá. Você também pode dar uma olhada [neste artigo](https://cloudly.tech/blog/aws-lambda-sqs-trigger-error-handling/), onde o autor explica como o tratamento de erros no SQS funciona. E, finalmente, [você pode ir para a documentação oficial](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-dead-letter-queue.html).

O [tempo limite de visibilidade](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html) é o tempo em que a mensagem permanece na fila sem que outros consumidores possam receber e processar a mensagem, aguardando a confirmação (ou o erro) do consumidor original. Se, após esse período, a fila não receber a solicitação de exclusão do consumidor original, a fila disponibilizará a mensagem para o próximo consumidor.

O período de retenção de mensagens é o momento em que uma mensagem é colocada em uma fila antes de ser excluída pelo sistema, se ninguém a consumir. O máximo é 14 dias.

A [política de redrive](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-dead-letter-queue.html) é onde especificamos o que acontece quando uma mensagem não pode ser processada pelo consumidor. No nosso caso, estamos dizendo que gostaríamos de especificar um DLQ (Dead Letter Queue) e que uma mensagem será enviada para o DLQ após três tentativas falhas de processamento.

O código de enviar mensagens é o mesmo (ou muito semelhante) que Jeremy tem em seu post sobre SQS:

```tsx
const AWS = require('aws-sdk');
const SQS = new AWS.SQS();

module.exports.handler = async (event, context) => {
    const body = JSON.parse(event.body);
    const times = parseInt(body.times);
    const queue = process.env.queueUrl;
    console.log(`Queue is: ${queue}`);
    for (let i=0; i<times; i++) {
        await SQS.sendMessageBatch({ Entries: createMessages(), QueueUrl: queue }).promise()
    }
    return {
        statusCode: 200,
        body: JSON.stringify("all done")
    };
}

const createMessages = () => {
    let entries = []
   
    for (let i=0; i<10; i++) {
        entries.push({
          Id: 'id'+parseInt(Math.random()*1000000),
          MessageBody: 'value'+Math.random()
        })
    }
    return entries
}
```

E o código do *worker* e do *DLQReader* são basicamente os mesmos:

```tsx
let counter = 1
let messageCount = 0
let funcId = 'id'+parseInt(Math.random()*1000)
 
module.exports.handler = async (event) => {
    counter++;
    if (counter % 10 === 0){
        throw new Error('Simulating error');
    }
    
    // Grava o número de mensagens recebidas
    if (event.Records) {
        messageCount += event.Records.length
    }
    console.log(funcId + ' REUSE: ', counter)
    console.log(funcId + ' Message Count: ', messageCount)
    console.log(JSON.stringify(event))
    console.log(funcId + ' processing...');
    await sleep(2000);
    console.log(funcId + ' job done!');
    return 'done'
};

const sleep = (milliseconds) => {
    return new Promise(resolve => setTimeout(resolve, milliseconds))
}
```

[Você pode conferir a solução completa aqui](https://github.com/vgaltes/ServerlessPatterns/tree/master/ScalableWebhook).

# Finalizando

Neste artigo, vimos a implementação de alguns padrões do excelente artigo de Jeremy Daly. Continuaremos com isso no artigo seguinte.

Espero que tenha te ajudado!!

---

# Créditos

- [Serverless Patterns implemented, part 1](https://vgaltes.com/post/serverless-patterns-implemented-part1/), escrito originalmente por [Vicenç García-Altés](https://twitter.com/vgaltes).