AWS Serverless 04: persistência com DynamoDB
Conheça o modelo de dados do Amazon DynamoDB, crie a tabela de livros e itens pelo console e faça a função Lambda (Node.js, AWS SDK v3, async/await) inserir livros recebidos pelo POST /livros do API Gateway.
1. Visão geral
Neste material, prosseguimos com o desenvolvimento da solução retratada a seguir.

A aplicação que estamos construindo ao longo da série.
O que você vai aprender
- As características e o modelo de dados do Amazon DynamoDB (tabelas, itens, atributos, Partition Key e Sort Key)
- Como criar uma tabela e itens pelo console do DynamoDB
- Os tipos de atributo do DynamoDB (S, N, B, BOOL, M, L)
- Como acessar o DynamoDB a partir de uma função Lambda com o AWS SDK para JavaScript v3
- Como usar
async/awaitno handler da função Lambda - Como testar o
POST /livroscom a Thunder Client
O que você vai precisar
- Acesso ao AWS Academy Learner Lab
- A API
loja-livrose a função Lambda criadas nos codelabs anteriores da série - O VS Code com a extensão Thunder Client
2. Sobre o DynamoDB
O DynamoDB é um serviço de armazenamento de dados gerenciado da Amazon. Algumas de suas características são:
- Por ser um serviço gerenciado, não nos cabe fazer atualizações de sistema operacional ou mesmo do próprio sistema gerenciador de banco de dados. A escalabilidade também fica por conta da Amazon.
- Os dados que armazenamos são criptografados automaticamente.
- Todos os dados são armazenados em SSDs.
- Todos os dados são automaticamente replicados em múltiplas zonas de disponibilidade dentro de uma mesma região.
- Podemos escolher replicar os dados globalmente, em diversas regiões.
- Seu modelo de dados baseia-se no seguinte:
- Tabelas: similar a tabelas de outros sistemas de bancos de dados.
- Itens: cada tabela contém zero ou mais itens. Um item é um grupo de atributos que pode ser diferenciado de todos os demais. Em uma tabela que armazena pessoas, por exemplo, um item é uma pessoa.
- Atributos: um dado "escalar", por exemplo uma string, um número inteiro, um valor booleano etc.
- Não há "schema" predefinido. Isso quer dizer que não é necessário definir os atributos que os itens de uma tabela têm e nem o seu tipo.
- As tabelas necessariamente possuem chave primária. A chave primária de uma tabela pode ser simples (um único atributo) ou composta (dois atributos).
- Uma chave primária simples leva o nome de Partition Key. Em função de seu valor, o DynamoDB calcula um código hash que serve para determinar a localização física daquele item no mecanismo interno do sistema.
- Uma chave primária composta possui uma Partition Key e uma Sort Key. O código hash calculado em função de sua Partition Key também serve para determinar a localização física do item. Todos os itens com Partition Key igual são armazenados juntos, ordenados de acordo com a Sort Key. Uma tabela que possui chave primária composta pode conter valores repetidos na Partition Key, desde que os valores de Sort Key sejam diferentes.
As figuras a seguir mostram exemplos de tabelas com chaves primária simples e composta.

Chave primária simples.

Chave primária composta.
Leia mais sobre o DynamoDB na documentação: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html.
3. Criando a tabela de livros
Utilizaremos o DynamoDB para armazenar os dados envolvidos em nossa aplicação em meio persistente. No console AWS, busque por DynamoDB. Clique em seu nome segurando CTRL no teclado. Assim você terá uma aba exclusiva para manipular o DynamoDB.

Buscando o DynamoDB.

Conferindo a região.
Para criar uma nova tabela, clique em Create table.

Create table.
Use Livro como nome da tabela e LivroID como a sua chave primária. A tabela terá uma chave primária simples. Portanto, use apenas o campo Partition Key. O tipo dela é String.

Nome da tabela e chave de partição.
Mais abaixo, mantenha a opção Use default settings. Clique em Create table.

Mantendo as configurações padrão.
Segundo a mensagem a seguir, a criação da tabela pode demorar um pouco. Aguarde.

A criação da tabela pode levar alguns instantes.
Clique no nome da tabela.

Abrindo a tabela.
4. Criando um item
Um item é semelhante a uma linha de uma tabela no modelo relacional. É uma instância da entidade representada por aquela tabela. Criemos um item manualmente em nossa tabela de livros. Para isso, clique em Actions >> Create Item.

Actions >> Create Item.
Coloque o número 1 (ele será armazenado como String) como chave primária. Depois, clique em Add new attribute para adicionar um novo atributo. Ele será do tipo String. Um atributo é semelhante a uma coluna no modelo relacional.

Adicionando um atributo do tipo String.
Repita o passo a passo para criação de novos atributos (Add new attribute >> String) que caracterizam um livro. Todos eles serão do tipo String. Ao final, clique em Create item.

Os atributos do livro.
Clique sobre a chave primária do item para visualizá-lo.

Abrindo o item criado.
Observe que ele pode ser visualizado como um objeto JSON.

O item visualizado como JSON.
Clique em Cancel quando terminar de visualizar o item.
5. Acessando o DynamoDB a partir de uma função Lambda
Os serviços AWS podem ser acessados "programaticamente" por meio de SDKs que a Amazon disponibiliza para diferentes linguagens. Quando vamos acessar um dos serviços em uma função Lambda, as funcionalidades dos SDKs já estão automaticamente disponíveis, não há a necessidade de fazer qualquer instalação.
Neste passo, vamos editar a nossa função Lambda. Ela será responsável por fazer a inserção de um livro. Lembre-se que ela está sendo acionada pelo API Gateway quando ele recebe uma requisição do tipo POST direcionada ao recurso /livros. Comece visitando a sua lista de funções Lambda e escolhendo aquela em que temos trabalhado até então.

Escolhendo a função Lambda.
De callbacks para async/await
A seguir, vamos ajustar a nossa função para que ela deixe de operar utilizando callbacks e passe a utilizar a construção async/await do JavaScript. Dessa forma, o código tende a ficar mais fácil de ler, manter e "debugar".
Veja como ela fica.
index.mjs
export const handler = async (event, context) => {
};
Importando o SDK
O próximo passo é importar:
- DynamoDBClient: como o nome sugere, representa um cliente DynamoDB. Permite que executemos comandos para a criação de tabelas, criação de itens, remoção de itens etc.
- DynamoDBDocumentClient: empacota o DynamoDBClient. Ele se encarrega de fazer conversões entre tipos nativos do JavaScript e do DynamoDB. Veja o que a documentação oficial fala sobre ele: "The document client simplifies working with items in Amazon DynamoDB by abstracting away the notion of attribute values. This abstraction annotates native JavaScript types supplied as input parameters, as well as converts annotated response data to native JavaScript types." (https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html)
- PutCommand: representa um comando de inserção de item no DynamoDB. Ele será construído com informações como o nome da tabela e os valores que caracterizam um item a ser inserido. Depois, utilizamos um cliente para enviá-lo ao servidor.
index.mjs
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { PutCommand, DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
export const handler = async (event, context) => {
};
Os clientes
A seguir, construímos o objeto DynamoDBClient. Observe que entregamos ao seu construtor um objeto em que poderíamos personalizar opções diversas. Um exemplo é a região. Neste momento, no entanto, não temos nada a personalizar. Por isso, passamos um objeto vazio.
index.mjs
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { PutCommand, DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
export const handler = async (event, context) => {
};
A seguir, construímos o objeto DynamoDBDocumentClient. Observe como ele "empacota" o objeto DynamoDBClient, provendo mais funcionalidades, como a conversão de tipos que mencionamos.
index.mjs
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { PutCommand, DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
export const handler = async (event, context) => {
};
O id do livro
Precisamos de um id para o livro. Há diferentes formas para se obter um. Neste exemplo, observe que, a cada requisição recebida pela função Lambda, um identificador de requisição é gerado. Vamos utilizar este mesmo identificador como identificador do livro sendo inserido no momento. Ele pode ser obtido a partir do objeto context que a nossa função recebe e seu nome é awsRequestId.
index.mjs
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { PutCommand, DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
export const handler = async (event, context) => {
const id = context.awsRequestId;
};
O comando de inserção
A seguir, construímos o objeto PutCommand especificando o nome da tabela e os dados do item (o livro) a ser inserido. Observe como utilizamos o id da requisição e também os dados do objeto event.
index.mjs
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { PutCommand, DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
export const handler = async (event, context) => {
const id = context.awsRequestId;
const command = new PutCommand({
TableName: "Livro-pdfs",
Item: {
LivroID: context.awsRequestId,
titulo: event.titulo,
autor: event.autor,
edicao: event.edicao
},
});
};
A seguir, podemos enviar o comando e "aguardar" a resposta com a construção await. Depois disso, devolvemos um objeto com a instrução return. O que ela devolve é equivalente àquilo que devolvíamos quando utilizávamos a função callback, usando seu segundo parâmetro.
index.mjs
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { PutCommand, DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
export const handler = async (event, context) => {
const id = context.awsRequestId;
const command = new PutCommand({
TableName: "Livro-pdfs",
Item: {
LivroID: context.awsRequestId,
titulo: event.titulo,
autor: event.autor,
edicao: event.edicao
},
});
const response = await docClient.send(command);
return response;
};
A função Lambda está pronta. Não se esqueça de clicar em Deploy.
6. Teste com a Thunder Client
Caso tenha realizado alguma alteração na sua API no API Gateway, faça novo deploy dela. Se estiver na dúvida, faça mesmo assim.

Actions >> Deploy API.
Depois disso, pegue o link de seu stage no API Gateway.

O link do stage.
Faça uma nova requisição na Thunder Client: método POST, a URL do stage terminada em /livros e, na aba Body, um JSON com os dados do livro.
Thunder Client · Body (JSON)
{
"titulo": "ABC",
"autor": "ABC",
"edicao": "1"
}

Requisição POST /livros e sua resposta.
Observe que o resultado inclui detalhes técnicos da requisição enviada ao DynamoDB. Isso está acontecendo pois a função Lambda realiza a requisição ao DynamoDB e, quando recebe a resposta, apenas a devolve ao API Gateway.
Visite também o DynamoDB e verifique se o novo livro foi cadastrado.

Abrindo a tabela no DynamoDB.

Explorando os itens da tabela.

O novo livro cadastrado no DynamoDB.
7. Exercícios
- Use o quadro Integration Response do API Gateway e/ou a função Lambda para transformar o resultado que será entregue para o cliente. Você deve devolver um objeto assim:
{
"id": "id que foi gerado durante a requisição e salvo no DynamoDB",
"titulo": "ABC",
"autor": "ABC",
"edicao": "1"
}
- Implemente o endpoint
GET /livros. Ele deve devolver a coleção de livros existente no DynamoDB. Para fazer essa implementação, você pode criar uma nova função Lambda. Estude os exemplos de código em https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/javascript_dynamodb_code_examples.html#actions. No quadro Integration Response, transforme o resultado da seguinte forma:
[
{
"id": "id que foi gerado durante a requisição e salvo no DynamoDB",
"titulo": "ABC",
"autor": "ABC",
"edicao": "1"
},
{
"id": "id que foi gerado durante a requisição e salvo no DynamoDB",
"titulo": "JKD",
"autor": "JKD",
"edicao": "2"
}
]
8. Encerramento
Parabéns! Sua API agora grava livros de forma persistente no DynamoDB: o POST /livros do API Gateway aciona a função Lambda, que usa o AWS SDK para inserir o item na tabela. No próximo codelab da série (AWS Serverless 05), seguimos com a implementação.
Referências
- Amazon Web Services (AWS) - Cloud Computing Services. 2023. Disponível em https://aws.amazon.com/. Acesso em setembro de 2023.
- PiCloud Launches Serverless Computing Platform To The Public | TechCrunch. 2023. Disponível em https://techcrunch.com/2010/07/19/picloud-launches-serverless-computing-platform-to-the-public/. Acesso em setembro de 2023.
- Serverless Architectures. 2023. Disponível em https://martinfowler.com/articles/serverless.html. Acesso em setembro de 2023.
- Who coined the term 'serverless'?. 2023. Disponível em https://www.quora.com/Who-coined-the-term-serverless. Acesso em setembro de 2023.