AWS Food Fair API: Um CRUD com serviços da AWS e testes automatizados de integração e end-to-end
Este artigo é um complemento para o repositório marcusviniciusfa/aws-food-fair-api. Lá, você vai encontrar todo o projeto: o código, informações sobre como executar os testes automatizados, como fazer a implantação do código na Amazon Web Services, a partir do terminal, além de uma pequena documentação da API. Aqui, por outro lado, você terá um passo a passo para reproduzir o provisionamento e a integração da infraestrutura deste projeto, diretamente no console da AWS. Estou presumindo, porém, que você já tenha uma conta na Cloud (de preferência uma conta de nível gratuito - para fins de teste). Mas, caso não tenha, pode criar facilmente a partir do link aws.amazon.com/free.
Considere
A interface do console da AWS pode mudar de tempos em tempos, porém, é muito provável que a experiência de uso da interface não se torne difícil, e que a curva de aprendizado seja mínima. Sendo assim, acredito que será possível refazer os passos aqui descritos para o provisionamento e gestão dos recursos, sem dificuldade.
Visão geral
O sistema que vamos construir juntos é uma API para CRUD de itens de uma Feira (ou Mercado) de Alimentos. A ideia é que o projeto seja muito simples, para que possamos nos preocupar mais com os serviços da AWS. A estrutura dos itens não é complexa, eles possuem apenas um identificador, nome e preço. Outros atributos podem ser adicionados posteriormente, tais como: descrição, categoria, peso, etc.
Para a construção da API, vamos utilizar o serviço de Funções Lambda (serverless) para executar o nosso código (JavaScript com Node.js), o serviço de API Gateway para receber as solicitações HTTP e direcionar para a função Lambda e o serviço de DynamoDB (banco de dados NoSQL).
Nossa API pode ser testada manualmente através de algum cliente HTTP (veja a seção Documentação da API, no repositório do projeto). Porém, temos uma alternativa muito mais produtiva! Criei alguns casos de testes automatizados de integração, para verificar o comportamento da função handler de forma isolada dos outros serviços e, também, casos de testes end-to-end, para testar a API. Sendo assim, ao final do provisionamento, configuração e implantação da função Lambda, poderemos executar os casos de testes end-to-end para simular o uso da API, por parte de um cliente real, verificando os resultados das solicitações HTTP e fazendo passar ou falhar os casos de testes, sem a necessidade de uma intervenção manual.
Provisionamento
DynamoDB
No console da AWS, use o campo de pesquisa para procurar por "DynamoDB", e selecione o serviço. Em Tabelas, clique em Criar tabela para iniciar a criação de uma tabela.
Neste próximo passo, preencha o campo Nome da tabela com "crud-food-fair-table" (exemplo), e o campo Chave de partição com "id" do tipo String e clique em Criar tabela, para então, provisionar este recurso.
Lambda
No console da AWS, use o campo de pesquisa para procurar por "Lambda", e selecione o serviço. Em Funções, clique em Criar função para iniciar a criação de uma função.
Neste próximo passo, preencha o campo Nome da função com "crud-food-fair-function" (exemplo).
Em seguida, na seção Permissões, clique no menu Alterar a função de execução padrão. Selecione a opção Criar uma função a partir da política da AWS templates. Na seção Nome da função, adicione o nome "database-crud-access" (exemplo) para o papel (função). Em Modelos de política, procure por "Permissões de microsserviço simples", adicione o modelo e clique em Criar função para provisionar o recurso.
Após o provisionamento do recurso o console irá mostrar uma IDE para editar e implantar o código da função. Ignore essa abordagem, vamos criar a nossa função em ambiente local, e fazer a implantação dela para substituir esse código, logo adiante.
API Gateway
No console da AWS, use o campo de pesquisa para procurar por "API Gateway", e selecione. Em APIs, escolha o tipo API HTTP clicando em Compilar.
Neste próximo passo, preencha o campo Nome da API com "crud-food-fair-api" (exemplo) e clique em Avançar.
Avance os passos seguintes até chegar em Analisar e criar. Neste passo, o console mostrará um resumo do que foi definido para o provisionamento da API. Clique em Criar para provisionar o recurso.
Após o provisionamento o console deverá mostrar alguns detalhes da API. Copie o valor do campo Invocar URL, vamos precisar dele para configurar a URL base para as solicitação HTTP (Não se preocupe, esta página sempre estará disponível para consulta posteriores).
Configuração
Com o console ainda aberto na aba de detalhes da API, vamos agora configurar as rotas que irão receber as solicitações HTTP. Clique em Routes e, em seguida, em Create. Selecione o método HTTP, insira o caminho e clique em Salvar. Neste passo, é importante criar exatamente as mesmas rotas do exemplo (métodos e os seus respectivos caminhos), caso contrário, seria necessário alterar todos os testes end-to-end.
Adicione as rotas a seguir:
- Método: GET + Caminho: /api/items
- Método: GET + Caminho: /api/items/{id}
- Método: POST + Caminho: /api/items
- Método: DELETE + Caminho: /api/items/{id}
- Método: PUT + Caminho: /api/items/{id}
- Método: PATCH + Caminho: /api/items/{id}
- Método: - (qualquer um) + Caminho: $default
A rota com o caminho $default recebe requisições para caminhos que não foram mapeados pelas outras rotas. Ela será utilizada para darmos uma resposta personalizada às solicitações que não são tratadas pela nossa API. Ao final, teremos criado todas as rotas que serão tratadas pela função Lambda.
Com as rotas criadas acesse a opção Integrations no menu lateral e depois acesse a aba Gerenciar integrações. Clique em Create para criar uma integração para as rotas.
Na seção Tipo de integração, selecione a opção Função do Lambda. Na seção Função do Lambda selecione a nossa função (crud-food-fair-function) e clique em Criar.
Na aba Anexar integrações a rotas, comece selecionando uma rota e adicionando a ela a integração (que acabamos de criar) com a função Lambda. Em seguida, clique em Anexar integração. Faça este mesmo processo para todas as rotas, inclusive para a rota $default.
Agora que temos todas as rotas configuradas, baixe o repositório do projeto para fazermos as últimas configurações antes de implantar a função Lambda e executar os testes end-to-end. Faça o download do repositório e entre no diretório do projeto executando o comando a seguir:
git clone https://github.com/marcusviniciusfa/aws-food-fair-api.git && cd aws-food-fair-api
Instale as dependências do projeto Node.js, executando npm install
.
Crie um arquivo .env
(responsável por armazenar as variáveis de ambiente), e adicione a variável API_GATEWAY_BASE_URL contendo o valor da URL que copiamos da página de detalhes da API. Seu arquivo de variáveis de ambiente deve se parecer com isso:
# arquivo .env
API_GATEWAY_BASE_URL=https://d5lfqyfvwj.execute-api.us-east-1.amazonaws.com
Você também pode criar o arquivo utilizando a linha de comando. Para isso, execute o comando:
# não esqueça de editar a URL!
echo API_GATEWAY_BASE_URL=https://d5lfqyfvwj.execute-api.us-east-1.amazonaws.com >> .env
Por último, caso tenha dado um nome diferente do nome de exemplo (crud-food-fair-table) para a tabela de itens no DynamoDB, você precisará alterar manualmente o valor padrão para a variável TABLE_NAME no código da função. É simples, basta editar a linha 10 no arquivo index.mjs
(que está na raiz do projeto), substituindo o valor atual da variável pelo nome que você deu para a tabela de itens, no momento da criação do recurso. Isso também vale para o nome da função Lambda. Caso tenha dado a função um nome diferente do nome de exemplo (crud-food-fair-function), você precisará alterar manualmente o script usado para fazer implantações (script deploy). Este script fica na linha 12 do arquivo package.json
, e a alteração é bastante simples. No valor do script substitua o nome que vêm por padrão, após o parâmetro "--function-name", pelo nome que você deu para a função Lambda, no momento da criação do recurso.
Implantação
Para utilizar os scripts abaixo e fazer a implantação da função Lambda será necessário ter a CLI da AWS instalada na sua máquina. Se não tiver, você poderá fazer a instalação através do link https://aws.amazon.com/pt/cli. Após a instalação verifique se CLI está funcionando, executando o comando aws --version
. Com a CLI pronta para o uso, configure sua conta executando o comando aws configure
.
Para fazer a implantação precisaremos gerar um arquivo function.zip contendo o função handler que será implantada. Sendo assim, vamos executar o seguinte script npm run build
. Agora que temos o arquivo function.zip só precisamos executar o script npm run deploy
, para fazer a implantação da função Lambda na AWS.
Testando a API
Os testes end-to-end verificam o funcionamento das funcionalidades de "ponta-a-ponta". Em nosso projeto, eles fazem solicitações HTTP para o API Gateway, que recebe e distribui as solicitações como eventos para a função Lambda. Neste caso, diferente dos testes de integração, o banco de dados DynamoDB foi estimulado e os resultados são reais. Execute os testes end-to-end com o script npm run test:e2e
Você deve ter um resultado semelhante ao da imagem abaixo, com todos os testes passando. Perceba que os testes são uma ótima documentação para nossa API, todos os requisitos estão descritos neles, de forma que sabemos exatamente qual deve ser o comportamento para cada cenário.
Para mais informações sobre os testes veja a seção Testes, no repositório do projeto.
Considerações finais
Preciso deixar claro que é um boa prática na construção de funções Lambda, que elas sejam curtas (com pouco código) e dedicadas a uma única operação. Assim elas podem ser mais rápidas, mais legíveis e mais manuteníveis. No nosso caso, cada endpoint deveria então estar associado a apenas uma função Lambda. Porém, nossa função handler recebe todos os tipos de solicitação HTTP, e direciona os eventos para o código que deve tratá-lo, tudo isso dentro de uma mesma função. Resolvi fazer dessa maneira por ser um projeto inicial e de estudos. Entretanto, a forma como o código está estruturado, permite que ele seja facilmente quebrado em partes menores e movido para outras funções Lambda, seguindo o princípio Single Responsibility do SOLID. Pretendo criar uma segunda branch com esta alteração, e assim, demonstrar como deve ser uma função Lambda em um ambiente real. Por ora, você pode trabalhar nisso se quiser, e caso o faça, eu gostaria muito de saber da sua contribuição. Vale lembrar que como estamos fazendo uso do API Gateway, uma mudança desse tipo (divisão da função Lambda em várias funções) deve ser transparente para os clientes da API, que não devem fazer adaptações em suas solicitações HTTP a fim de continuar utilizando a API de forma correta.
Obrigado por ter lido até aqui! 🎉
Gostou desse conteúdo? Achou relevante para outras pessoas?
Se sim, interaja na postagem!
Além disso, vamos nos conectar no LinkedIn? Envie um pedido de conexão para o meu perfil, será um prazer receber você na minha rede de contatos.