Utilizando Spring Cloud com AWS SQS e LocalStack
Numa arquitetura de nuvem moderna, as aplicações são separadas em componentes independentes que em tese deveriam tornar mais fáceis o desenvolvimento, implantação e manutenção dos projetos.
Quando construímos aplicações para rodar em ambientes como a AWS, acessamos vários serviços, para variados fins: S3 para armazenar arquivos, DynamoDB ou RDS para salvar dados, funções Lambda para escrever manipuladores de eventos, SQS para envio e recebimento de mensagens e muito mais.
Como ficamos dependentes desses serviços, surgem algumas dificuldades tanto para rodar a aplicação de modo local, quanto para elaborar os testes integrados.
Uma alternativa para esses casos é o LocalStack. Com ele nós podemos desenvolver e testar nossas aplicações com implementações simuladas desses serviços.
De uma forma bem resumida, o LocalStack é um simulador open source dos serviços reais da AWS. Ele fornece um ambiente de teste em nossa máquina local com as mesmas APIs dos serviços reais da AWS. Dessa forma podemos configurar nossa aplicação para usarmos os serviços reais apenas em ambientes específicos.
Mais sobre o Localstack aqui: https://github.com/localstack/localstack
O objetivo desse post é demonstrar como uma aplicação desenvolvida com Spring Boot utilizando o projeto Spring Cloud pode, por meio do Localstack, simular o envio e recebimento de mensagens ao SQS, um dos serviços de fila da AWS.
Spring Cloud é um conjunto de projetos que nos ajuda a construir uma aplicação aderente aos princípios do 12 factors.
Mais sobre o 12 factors aqui: https://12factor.net/pt_br/
Assim como o Spring Boot, muitos sub-projetos do Spring Cloud incluem entradas que podemos adicionar como dependências para ativar recursos nativos da nuvem ao nosso projeto. Em muitos casos, muitos recursos são habilitados puramente adicionando o starter ao nosso pom.xml.
Spring Cloud for Amazon Web Services (AWS)
Spring Cloud for Amazon Web Services (AWS) é um sub-projeto do Spring Cloud que facilita a integração com os serviços AWS. É construído por módulos, onde cada módulo é responsável por fornecer integração com um Serviço AWS.
Para configurar o Spring Cloud AWS, precisamos adicionar a dependência do Spring Cloud AWS BOM no nosso arquivo pom.xml usando este bloco de dependência:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>2.3.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Spring Cloud AWS Messaging
Dentre os módulos do Spring Cloud for Amazon Web Services (AWS), o Spring Cloud AWS Messaging é o módulo que faz a integração com o AWS SQS para simplificar a publicação e o consumo de mensagens sobre SQS.
Para adicionar o suporte para mensagens, precisamos incluir a dependência do módulo em nossa configuração do Maven. Fazemos isso adicionando o Starter Module Spring-Cloud-Starter-AWS-Messaging:
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-starter-aws-messaging</artifactId>
</dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-messaging</artifactId>
<version>2.3.0</version>
</dependency>
O Spring-Cloud-Starter-AWS-Messaging inclui as dependências transitivas para a Spring-Cloud-Starter-AWS e a Spring-Cloud-AWS-Messaging.
O Amazon SQS permite apenas cargas do tipo String, de modo que qualquer objeto enviado ao SQS deve ser transformado para este tipo antes de ser colocado na fila. O Spring Cloud AWS permite transferir objetos Java para SQS convertendo-os em String no formato JSON.
Enviando uma Mensagem
Podemos enviar mensagens para uma fila SQS usando o QueueMessageChannel ou QueueMessingTemplate.
O QueueMessagingTemplate contém muitos métodos para envio de mensagens. Em alguns, o destino pode ser especificado por meio de um objeto QueueMessageChannel criado com a URL de endereço da fila, em outros, apenas com o nome da fila fornecida como uma String.
Para tanto precisamos criar um bean de QueueMessagingTemplate na classe de configuração com um cliente AmazonSQSAsync, que está disponível por padrão quando utilizamos o Spring Cloud AWS Messaging Boot Starter.
A criação desses beans irá permitir que nossa aplicação converse com o serviço SQS que localmente será fornecido pelo Localstack. Podemos adicionar uma configuração para cada perfil (Local, Dev, hom e etc) na nossa aplicação que apontará para o ambiente AWS real. Para testar localmente, vamos subir o LocalStack, criar uma fila de teste e apontar a nossa configuração para este serviço.
Uma mensagem a ser enviada ao SQS é representada pela interface Message. Após montar o conteúdo da mensagem, podemos usar o método convertAndSend() para enviá-la a fila de destino:
Para subir o SQS no LocalStack com docker-compose:
localhost:4576 é a porta onde o SQS será acessado e localhost:8080 é a interface gráfica do Localstack, onde poderemos ver a fila criada ao subir a aplicação.
Recebendo uma mensagem
Para receber uma mensagem, o cliente deve chamar a API do SQS para verificar novas mensagens (ou seja, as mensagens não são enviadas do servidor para o cliente. O cliente deve buscá-la). Há duas maneiras de pesquisar novas mensagens do SQS:
- Short Polling: Retorna imediatamente, mesmo que a fila da mensagem pesquisada esteja vazia. Para uma pequena pesquisa, chamamos o método receive() de QueueMessagingTemplate em um loop infinito que regularmente pesquisa a fila. Esse método retorna vazio se não houver mensagens na fila.
- Long Polling: Não retorna uma resposta até que uma mensagem chegue na fila, ou ocorra o timeout. Fazemos isso com a anotação @SQSListener.
Na maioria dos casos, o Long Polling é preferível, já que as longas solicitações de pesquisa permitem que os consumidores de filas recebam mensagens assim que chegam na fila, enquanto reduzem o número de respostas vazias retornadas (e, portanto, os custos do SQS, já que são calculado por chamadas de API).
Anotamos um método com @SQSListener para escutar uma fila. Essa anotação adiciona comportamento de pesquisa ao método e também fornece suporte a serialização e conversão da mensagem recebida para um objeto Java, conforme mostrado aqui:
Neste exemplo, a mensagem recebida é serializada e passada para o nosso método de receiveMessage(). Também definimos a política de exclusão como ON_SUCCESS, ou seja, a mensagem só será excluída da fila quando nenhuma exceção for lançada, quando houver sucesso no processamento daquela mensagem.
Uma deletionPolicy (política de exclusão) é usada para definir em quais casos uma mensagem deve ser excluída após o método do ouvinte ser chamado.
Conclusão
Neste post foi apresentado uma maneira simples de implementar um consumidor e um produtor de mensagens com o módulo Spring Cloud for Amazon Web Services (AWS) para interagir com uma fila SQS. Um código de exemplo com base nesse post está disponível no Github e pode ser encontrado aqui:
Referências
Getting Started With AWS SQS and Spring Cloud (reflectoring.io)