O blog da AWS

O AWS Lambda agora oferece suporte ao Java 17

Este post foi escrito por Mark Sailes, arquiteto sênior de soluções especializado de Serverless.

Agora você pode desenvolver funções do AWS Lambda com a distribuição Amazon Corretto do Java 17. Essa versão do Corretto vem com suporte de longo prazo (LTS), o que significa que ela receberá atualizações e correções de bugs por um período prolongado, fornecendo estabilidade e confiabilidade aos desenvolvedores que criam aplicativos nela. Esse runtime também é compatível com o AWS Lambda SnapStart, para que você possa fazer o upgrade para o runtime gerenciado mais recente sem perder suas melhorias de desempenho.

O Java 17 vem com novos recursos de linguagem para desenvolvedores, incluindo java records, classes seladas e cadeias de caracteres de várias linhas. Ele também vem com melhorias para otimizar ainda mais a execução do Java em arquiteturas de CPU ARM, como o Graviton.

Este blog explica como começar a usar o Java 17 com o Lambda, como usar os novos recursos da linguagem e o que mais mudou com o runtime.

Novos recursos de linguagem

Em Java, é comum passar dados usando um objeto imutável. Antes do Java 17, isso resultava no código básico ou no uso de uma biblioteca externa como a Lombok. Por exemplo, um objeto Person genérico pode ter a seguinte aparência:

public class Person {
    
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        return Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

No Java 17, você pode substituir essa classe inteira por um registro, expresso como:

public record Person(String name, int age) {

}

Os métodos equals, hashCode e toString, bem como os campos privados, finais e o construtor público, são gerados pelo compilador Java. Isso simplifica o código que você precisa manter.

O runtime gerenciado do Java 17 introduz um novo recurso que permite que os desenvolvedores usem records como objeto para representar dados de eventos no método handler. Os records foram introduzidos no Java 14 e fornecem uma sintaxe mais simples para declarar classes usadas principalmente para armazenar dados. Os records permitem que os desenvolvedores definam uma classe imutável com um conjunto de propriedades e métodos nomeados para acessar essas propriedades, tornando-as perfeitas para dados de eventos. Esse recurso simplifica o código, facilitando a leitura e a manutenção. Além disso, ele pode fornecer melhor desempenho, pois os records são imutáveis por padrão e o runtime do Java pode otimizar a alocação de memória e o processo de garbage collection. Para usar records como parâmetro para o método handler de eventos, defina o registro com as propriedades necessárias e transmita o records para o método. A capacidade de usar records como objeto para representar dados de eventos no método manipulador é uma adição útil à linguagem Java, fornecendo uma maneira concisa e eficiente de definir estruturas de dados de eventos.

Por exemplo, a função Lambda a seguir usa um record de pessoa para representar os dados do evento:

public class App implements RequestHandler<Person, APIGatewayProxyResponseEvent> {

    public APIGatewayProxyResponseEvent handleRequest(Person person, Context context) {
        
        String id = UUID.randomUUID().toString();
        Optional<Person> savedPerson = createPerson(id, person.name(), person.age());
        if (savedPerson.isPresent()) {
            return new APIGatewayProxyResponseEvent().withStatusCode(200);
        } else {
            return new APIGatewayProxyResponseEvent().withStatusCode(500);
        }
    }

Garbage collection

O Java 17 disponibiliza dois novos garbage collectors (GCs) Java: Z Garbage collector (ZGC) introduzido no Java 15 e Shenandoah introduzido no Java 12.

Você pode avaliar os GCs em relação a três eixos:

  • Rendimento: a quantidade de trabalho que pode ser feito.
  • Latência: quanto tempo o trabalho leva para ser concluído.
  • Footprint de memória: quanta memória adicional é necessária.

Tanto o ZGC quanto o Shenandoah GCs negociam troughput e footprint para se concentrar na redução da latência sempre que possível. Eles realizam todo o trabalho custoso simultaneamente, sem interromper a execução dos encadeamentos do aplicativo por mais do que alguns milissegundos.

No runtime gerenciado do Java 17, o Lambda continua usando o Serial GC da mesma forma que usa no Java 11. Esse é um GC de baixo custo, adequado para máquinas com um único processador, o que geralmente acontece quando se usa funções Lambda.

Você pode alterar o GC padrão usando a variável de ambiente JAVA_TOOL_OPTIONS para uma alternativa, se necessário. Por exemplo, se você estava executando com mais memória e, portanto, com várias CPUs, considere o GC paralelo. Para usar isso, defina JAVA_TOOL_OPTIONS como -XX:+UseParallelGC.

Alterações na configuração da JVM em runtime

No runtime do Java 17, o sinalizador JVM para compilação em camadas agora está configurado para parar no nível 1 por padrão. Nas versões anteriores, você precisaria fazer isso definindo  JAVA_TOOL_OPTIONS como -XX:+UseParallelGC.

Isso é útil na maioria dos wokloads síncronos porque pode reduzir a latência de inicialização em até 60%. Para obter mais informações sobre como configurar a compilação em camadas, consulte “Otimizando o desempenho da função AWS Lambda para Java”.

Se você estiver executando um workload que processa um grande número de lotes, simula eventos ou qualquer outra ação altamente repetitiva, talvez descubra que isso retarda a duração da sua função. Um exemplo disso seriam as simulações de Monte Carlo. Para voltar às configurações anteriores, defina JAVA_TOOL_OPTIONS como -XX:-TieredCompilation.

Usando o Java 17 no Lambda

Console de gerenciamento da AWS

Para usar o runtime do Java 17 para desenvolver suas funções do Lambda, defina o valor do runtime como Java 17 ao criar ou atualizar uma função.

Para atualizar uma função Lambda existente para o Java 17, navegue até a função no console do Lambda e escolha Editar no painel de configurações do runtime. A nova versão está disponível no menu suspenso Runtime:

AWS Serverless Application Model (AWS SAM)

No AWS SAM, defina o atributo Runtime como java17 para usar esta versão:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Simple Lambda Function

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: HelloWorldFunction
      Handler: helloworld.App::handleRequest
      Runtime: java17
      MemorySize: 1024

O AWS SAM suporta a geração desse modelo (template) com o Java 17 pronto para uso para novos aplicativos serverless usando o comando sam init. Consulte a documentação do AWS SAM aqui.

Cloud Development Kit da AWS (AWS CDK)

No AWS CDK, defina o atributo de runtime como runtime.java_17 para usar essa versão. Em Java:

import software.amazon.awscdk.core.Construct;
import software.amazon.awscdk.core.Stack;
import software.amazon.awscdk.core.StackProps;
import software.amazon.awscdk.services.lambda.Code;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.Runtime;

public class InfrastructureStack extends Stack {

    public InfrastructureStack(final Construct parent, final String id, final StackProps props) {
        super(parent, id, props);

        Function.Builder.create(this, "HelloWorldFunction")
                .runtime(Runtime.JAVA_17)
                .code(Code.fromAsset("target/hello-world.jar"))
                .handler("helloworld.App::handleRequest")
                .memorySize(1024)
                .build();
    }
}

Frameworks de aplicativos

Os frameworks de aplicativos Java Spring e Micronaut anunciaram que suas versões mais recentes, Spring Boot 3 e Micronaut 4, exigem o Java 17 no mínimo. O Quarkus 3 continua oferecendo suporte ao Java 11. O Java 17 é mais rápido que o 8 ou 11, e os desenvolvedores de frameworks querem transmitir as melhorias de desempenho aos clientes. Eles também querem usar as melhorias da linguagem Java em seu próprio código e mostrar exemplos de código com as formas mais modernas de trabalhar.

Para testar o Micronaut 4 e o Java 17, você pode usar o launch web service do Micronaut para gerar um projeto de exemplo que inclui todo o código do aplicativo e a infraestrutura do AWS Cloud Development Kit (CDK) como código necessário para implantá-lo no Lambda.

O comando a seguir cria um aplicativo Micronaut, que usa o padrão  common controler para lidar com solicitações REST. O código de infraestrutura criará um Amazon API Gateway e enviará por proxy todas as suas solicitações para a função Lambda.

curl --location --request GET 'https://launch.micronaut.io/create/default/blog.example.lambda-java-17?lang=JAVA&build=MAVEN&test=JUNIT&javaVersion=JDK_17&features=amazon-api-gateway&features=aws-cdk&features=crac' --output lambda-java-17.zip

Descompacte o arquivo baixado e execute o seguinte comando do Maven para gerar o artefato implantável.

./mvnw package

Por fim, implante os recursos na AWS com o CDK:

cd infra
cdk deploy

Conclusão

Esta postagem descreve como criar uma nova função Lambda executando o runtime gerenciado do Amazon Corretto Java 17. Ele apresenta o novo recurso de linguagem de records para modelar o evento que está sendo enviado para sua função do Lambda e explica como as alterações na configuração padrão da JVM podem afetar o desempenho de suas funções.

Se você estiver interessado em saber mais, visite serverlessland.com. Se isso inspirou você a tentar migrar um aplicativo existente para o Lambda, leia nosso guia de reformulação de plataforma.

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

Biografia do Autor

Mark Sailes, Senior Specialist Solutions Architect

Biografia do Tradutor

Rodrigo Peres é Arquiteto de Soluções na AWS, com mais de 20 anos de experiência trabalhando com arquitetura de soluções, desenvolvimento de sistemas e modernização de sistemas legados.

Biografia do Revisor

Daniel Abib é arquiteto de soluções sênior na AWS, com mais de 25 anos trabalhando com gerenciamento de projetos, arquiteturas de soluções escaláveis, desenvolvimento de sistemas e CI/CD, microsserviços, arquitetura Serverless & Containers e segurança. Ele trabalha apoiando clientes corporativos, ajudando-os em sua jornada para a nuvem.

https://www.linkedin.com/in/danielabib/