DEV Community

Cover image for Seu primeiro sistema serverless com AWS Lambda
Giovana Armani for AWS

Posted on

Seu primeiro sistema serverless com AWS Lambda

A ideia de subir código na nuvem pode parecer muito complexa para quem nunca fez isso antes. E se chover? Ai ferrou minha aplicação...
Brincadeirinhas à parte, juro que é mais tranquilo do que parece. Nesse artigo vamos construir uma aplicação na nuvem do zero usando AWS Lambda!


O que é AWS Lambda?

AWS Lambda é um serviço da AWS que permite você criar sistemas serverless. Isso significa que você não precisa configurar os servidores do seu sistema, a nuvem faz isso pra você e você fica livre pra focar no código! Para saber mais sobre serverless e suas vantagens e desvantagens, veja esse artigo: A verdade oculta por trás do Serverless

Uma função Lambda é uma unidade de código que fica na nuvem esperando ser chamada para executar alguma tarefa. Chamamos de evento o acontecimento que aciona a função. Em sistemas gerais, esse evento pode ser uma notificação, uma atualização no banco de dados, um usuário apertando um botão, entre outros. Após ser acionada, uma função Lambda pode executar várias tarefas, inclusive chamar outros serviços como um banco de dados.

Diagrama de funcionamento lambda

Por onde começar?

Tudo que veremos nesse post pode ser replicada em uma conta free tier da AWS! Ela permite que você comece a explorar vários serviços da AWS gratuitamente e apenas será cobrado caso escolha trocar de plano. Aprenda mais sobre o free tier e como setar sua conta nesse post.

Nossa aplicação de exemplo

Para demonstrar como subir e rodar código na nuvem, vamos construir uma aplicação que transforma arquivos CSV (ou planilhas de Excel) em JSON. Sei que existem sites que já fazem isso gratuitamente, mas às vezes precisamos converter arquivos privados e sensíveis que talvez a gente não queira subir em um sistema de outros. Em momentos como esse, é muito bom ser dev e conseguir construir sua própria solução rapidinho!

O diagrama abaixo demonstra o funcionamento do nosso programa. Vamos subir o arquivo que queremos converter em um bucket do Amazon S3 (Amazon Simple Storage Service). Ele é um serviço de armazenamento de arquivos oferecido pela AWS. A ação de colocar o arquivo no bucket vai acionar nossa função lambda. Ela vai buscar o arquivo no bucket, converter para JSON e colocar o arquivo convertido em outro bucket de output.

Arquitetura da aplicação

Construindo a aplicação

Listarei a seguir os passos que segui para construir a aplicação. Siga os passos para construir comigo ou acompanhe o processo pelo video no final.

Você sabia que pode configurar as páginas da conta AWS para ficar em português caso não esteja automaticamente? Assim que entrar na sua conta, selecione o ícone de engrenagem à direita da tela e no dropdown embaixo de "Language", selecione "Portugês"!

Console AWS em português

Criando a função inicial

  1. Na conta free tier da AWS, naveguei até a página do Lambda através da caixa de busca
  2. Cliquei em "Criar função"
  3. Chamei minha função de csvToJsonConverter e selecionei nodejs24.x como linguagem. Outras configurações mantive como default
  4. Cliquei em "Criar função" no canto inferior direito da página para criar e ser direcionada para a página da função Console AWS demonstrando editor de código na página do Lambda
  5. Na seção de Configurações de tempo de execução, me certifiquei que o manipulador era index.handler, para indicar o ponto de entrada da aplicação
  6. Na página da função Lambda, na aba de código, existe um editor de texto, como demonstrado na imagem acima, que pode ser usado para escrever o código da função. Essa funcionalidade está disponível para códigos em Node.js, Python e Ruby. Nele, já existia um arquivo chamado index.mjs que serve como porta de entrada do programa. Em funções Lambda, essa porta de entrada é chamada de handler e no console da AWS em português é traduzido como "manipulador". Adicionei apenas um log no arquivo para validar minha estrutura inicial:
export const handler = async () => {
  console.log("Handler chamado!")

  const response = {
    statusCode: 200,
    body: JSON.stringify('Minha primeira função!'),
  };
  return response;
};
Enter fullscreen mode Exit fullscreen mode

Parabéns! Você escreveu seu primeiro código na Lamba! Agora clique
no botão "Deploy" para subir o código para a função lambda!

Após criar essa estrutura inicial é possível testar a chamada para a função na aba de "Testar" e ver nos logs da execução se nosso código está sendo chamado como esperado. Para isso, mantive todas as configrações da aba de teste como estavam e apenas cliquei em "Testar".

Criando os buckets S3

Os buckets vão servir para armazenar nossos arquivos de entrada e de saída. Vamos criá-los!

  1. Naveguei até a página do S3 pelo console da AWS Console AWS na página de configuração do bucket S3
  2. Cliquei em "Criar bucket" e chamei o bucket de gio-csv-converter-input e mantive outras configurações default (lembre-se de **sempre* bloquear todo o acesso público)*. Os nomes de buckets no S3 são únicos globalmente, então você não conseguirá usar o mesmo nome de bucket que o meu. Sugiro usar <seu-nome>-csv-converter-input
  3. Cliquei em "Criar bucket" no final da tela de configurações para criar
  4. Criei também outro bucket chamado gio-csv-converter-output que servirá para guardar os arquivos convertidos. Novamente, você precisará de um nome de bucket diferente. Sugiro <seu-nome>-csv-converter-output
  5. Novamente, cliquei em "Criar bucket" no final da tela

Adicionando a conexão com o S3

Como vimos anteriormente, quando colocarmos um arquivo no bucket do S3, nossa função lambda vai ser acionada. Agora temos que avisar a AWS que vai ser assim

  1. De volta na página da função lambda, cliquei em "Adicionar gatilho" Console AWS na página de criação de gatilho para função Lambda
  2. No dropdown de seleção procurei por "S3" e selecionei o serviço
  3. Cliquei no campo de "bucket" e selecionei meu bucket de input
  4. No campo de "Event types" selecionei "All object create events" que significa que será acionando smepre que um novo arquivo for adicionado no bucket
  5. Mantive os campos de "Prefix" e "Sufix" vazios
  6. Cliquei na caixa de seleção no campo de "Recursive invocation". Infelizmente a mensagem dela ainda não foi traduzida para o português, mas a tradução seria: Reconheço que usar o mesmo bucket S3 para entrada e saída não é recomendado e que essa configuração pode causar invocações recursivas, aumento no uso do Lambda e aumento nos custos. É por esse motivo que criamos buckets separados para o input e output. Se colocássemos o arquivo final do mesmo bucket de input, a lambda seria acionada novamente e entraríamos em um loop infinito (que eventualmente ia custar uma fortuna!)
  7. Por fim, cliquei em "Adicionar" para criar o gatilho

Ao adicionarmos o S3 como gatilho para a função Lambda, a AWS vai automaticamente configurar as permissões para que o S3 possa acioná-la.

É possível ainda testar essa conexão pelo console navegando até o S3 e subindo um arquivo no bucket. Podemos ver os logs da nossa aplicação através do Amazon CloudWatch, o serviço de monitoramento de aplicações da AWS, seguindo os passos:

  1. Usando a caixa de pesquisa do console da AWS, navegue para a página do CloudWatch
  2. No menu à esquerda, clique em Logs > Gerenciamento de logs
  3. Clique no nome da sua função Lambda que aparecerá na lista de Log groups
  4. Procure pelo arquivo de log correspondente ao horário em que você executou a função

Console AWS mostrando logs no Cloudwatch

Implementação de funcionalidade e permissões

Usei a biblioteca @aws-sdk/client-s3 (clique aqui para a documentação) para conexão com os buckets S3 para acessar os arquivos. Escrevendo o código diretamente pelo console do Lambda, pude simplemente importar o que precisava sem instalar a biblioteca porque as funções Lambda já tem acesso ao SDK da AWS implicitamente. Para converter o conteúdo CSV para JSON, usei simples manipulação de string do javascript. Veja o código completo abaixo ou nesse repositório.

import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';

const s3Client = new S3Client({});
const OUTPUT_BUCKET = 'gio-csv-converter-output';

export const handler = async (event) => {
    console.log('abacaxi');
    console.log(`event: ${JSON.stringify(event)}`);

    for (const record of event.Records) {
        const sourceBucket = record.s3.bucket.name;
        const sourceKey = record.s3.object.key;
        const destinationKey = sourceKey.replace('.csv', '.json');

        try {
        const csvContent = await getCSVFromS3(sourceBucket, sourceKey);

        const jsonData = csvToJson(csvContent);

        await uploadJsonToS3(destinationKey, jsonData);

        return {
            statusCode: 200,
            body: JSON.stringify({
                message: 'CSV converted to JSON successfully',
                source: `${sourceBucket}/${sourceKey}`,
                destination: `${OUTPUT_BUCKET}/${destinationKey}`,
            }),
        };
    } catch (error) {
        console.error('Error processing file:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({ error: error.message }),
        };
    }
    }
};

async function getCSVFromS3(sourceBucket, sourceKey) {
    const getCommand = new GetObjectCommand({
            Bucket: sourceBucket,
            Key: sourceKey,
        });
    const response = await s3Client.send(getCommand);
    const csv = response.Body.transformToString();

    console.log(`String do csv puxado do S3: ${csv}`);
    return csv;
}

function csvToJson(csvContent) {
    const lines = csvContent.trim().split('\n');
    const headers = lines[0].split(',').map(h => h.trim());

    console.log(`Lines: ${lines}`);
    console.log(`Headers: ${headers}`);

    return lines.slice(1).map(line => {
        const values = line.split(',').map(v => v.trim());
        return headers.reduce((obj, header, index) => {
            obj[header] = values[index];
            return obj;
        }, {});
    });
}

async function uploadJsonToS3(destinationKey, content) {
    const putCommand = new PutObjectCommand({
        Bucket: OUTPUT_BUCKET,
        Key: destinationKey,
        Body: JSON.stringify(content, null, 2),
        ContentType: 'application/json',
    });
    await s3Client.send(putCommand);
}
Enter fullscreen mode Exit fullscreen mode

Caso copie o código acima no editor de texto da função lambda para testar, não se esqueça de clicar em "Deploy"!

Uma coisa importante de configurar também são as permissões para a função Lambda conseguir acessar os buckets. Se quiser entender mais sobre como funciona o IAM e os diferentes tipos de permissão veja esse artigo, mas em linhas gerais, segui esses passos:

  1. Na página da minha função Lambda, acessei a aba de "Configuração"
  2. No menu esquerdo, selecionei "Permissões"
  3. Cliquei no link embaixo de "Noma da função" que me direcionou para a página do IAM (AWS Identity and Access Management). Esse é o serviço que permite gerenciar permissões dentro da sua conta AWS Console AWS demonstrando como achar role da função
  4. A página do IAM foi aberta diretamente na role da função Lambda que gerencia o que ela pode fazer em outros serviços. Cliquei em "Adicionar permissões" > "Criar política em linha"
  5. Fui direcionada para a página do Policy Editor onde selecionei formato JSON e adicionei a permissão abaixo. Cliquei em "Próximo" no canto inferior da página e chamei a política de "AcessoAoS3" e cliquei em "Criar política" para terminar a criação
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "GetObjectPermission",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<nome-do-bucket-de-input>/*"
        },
        {
            "Sid": "PutObjectPermission",
            "Effect": "Allow",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::<nome-do-bucket-de-output>/*"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Podemos ver que essa permissão contém dois objetos dentro do array de "Statement". Cada objeto desses concede um acesso diferente. O primeiro permite (sinalizado pelo "Effect": "Allow") que nossa função faça a ação de "GetObject" sobre o bucket de input. Ou seja, a função vai poder pegar os arquivos que colocarmos no bucket de input para fazer a conversão. Já o segundo, permite que a função faça a ação de "PutObject" no bucket de output, ou seja, coloque os arquivos convertidos no bucket para podermos baixar.

⚠️ Não se esqueça de trocar os nomes dos buckets na permissão acima pelo nome dos seus buckets!

Testando tudo junto!

Está pronto! Agora podemos testar a aplicação final!

Usei esse csv de exemplo e coloque ele no meu bucket de input do S3. É possível ver os logs da execução pelo CloudWatch como explicado anteriormente e ver o arquivo de output final do bucket de output!

Console AWS demonstrando arquivo no bucket de output

Limpeza da casa

É uma boa prática nos acostumarmos a deletar os recursos da nuvem quando não estão mais em uso. Se algum dia você não estiver mais usando uma conta free tier, isso vai te poupar de ter cobranças surpresa (pode me agradecer depois). No nosso caso, os recursos criados foram uma função Lambda e dois buckets do S3.

Para remover a função, basta navegar até a página principal do serviço Lambda, selecionar a função que quer apagar e clicar em "Ações" > "Excluir".

Para remover os buckets, vá para para a página principal do S3, selecione o bucket e clique em "Excluir". É preciso que o bucket esteja vazio para que ele possa ser deletado. Caso não esteja, o console mostrará uma mensagem informando que é preciso esvaziá-lo. Você pode clicar em "Esvaziar bucket" para apagar todos os objetos. Após fazer isso, selecione o bucket novamente e clique em "Excluir".

Próximos passos

Agora você já sabe como subir seu código na nuvem e até mesmo como montar seu primeiro sistema serverless!

Uma limitação dessa aplicação que construimos é que os arquivos são convertidos para json um de cada vez, gerando vários documentos, mas e se quisermos colocar vários arquivos em um único output? Será que poderíamos subir uma pasta no S3 e converter tudo de uma vez?

Deixo como exercício clonar o repositório, replicar a infraestrutura que vimos e implementar essa funcionalidade nova!

Top comments (0)