O blog da AWS

Re-arquitetando aplicações Java usando o AWS Serverless Java Container atualizado

Esta publicação foi escrita por Dennis Kieselhorst, arquiteto principal de soluções, e traduzida para o português por Bianca Mota, arquiteta de soluções para o segmento de startups.

A combinação de portabilidade, eficiência, comunidade e variedade de recursos tornou o Java uma escolha popular para empresas criarem suas aplicações por mais de 25 anos. A introdução de funções Serverless, em que o AWS Lambda foi pioneiro, mudou o que você precisa em uma linguagem de programação e ambiente de execução (runtime). As funções geralmente têm ciclo de vida curto, têm uma finalidade única e não exigem uma configuração extensa de infraestrutura.

Esta publicação mostra como você pode modernizar uma aplicação Java legada para ser executada no Lambda com o mínimo de alterações de código usando o AWS Serverless Java Container atualizado.

Comparação de modelo de implantação

As aplicações Java corporativas clássicas geralmente são executadas em servidores de aplicações como JBoss/WildFly, Oracle WebLogic e IBM WebSphere, ou em contêineres de servlet como o Apache Tomcat. A máquina virtual Java subjacente normalmente funciona 24 horas por dia, 7 dias por semana, e atende a várias solicitações usando seus recursos de multithreading.

Servidor de aplicação Java típico de longa execução

Ao criar funções do Lambda com Java, um servidor HTTP não é mais necessário e há outras considerações para executar código em um ambiente Lambda. O código é executado em um ambiente de execução, que processa uma única invocação por vez. As funções podem ser executadas por até 15 minutos com um máximo de 10 Gb de memória alocada.

As funções são acionadas por eventos como, por exemplo, uma requisição HTTP com um payload correspondente. Uma solicitação HTTP do Amazon API Gateway invoca a função com o seguinte payload JSON:

Carga útil (payload) de solicitação HTTP do Amazon API Gateway

O código para processar esses eventos é diferente de como você o implementa em um aplicativo tradicional.

AWS Serverless Java Container

O AWS Serverless Java Container facilita a execução de aplicações Java escritas com estruturas como Spring, Spring Boot ou JAX-RS/Jersey no Lambda.

O contêiner fornece lógica de adaptador para minimizar as alterações no código. Os eventos recebidos são traduzidos para a especificação Servlet para que as estruturas funcionem como antes.

Adaptador AWS Serverless Java Container

A versão 1 dessa biblioteca foi lançada em 2018. Hoje, a AWS está anunciando o lançamento da versão 2, que suporta a especificação mais recente do Jakarta EE, junto com o Spring Framework 6.x, o Spring Boot 3.x e o Jersey 3.x.

Exemplo: modificação de uma aplicação Spring Boot

O exemplo a seguir ilustra como migrar um aplicativo Spring Boot 3. Você pode encontrar o exemplo completo do Spring e de outras estruturas no repositório do GitHub.

  1. Adicione a dependência do AWS Serverless Java ao seu arquivo de compilação POM do Maven (ou Gradle):
    <dependency>
        <groupId>com.amazonaws.serverless</groupId>
        <artifactId>aws-serverless-java-container-springboot3</artifactId>
        <version>2.0.0</version>
    </dependency>
    
  2. O Spring Boot, por padrão, incorpora o Apache Tomcat para lidar com requisições HTTP. Os exemplos usam o Amazon API Gateway para lidar com requisições HTTP de entrada para que você possa excluir a dependência.
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <artifactSet>
                                <excludes>
                                    <exclude>org.apache.tomcat.embed:*</exclude>
                                </excludes>
                            </artifactSet>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    

    O AWS Serverless Java Container aceita solicitações de proxy do API Gateway e as transforma em um objeto Java simples. A biblioteca também transforma as saídas em um objeto de resposta adequado para o API Gateway.

    Depois de executar seu processo de build, o plug-in Shade do Maven agora produz um Uber-JAR que agrupa todas as dependências, que você pode enviar para o Lambda.

  3. O runtime do Lambda deve saber qual método handler deve ser invocado. Você pode configurar e usar a implementação SpringDelegatingLambdaContainerHandler ou implementar sua própria classe handler do Java que delega ao AWS Serverless Java Container. Isso é útil se você quiser adicionar funcionalidades adicionais.
  4. Configure o nome do handler nas configurações de runtime da sua função.

    Configurar o nome do handler

  5. Configure uma variável de ambiente chamada MAIN_CLASS para que o handler genérico saiba onde encontrar a classe principal da aplicação original, que geralmente é anotada com @SpringBootApplication.

    Configurar a variável de ambiente MAIN_CLASS

Você também pode definir essas configurações usando ferramentas de infraestrutura como código (IaC), como o AWS CloudFormation, o AWS Cloud Development Kit (AWS CDK) ou o AWS Serverless Application Model (AWS SAM).

Em um template do AWS SAM, as alterações relacionadas são as seguintes. Os templates completos fazem parte do repositório do GitHub.

Handler: com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler 
Environment:
  Variables:
    MAIN_CLASS: com.amazonaws.serverless.sample.springboot3.Application

Otimizando configurações de memória

Ao executar funções do Lambda, o tempo de inicialização e o consumo de memória são considerações importantes. A quantidade de memória que você configura para sua função Lambda também determina a quantidade de vCPU disponível. Adicionar mais memória aumenta proporcionalmente a quantidade de vCPU e, portanto, aumenta a potência computacional geral disponível. Se uma função estiver vinculada à CPU, à rede ou à memória, adicionar mais memória pode melhorar o desempenho.

O Lambda cobra pela quantidade total de gigabytes por segundo consumida por uma função. Os gigabytes por segundo são uma combinação de memória total (em gigabytes) e duração (em segundos). O aumento da memória acarreta custos adicionais. No entanto, em muitos casos, aumentar a memória disponível causa uma diminuição na duração da função devido à CPU adicional disponível. Como resultado, o aumento geral do custo pode ser insignificante em termos de desempenho adicional ou pode até diminuir.

Escolher a memória alocada para suas funções do Lambda é um processo de otimização que equilibra velocidade (duração) e custo. Você pode testar manualmente as funções selecionando diferentes alocações de memória e medindo o tempo de conclusão. O AWS Lambda Power Tuning é uma ferramenta para simplificar e automatizar o processo, que você pode usar para otimizar sua configuração.

O Power Tuning usa o AWS Step Functions para executar várias versões simultâneas de uma função Lambda em diferentes alocações de memória e medir o desempenho. A função é executada em sua conta da AWS, realizando chamadas HTTP ao vivo e interações com SDK, para medir o desempenho em um cenário de produção.

Melhorando o tempo de inicialização a frio (cold-start) com o AWS Lambda SnapStart

Aplicações tradicionais geralmente têm uma grande árvore de dependências. O Lambda carrega o código da função e inicializa as dependências durante a fase de inicialização do ciclo de vida do Lambda. Com muitas dependências, esse tempo de inicialização pode ser muito longo para suas necessidades. O AWS Lambda SnapStart para funções baseadas em Java pode oferecer um desempenho de inicialização até 10 vezes mais rápido.

Em vez de executar a fase de inicialização da função em cada inicialização a frio (cold-start), o Lambda SnapStart executa o processo de inicialização da função no momento da implantação. O Lambda tira um snapshot do ambiente de execução inicializado. Esse snapshot é criptografado e mantido em cache para acesso de baixa latência. Quando a função é invocada e escalada, o Lambda retoma o ambiente de execução a partir do snapshot salvo em vez de executar o processo de inicialização completo. Isso resulta em menor latência de inicialização.

Para habilitar o Lambda SnapStart, você deve primeiro habilitar a definição de configuração e também publicar uma versão da função.

Habilitando o SnapStart

Certifique-se de apontar seu endpoint do API Gateway para a versão publicada ou um alias para garantir que você esteja usando a função com o SnapStart habilitado.

As configurações correspondentes em um modelo do AWS SAM contêm o seguinte:

SnapStart: 
  ApplyOn: PublishedVersions
AutoPublishAlias: my-function-alias

Leia as considerações de compatibilidade do Lambda SnapStart na documentação, pois sua aplicação pode conter um código específico que requer atenção.

Conclusão

Ao criar aplicações Serverless com o Lambda, você pode construir funcionalidades mais rapidamente, mas sua linguagem e runtime devem funcionar dentro do modelo arquitetônico Serverless. O AWS Serverless Java Container ajuda a estabelecer uma ponte entre aplicações corporativas Java tradicionais e funções Serverless modernas nativas da nuvem.

Você pode otimizar a configuração de memória da sua função Lambda em Java usando a ferramenta AWS Lambda Power Tuning e permitir que o SnapStart otimize o tempo de inicialização a frio (cold-start).

O workshop self-paced sobre Java no AWS Lambda mostra como criar aplicações Java nativas da nuvem e migrar aplicações Java existentes para o Lambda.

Explore o repositório GitHub do AWS Serverless Java Container, onde você pode relatar problemas e solicitar novos de recursos.

Para obter mais recursos de aprendizado Serverless, visite o Serverless Land.

Este blog em português é uma tradução do blog original em inglês (link aqui).

Biografia do autor

 Dennis Kieselhorst é arquiteto principal de soluções.

Biografia da tradutora

Bianca Mota é arquiteta de soluções para o segmento de startups e iniciou sua jornada na AWS em 2019 ajudando clientes em suas jornadas na nuvem. Além disso, Bianca é parte da comunidade Serverless na AWS e já apresentou sobre o assunto em diversos eventos, como o AWS Summit São Paulo e o AWS re:Invent.