Java + AWS Lambda: desenvolvendo aplicações serverless com SQS, S3 e LocalStack usando a API Oficial da Marvel

Antes de Comecar

Recomendo a leitura dos artigos abaixo antes de iniciar:

Objetivo

No último artigo, criamos um ambiente de desenvolvimento local para a criação, depuração e execução de um Lambda com Java. Vamos aprofundar o conhecimento, consumindo informações de um SQS e criando arquivos em um bucket S3 através dos eventos de um Lambda.

LocalStack

Antes de iniciar a execução e implantação do Lambda no ambiente do LocalStack, devemos mudar o modo para execução como local. O modo padrão de execução é através de um container Docker.

Neste modo, podemos ter problemas para acessar URL’s externas devido a rede onde o container está sendo executado:

Configurando o S3 no SDK

Quando executamos o Lambda dentro da IDE, a aplicação é executada usando um container Docker com uma imagem AMI. Neste contexto, teremos problemas para acessar a URL padrão do S3 no LocalStack através da IDE e impossibilitando a depuração da nossa aplicação de forma integrada com os recursos da AWS.

Para resolver esse problema usamos o S3 no formato path onde a URL é chamada diretamente.

Para entender melhor vamos comparar a chamada das URL’s do S3:

A primeira forma (Modo Path) funciona na comunicação entre containers, pois esse endereço é o mapeamento para localhost.

A segunda forma (Modo Padrão) não conseguimos efetuar a comunicação entre containers, pois é um endereço “externo” criado pelo próprio LocalStack para acessar o S3 e simular o formato padrão que é executado dentro da AWS.

Na aplicação existem duas variáveis de ambiente a ENV_DOCKER para execução no Modo Path e a variável ENV_TYPE para definir se a aplicação está em execução na AWS ou no LocalStack:

Funcionamento da Aplicação

Continuando o exemplo dos outros artigos, a nossa aplicação obtém as informações dos personagens da API Oficial da Marvel, e envia uma mensagem com a URL da imagem do personagem selecionado para um SNS que notifica o SQS.

O Lambda é captura essa messagem do SQS com a URL e criar o arquivo em um bucket S3.

Efetuamos esse processamento de forma assíncrona, pois operações de escrita com arquivo podem ser demoradas ocupando maior tempo de resposta na API.

Abaixo segue o desenho da solução:

Código do Lambda

Para o desenvolvimento do Lambda, precisamos implementar a interface RequestHandler na qual devemos passar a entrada que será a trigger de ativação da nossa função Lambda. A saida é normalmente uma String somente para demonstrar um status de execução do nosso Lambda.

A entrada é um evento do SQS e para isso, utilizamos a classe SQSEvent.

Em um próximo artigo vamos entrar em detalhe nos AWS Lambda Destinations:

Utilizamos o SDK padrão da AWS para criar o arquivo da imagem do personagem em um bucket S3:

No processo de execução, obtemos a imagem através da URL gerada a partir da consulta do persongem na API Oficial da Marvel e gravamos em um bucket S3:

No nosso exemplo, incluímos um log personalizado para criar um trace id das chamadas do Lambda e possibilitar o rastreamento de possíveis problemas:

Executando a Aplicação no Intellij

Na execução dentro da IDE, devemos passar as variaveis de ambiente conforme exemplo abaixo e um arquivo que contém o conteúdo da mensagem do SQS que o Lambda consumirá:

Testando o Lambda com Testcontainer

Para garatirmos a qualidade da nossa aplicação, foram criados testes unitários e integrados que simulam a execução do Lambda dentro do JUnit utilizando o Testcontainers:

Com o Testcontainer, conseguimos subir aplicações externas através do Docker e efetuar a comunicação usando o JUnit.

No nosso caso, subimos o LocakStack dentro da classe de teste para executar o Lambda:

Simulamos a URL da imagem através do Wiremock. Para mais informações sobre Wiremock, consulte o artigo abaixo:

Executando o teste do Lambda para gerar um arquivo no bucket S3:

Implantando a aplicação no LocalStack

O Lambda precisa de um IAM Role para a sua execução:

aws --endpoint http://localhost:4566 --profile localstack \
iam create-role \
--role-name marvelWorkerFunctionRole \
--assume-role-policy-document "{\"Version\": \"2012-10-17\",\"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}"

Com a IAM Role criada, vamos incluir a policy de execução:

aws --endpoint http://localhost:4566 --profile localstack \
iam attach-role-policy \
--role-name marvelWorkerFunctionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Precisamos incluir a permissão para o AWS Lambda efetuar a leitura das mensagens armazenadas em um SQS:

aws --endpoint http://localhost:4566 --profile localstack \
iam attach-role-policy \
--role-name marvelWorkerFunctionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole

Permissão para o Lambda gravar arquivos em um bucket S3:

aws --endpoint http://localhost:4566 --profile localstack \
iam attach-role-policy \
--role-name marvelWorkerFunctionRole \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess

Efetuamos a implantação do Lambda no LocalStack com o seguinte comando:

aws --endpoint http://localhost:4566 --profile localstack \
lambda create-function \
--function-name marvelWorkerFunction \
--zip-file fileb://aws-lambda-marvel-worker-1.0.jar \
--handler br.com.thomasdacosta.handler.ApplicationHandler \
--runtime java11 \
--role arn:aws:iam::000000000000:role/marvelWorkerFunctionRole \
--memory-size 512 --timeout 600 \
--environment Variables={ENV_TYPE=localstack}

No comando acima, definimos o tamanho máximo de memória que a aplicação utilizará.

A variável de ambiente ENV_TYPE é atribuida para definir o ambiente de execução para LocalStack.

O arquivo JAR de implantação fica dentro do diretório target do projeto Maven.

Definimos que a trigger do Lambda será a partir de um SQS:

aws --endpoint http://localhost:4566 --profile localstack \
lambda create-event-source-mapping \
--function-name marvelWorkerFunction \
--batch-size 10 \
--event-source-arn arn:aws:sqs:us-east-1:000000000000:marvelThumbnailImageQueue

Live no YouTube do SouJava

Maiores informações assitam a Live no YouTube do SouJava:

Github:

Aplicação completa:

Código Java do AWS Lambda:

Conclusão

Utilizando os recursos do AWS SAM e do AWS SDK, conseguimos desenvolver um Lambda que é acionada através de uma fila SQS e grava um arquivo em um bucket S3.

Conseguimos efetuar os testes localmente usando LocalStack e além disso, os testes unitários são executados através do TestContainer e do Wiremock para simulação fiel do ambiente.

Através de variáveis de ambiente, conseguimos definir qual o local de execução da apliação.

Nos próximos artigos vamos abordar conceitos mais avançados como Layers e a utilização de Quarkus, Micronaut e Spring Cloud Function para o desenvolvimento de aplicações serverless.

Até a próxima!

--

--

📺+ de 20 anos gerando código fonte em Java, Arquiteto Java, Desenvolvedor Java, Professor Universitário e Colecionador de Videogames

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store