Programar o DynamoDB com o AWS SDK for Java 2.x - Amazon DynamoDB

Programar o DynamoDB com o AWS SDK for Java 2.x

Este guia de programação fornece uma orientação para programadores que desejam usar o Amazon DynamoDB com Java. O guia aborda diversos conceitos, incluindo camadas de abstração, gerenciamento de configurações, tratamento de erros, controle de políticas de novas tentativas e gerenciamento de keep-alive.

Sobre o AWS SDK for Java 2.x

É possível acessar o DynamoDB pelo Java usando o AWS SDK for Java oficial. O SDK para Java tem duas versões: 1.x e 2.x. O fim do suporte para a versão 1.x foi anunciado em 12 de janeiro de 2024. Ele entrará no modo de manutenção em 31 de julho de 2024 e o fim do suporte está previsto para 31 de dezembro de 2025. Para novos desenvolvimentos, é altamente recomendável que você use 2.x, que foi lançado pela primeira vez em 2018. Este guia é voltado exclusivamente para a versão 2.x e se concentra somente nas partes do SDK relevantes para o DynamoDB.

Para obter informações sobre manutenção e suporte para os SDKs da AWS, consulte AWS SDK and Tools maintenance policy and AWS SDKs and Tools version support matrix no Guia de referência de SDKs e ferramentas da AWS.

O AWS SDK for Java 2.x é uma importante reescrita do código base da versão 1.x. O SDK para Java 2.x oferece suporte aos recursos Java modernos, como a E/S sem bloqueio introduzida no Java 8. O SDK para Java 2.x também adiciona suporte a implementações de clientes HTTP conectáveis para oferecer maior flexibilidade de conexão de rede e opções de configuração.

Uma alteração notável entre o SDK para Java 1.x e o SDK para Java 2.x é o uso de um novo nome de pacote. O SDK do Java 1.x usa o nome do pacote com.amazonaws, enquanto o SDK do Java 2.x usa software.amazon.awssdk. Da mesma forma, os artefatos do Maven do SDK para Java 1.x usam o groupId com.amazonaws, enquanto os artefatos do SDK para Java 2.x usam o groupId software.amazon.awssdk.

Importante

O AWS SDK for Java 1.x tem um pacote do DynamoDB chamado com.amazonaws.dynamodbv2. A descrição “v2" no nome do pacote não indica que é destinado ao Java 2 (J2SE). Em vez disso, “v2" indica que o pacote é compatível com a segunda versão da API de baixo nível do DynamoDB em vez da versão original da API de baixo nível.

Compatibilidade com versões do Java

O AWS SDK for Java 2.x é totalmente compatível com versões do Java de suporte de longo prazo (LTS).

Conceitos básicos da AWS SDK for Java 2.x

O tutorial a seguir mostra como usar o Apache Maven para definir dependências do SDK para Java 2.x. Este tutorial também mostra como escrever o código que se conecta ao DynamoDB para listar as tabelas disponíveis do DynamoDB. O tutorial deste guia é baseado no tutorial Get started with the AWS SDK for Java 2.x do Guia do desenvolvedor do AWS SDK for Java 2.x. Editamos esse tutorial para fazer chamadas para o DynamoDB e não para o Amazon S3.

Etapa 1: configurar para este tutorial

Antes de iniciar este tutorial, é necessário instalar o seguinte:

  • Permissão para acessar o DynamoDB.

  • Um ambiente de desenvolvimento Java configurado com acesso de autenticação única aos Serviços da AWS usando o Portal de acesso da AWS.

Para se preparar para este tutorial, siga as instruções em Setup overview no Guia do desenvolvedor do AWS SDK for Java 2.x. Depois de configurar seu ambiente de desenvolvimento com acesso de autenticação única para o SDK do Java e ter uma sessão ativa do portal de acesso da AWS, continue com a Etapa 2 deste tutorial.

Etapa 2: criar o projeto

Para criar o projeto para este tutorial, você executa um comando do Maven que solicita informações sobre como configurar o projeto. Depois que todas as informações forem inseridas e confirmadas, o Maven concluirá a construção do projeto criando um arquivo pom.xml e criará arquivos Java stub.

  1. Abra uma janela de terminal ou prompt de comando e navegue até um diretório de sua escolha, por exemplo, sua pasta Desktop ou Home.

  2. Insira o comando a seguir no terminal e pressione Enter.

    mvn archetype:generate \ -DarchetypeGroupId=software.amazon.awssdk \ -DarchetypeArtifactId=archetype-app-quickstart \ -DarchetypeVersion=2.22.0
  3. Para cada prompt, insira o valor listado na segunda coluna.

    Prompt Valor a informar
    Define value for property 'service': dynamodb
    Define value for property 'httpClient': apache-client
    Define value for property 'nativeImage': false
    Define value for property 'credentialProvider' identity-center
    Define value for property 'groupId': org.example
    Define value for property 'artifactId': getstarted
    Define value for property 'version' 1.0-SNAPSHOT: <Enter>
    Define value for property 'package' org.example: <Enter>
  4. Depois de inserir o último valor, o Maven lista as escolhas que você fez. Para confirmar, insira Y. Ou insira N e suas opções novamente.

O Maven cria uma pasta de projeto chamada getstarted com base no valor de artifactId que você informou. Dentro da pasta getstarted, encontre um arquivo chamado README.md que você possa revisar, um arquivo pom.xml e um diretório src.

O Maven cria a árvore de diretórios a seguir.

getstarted ├── README.md ├── pom.xml └── src ├── main │ ├── java │ │ └── org │ │ └── example │ │ ├── App.java │ │ ├── DependencyFactory.java │ │ └── Handler.java │ └── resources │ └── simplelogger.properties └── test └── java └── org └── example └── HandlerTest.java 10 directories, 7 files

O exemplo a seguir mostra o conteúdo do arquivo de projeto pom.xml.

A seção dependencyManagement contém uma dependência do AWS SDK for Java 2.x e a seção dependencies tem uma dependência do DynamoDB. A especificação dessas dependências força o Maven a incluir os arquivos .jar relevantes no caminho da classe Java. Por padrão, o SDK da AWS não inclui todas as classes para todos os Serviços da AWS. Para o DynamoDB, se você usar a interface de baixo nível, deverá ter uma dependência no artefato dynamodb. Se você usar a interface de alto nível, a dependência deverá estar no artefato dynamodb-enhanced. Se você não incluir as dependências relevantes, o código não será compilado. O projeto usa o Java 1.8 devido ao valor 1.8 nas propriedades maven.compiler.source e maven.compiler.target.

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>getstarted</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.shade.plugin.version>3.2.1</maven.shade.plugin.version> <maven.compiler.plugin.version>3.6.1</maven.compiler.plugin.version> <exec-maven-plugin.version>1.6.0</exec-maven-plugin.version> <aws.java.sdk.version>2.22.0</aws.java.sdk.version> <-------- SDK version picked up from archetype version. <slf4j.version>1.7.28</slf4j.version> <junit5.version>5.8.1</junit5.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>${aws.java.sdk.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>dynamodb</artifactId> <-------- DynamoDB dependency <exclusions> <exclusion> <groupId>software.amazon.awssdk</groupId> <artifactId>netty-nio-client</artifactId> </exclusion> <exclusion> <groupId>software.amazon.awssdk</groupId> <artifactId>apache-client</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>sso</artifactId> <-------- Required for identity center authentication. </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>ssooidc</artifactId> <-------- Required for identity center authentication. </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>apache-client</artifactId> <-------- HTTP client specified. <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>${slf4j.version}</version> </dependency> <!-- Needed to adapt Apache Commons Logging used by Apache HTTP Client to Slf4j to avoid ClassNotFoundException: org.apache.commons.logging.impl.LogFactoryImpl during runtime --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <!-- Test Dependencies --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>${junit5.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven.compiler.plugin.version}</version> </plugin> </plugins> </build> </project>

Etapa 3: escrever o código

O código a seguir mostra a classe App criada pelo Maven. O método main é o ponto de entrada no aplicativo, que cria uma instância da classe Handler e, em seguida, chama seu método sendRequest.

package org.example; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class App { private static final Logger logger = LoggerFactory.getLogger(App.class); public static void main(String... args) { logger.info("Application starts"); Handler handler = new Handler(); handler.sendRequest(); logger.info("Application ends"); } }

A classe DependencyFactory criada pelo Maven contém o método de fábrica dynamoDbClient, que cria e exibe uma instância de DynamoDbClient. A instância do DynamoDbClient usa uma instância do cliente HTTP baseado em Apache. Isso ocorre porque você especificou apache-client quando o Maven solicitou o cliente HTTP a ser usado.

O código a seguir mostra a classe DependencyFactory.

package org.example; import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; /** * The module containing all dependencies required by the {@link Handler}. */ public class DependencyFactory { private DependencyFactory() {} /** * @return an instance of DynamoDbClient */ public static DynamoDbClient dynamoDbClient() { return DynamoDbClient.builder() .httpClientBuilder(ApacheHttpClient.builder()) .build(); } }

A classe Handler contém a lógica principal do seu programa. Quando uma instância de Handler é criada na classe App, a classe DependencyFactory fornece o cliente de serviço do DynamoDbClient. O código usa a instância de DynamoDbClient para chamar o DynamoDB.

O Maven gera a seguinte classe Handler com um comentário TODO. A próxima etapa do tutorial substitui o comentário TODO pelo código.

package org.example; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; public class Handler { private final DynamoDbClient dynamoDbClient; public Handler() { dynamoDbClient = DependencyFactory.dynamoDbClient(); } public void sendRequest() { // TODO: invoking the API calls using dynamoDbClient. } }

Para preencher a lógica, substitua todo o conteúdo da classe Handler pelo código a seguir. O método sendRequest é preenchido e as importações necessárias são adicionadas.

O código a seguir usa a instância DynamoDbClient para recuperar uma lista das tabelas existentes. Se existirem tabelas para uma conta e Região da AWS, o código usará a instância de Logger para registrar em log os nomes dessas tabelas.

package org.example; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse; public class Handler { private final DynamoDbClient dynamoDbClient; public Handler() { dynamoDbClient = DependencyFactory.dynamoDbClient(); } public void sendRequest() { Logger logger = LoggerFactory.getLogger(Handler.class); logger.info("calling the DynamoDB API to get a list of existing tables"); ListTablesResponse response = dynamoDbClient.listTables(); if (!response.hasTableNames()) { logger.info("No existing tables found for the configured account & region"); } else { response.tableNames().forEach(tableName -> logger.info("Table: " + tableName)); } } }

Etapa 4: compilar e executar o aplicativo

Depois que o projeto for criado e contiver a classe Handler completa, crie e execute a aplicação.

  1. Garanta que você tenha uma sessão ativa do AWS IAM Identity Center. Para confirmar, execute o comando aws sts get-caller-identity da AWS Command Line Interface (AWS CLI) e verifique a resposta. Se você não tiver uma sessão ativa, consulte Sign in using the AWS CLI para obter instruções.

  2. Abra uma janela de terminal ou prompt de comando e navegue até o diretório getstarted do seu projeto.

  3. Para compilar seu projeto, execute o seguinte comando:

    mvn clean package
  4. Para executar a aplicação, execute o seguinte comando:

    mvn exec:java -Dexec.mainClass="org.example.App"

Depois de visualizar o arquivo, exclua o objeto e, em seguida, exclua o bucket.

Bem-sucedida

Se seu projeto Maven foi criado e executado sem erros, parabéns! Você compilou com sucesso sua primeira aplicação Java usando o SDK para Java 2.x.

Limpeza

Para limpar os recursos que foram criados durante este tutorial, exclua a pasta de projeto getstarted.

Analisar a documentação do AWS SDK for Java 2.x

O Guia do desenvolvedor do AWS SDK for Java 2.x abrange todos os aspectos do SDK em todos os Serviços da AWS. Recomendamos que você analise os seguintes tópicos:

  • Migrate from version 1.x to 2.x: inclui uma explicação detalhada das diferenças entre as versões 1.x e 2.x. Esse tópico também contém instruções sobre como usar as duas versões principais lado a lado.

  • DynamoDB guide for Java 2.x SDK: mostra como realizar operações básicas do DynamoDB, incluindo criação de tabelas e tratamento e recuperação de itens. Esses exemplos usam a interface de nível inferior. O Java tem várias interfaces, conforme explicado na seguinte seção: Interfaces compatíveis.

dica

Depois de analisar esses tópicos, adicione a Referência de API do AWS SDK for Java 2.x aos favoritos. Ela abrange todos os Serviços da AWS e recomendamos que você utilize-a como principal referência de API.

Interfaces compatíveis

O AWS SDK for Java 2.x oferece suporte às interfaces a seguir, dependendo do nível de abstração desejado.

Interface de nível inferior

A interface de nível inferior fornece um mapeamento individual para a API de serviço subjacente. Cada API do DynamoDB está disponível por meio dessa interface. Isso significa que a interface de baixo nível pode fornecer funcionalidade completa, mas geralmente é mais detalhada e complexa de usar. Por exemplo, você precisa usar as funções .s() para armazenar strings e as funções .n() para armazenar números. O exemplo a seguir de PutItem insere um item usando a interface de nível inferior.

import org.slf4j.*; import software.amazon.awssdk.http.crt.AwsCrtHttpClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.*; import java.util.Map; public class PutItem { // Create a DynamoDB client with the default settings connected to the DynamoDB // endpoint in the default region based on the default credentials provider chain. private static final DynamoDbClient DYNAMODB_CLIENT = DynamoDbClient.create(); private static final Logger LOGGER = LoggerFactory.getLogger(PutItem.class); private void putItem() { PutItemResponse response = DYNAMODB_CLIENT.putItem(PutItemRequest.builder() .item(Map.of( "pk", AttributeValue.builder().s("123").build(), "sk", AttributeValue.builder().s("cart#123").build(), "item_data", AttributeValue.builder().s("YourItemData").build(), "inventory", AttributeValue.builder().n("500").build() // ... more attributes ... )) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .tableName("YourTableName") .build()); LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)"); } }

Interfaces de alto nível

A interface de alto nível no AWS SDK for Java 2.x é chamada de cliente aprimorado do DynamoDB. Essa interface oferece uma experiência de criação de código mais idiomática.

O cliente aprimorado oferece uma forma de associar classes de dados do lado do cliente e tabelas do DynamoDB projetadas para armazenar esses dados. Basta definir as relações entre as tabelas e suas classes de modelo correspondentes no seu código. Depois, você pode recorrer ao SDK para gerenciar o tratamento do tipo de dados. Para obter mais informações sobre o cliente aprimorado, consulte DynamoDB enhanced client API no Guia do desenvolvedor do AWS SDK for Java 2.x.

O exemplo a seguir de PutItem usa a interface de alto nível. Neste exemplo, o DynamoDbBean denominado YourItem cria um TableSchema que permite seu uso direto como entrada para a chamada putItem().

import org.slf4j.*; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*; import software.amazon.awssdk.enhanced.dynamodb.model.*; import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; public class DynamoDbEnhancedClientPutItem { private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<YourItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromBean(YourItem.class)); private static final Logger LOGGER = LoggerFactory.getLogger(PutItem.class); private void putItem() { PutItemEnhancedResponse<YourItem> response = DYNAMODB_TABLE.putItemWithResponse(PutItemEnhancedRequest.builder(YourItem.class) .item(new YourItem("123", "cart#123", "YourItemData", 500)) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .build()); LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)"); } @DynamoDbBean public static class YourItem { public YourItem() {} public YourItem(String pk, String sk, String itemData, int inventory) { this.pk = pk; this.sk = sk; this.itemData = itemData; this.inventory = inventory; } private String pk; private String sk; private String itemData; private int inventory; @DynamoDbPartitionKey public void setPk(String pk) { this.pk = pk; } public String getPk() { return pk; } @DynamoDbSortKey public void setSk(String sk) { this.sk = sk; } public String getSk() { return sk; } public void setItemData(String itemData) { this.itemData = itemData; } public String getItemData() { return itemData; } public void setInventory(int inventory) { this.inventory = inventory; } public int getInventory() { return inventory; } } }

O AWS SDK for Java 1.x tem sua própria interface de alto nível, que geralmente é chamada pela sua classe principal DynamoDBMapper. O AWS SDK for Java 2.x é publicado em um pacote separado (e artefato do Maven) chamado software.amazon.awssdk.enhanced.dynamodb. O SDK do Java 2.x geralmente é chamado por sua classe principal DynamoDbEnhancedClient.

Interface de alto nível usando classes de dados imutáveis

O recurso de mapeamento da API do cliente aprimorado do DynamoDB funciona com classes de dados imutáveis. Uma classe imutável tem apenas getters e requer uma classe construtora que o SDK usa para criar instâncias da classe. A imutabilidade em Java é um estilo comumente utilizado que os desenvolvedores podem usar para criar classes sem efeitos secundários. Essas classes apresentam comportamento mais previsível em aplicações multithread complexas. Em vez de usar a anotação @DynamoDbBean conforme mostrado na High-level interface example, as classes imutáveis usam a anotação @DynamoDbImmutable, que utiliza a classe de construtor como sua entrada.

O exemplo a seguir usa a classe do construtor DynamoDbEnhancedClientImmutablePutItem como entrada para criar um esquema de tabela. Depois, o exemplo fornece o esquema como entrada para a chamada de API PutItem.

import org.slf4j.*; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.model.*; import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; public class DynamoDbEnhancedClientImmutablePutItem { private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<YourImmutableItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromImmutableClass(YourImmutableItem.class)); private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedClientImmutablePutItem.class); private void putItem() { PutItemEnhancedResponse<YourImmutableItem> response = DYNAMODB_TABLE.putItemWithResponse(PutItemEnhancedRequest.builder(YourImmutableItem.class) .item(YourImmutableItem.builder() .pk("123") .sk("cart#123") .itemData("YourItemData") .inventory(500) .build()) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .build()); LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)"); } }

O exemplo a seguir mostra a classe de dados imutáveis.

@DynamoDbImmutable(builder = YourImmutableItem.YourImmutableItemBuilder.class) class YourImmutableItem { private final String pk; private final String sk; private final String itemData; private final int inventory; public YourImmutableItem(YourImmutableItemBuilder builder) { this.pk = builder.pk; this.sk = builder.sk; this.itemData = builder.itemData; this.inventory = builder.inventory; } public static YourImmutableItemBuilder builder() { return new YourImmutableItemBuilder(); } @DynamoDbPartitionKey public String getPk() { return pk; } @DynamoDbSortKey public String getSk() { return sk; } public String getItemData() { return itemData; } public int getInventory() { return inventory; } static final class YourImmutableItemBuilder { private String pk; private String sk; private String itemData; private int inventory; private YourImmutableItemBuilder() {} public YourImmutableItemBuilder pk(String pk) { this.pk = pk; return this; } public YourImmutableItemBuilder sk(String sk) { this.sk = sk; return this; } public YourImmutableItemBuilder itemData(String itemData) { this.itemData = itemData; return this; } public YourImmutableItemBuilder inventory(int inventory) { this.inventory = inventory; return this; } public YourImmutableItem build() { return new YourImmutableItem(this); } } }

Interface de alto nível usando classes de dados imutáveis e bibliotecas de geração de código clichê de terceiros

As classes de dados imutáveis (mostradas no exemplo anterior) exigem código clichê. Por exemplo, as lógicas getter e setter nas classes de dados, além das classes Builder. Bibliotecas de terceiros, como Project Lombok, podem ajudar você a gerar esse tipo de código clichê. Reduzir a maior parte do código clichê ajuda a limitar a quantidade de código necessária para trabalhar com classes de dados imutáveis e com o SDK da AWS. Isso ocasiona ainda maior produtividade e legibilidade do código. Para obter mais informações, consulte Use third-party libraries, such as Lombok no Guia do desenvolvedor do AWS SDK for Java 2.x.

O exemplo a seguir demonstra como o Project Lombok simplifica o código necessário para usar a API do cliente aprimorado do DynamoDB.

import org.slf4j.*; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.model.*; import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; public class DynamoDbEnhancedClientImmutableLombokPutItem { private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<YourImmutableLombokItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromImmutableClass(YourImmutableLombokItem.class)); private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedClientImmutableLombokPutItem.class); private void putItem() { PutItemEnhancedResponse<YourImmutableLombokItem> response = DYNAMODB_TABLE.putItemWithResponse(PutItemEnhancedRequest.builder(YourImmutableLombokItem.class) .item(YourImmutableLombokItem.builder() .pk("123") .sk("cart#123") .itemData("YourItemData") .inventory(500) .build()) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .build()); LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)"); } }

O exemplo a seguir mostra o objeto de dados imutáveis da classe de dados imutáveis.

import lombok.*; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*; @Builder @DynamoDbImmutable(builder = YourImmutableLombokItem.YourImmutableLombokItemBuilder.class) @Value public class YourImmutableLombokItem { @Getter(onMethod_=@DynamoDbPartitionKey) String pk; @Getter(onMethod_=@DynamoDbSortKey) String sk; String itemData; int inventory; }

A classe YourImmutableLombokItem usa as seguintes anotações fornecidas pelo Project Lombok e pelo SDK da AWS:

  • @Builder: produz APIs de construtor complexas para classes de dados fornecidas pelo Project Lombok.

  • @DynamoDbImmutable: identifica a classe DynamoDbImmutable como uma anotação de entidade mapeável do DynamoDB fornecida pelo SDK da AWS.

  • @Value: a variante imutável de @Data. Todos os campos são transformados em privados e definitivos por padrão, e os setters não são gerados. O Projeto Lombok fornece essa anotação.

Interface Document

A interface Document do AWS SDK for Java 2.x evita a necessidade de especificar descritores de tipo de dados. Os tipos de dados estão implícitos pela semântica dos próprios dados. Essa interface Document é semelhante à interface Document do AWS SDK for Java 1.x, mas foi reformulada.

O Document interface example a seguir mostra a chamada PutItem expressa usando a interface Document. O exemplo também usa EnhancedDocument. Para executar comandos em uma tabela do DynamoDB usando a API de documentos aprimorada, primeiro é necessário associar a tabela ao esquema da tabela de documentos para criar um objeto de recurso DynamoDBTable. O construtor de esquema de tabela Document requer uma chave de índice primária e provedores de conversão de atributos.

É possível usar AttributeConverterProvider.defaultProvider() para converter atributos de documentos de tipos padrão. Você pode alterar o comportamento padrão geral com uma implementação AttributeConverterProvider personalizada. Você também pode alterar o conversor para um único atributo. O Guia de referência de SDKs e ferramentas da AWS fornece mais detalhes e exemplos sobre como usar conversores personalizados. O uso principal é para atributos de suas classes de domínio que não têm um conversor padrão disponível. Usando um conversor personalizado, é possível fornecer ao SDK as informações necessárias para gravação ou leitura no DynamoDB.

import org.slf4j.*; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument; import software.amazon.awssdk.enhanced.dynamodb.model.*; import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; public class DynamoDbEnhancedDocumentClientPutItem { private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<EnhancedDocument> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.documentSchemaBuilder() .addIndexPartitionKey(TableMetadata.primaryIndexName(),"pk", AttributeValueType.S) .addIndexSortKey(TableMetadata.primaryIndexName(), "sk", AttributeValueType.S) .attributeConverterProviders(AttributeConverterProvider.defaultProvider()) .build()); private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedDocumentClientPutItem.class); private void putItem() { PutItemEnhancedResponse<EnhancedDocument> response = DYNAMODB_TABLE.putItemWithResponse( PutItemEnhancedRequest.builder(EnhancedDocument.class) .item( EnhancedDocument.builder() .attributeConverterProviders(AttributeConverterProvider.defaultProvider()) .putString("pk", "123") .putString("sk", "cart#123") .putString("item_data", "YourItemData") .putNumber("inventory", 500) .build()) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .build()); LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)"); } }

Para converter documentos JSON de/em tipos de dados nativos do Amazon DynamoDB, você pode usar os seguintes métodos utilitários:

Comparar interfaces com um exemplo de Query

Esta seção mostra a mesma chamada Query expressa usando as várias interfaces. Para ajustar os resultados dessas consultas, observe o seguinte:

  • O DynamoDB aponta para um valor específico de chave de partição, portanto você deve especificar a chave de partição inteira.

  • Para que a consulta aponte apenas para os itens do carrinho, a chave de classificação tem uma expressão de condição de chave que usa begins_with.

  • Usamos limit() para limitar a consulta a um máximo de cem itens devolvidos.

  • Definimos o scanIndexForward como falso. Os resultados são exibidos na ordem de bytes UTF-8, o que geralmente significa que o item de carrinho com o menor número é exibido primeiro. Ao definir scanIndexForward como falso, revertemos a ordem, e o item do carrinho com o maior número é exibido primeiro.

  • Aplicamos um filtro para remover qualquer resultado que não corresponda aos critérios. Os dados que estão sendo filtrados consomem capacidade de leitura, independentemente de o item corresponder ou não ao filtro.

exemplo Query usando a interface de baixo nível

O exemplo a seguir consulta uma tabela chamada YourTableName usando um keyConditionExpression. Isso limita a consulta a um valor de chave de partição específico e a um valor de chave de classificação que começa com um valor de prefixo específico. Essas condições de chave limitam a quantidade de dados lidos do DynamoDB. Por fim, a consulta aplica um filtro aos dados recuperados do DynamoDB usando uma filterExpression.

import org.slf4j.*; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.*; import java.util.Map; public class Query { // Create a DynamoDB client with the default settings connected to the DynamoDB // endpoint in the default region based on the default credentials provider chain. private static final DynamoDbClient DYNAMODB_CLIENT = DynamoDbClient.builder().build(); private static final Logger LOGGER = LoggerFactory.getLogger(Query.class); private static void query() { QueryResponse response = DYNAMODB_CLIENT.query(QueryRequest.builder() .expressionAttributeNames(Map.of("#name", "name")) .expressionAttributeValues(Map.of( ":pk_val", AttributeValue.fromS("id#1"), ":sk_val", AttributeValue.fromS("cart#"), ":name_val", AttributeValue.fromS("SomeName"))) .filterExpression("#name = :name_val") .keyConditionExpression("pk = :pk_val AND begins_with(sk, :sk_val)") .limit(100) .scanIndexForward(false) .tableName("YourTableName") .build()); LOGGER.info("nr of items: " + response.count()); LOGGER.info("First item pk: " + response.items().get(0).get("pk")); LOGGER.info("First item sk: " + response.items().get(0).get("sk")); } }
exemplo Query usando a interface Document

O exemplo a seguir consulta uma tabela chamada YourTableName usando a interface Document.

import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument; import software.amazon.awssdk.enhanced.dynamodb.model.*; import java.util.Map; public class DynamoDbEnhancedDocumentClientQuery { // Create a DynamoDB client with the default settings connected to the DynamoDB // endpoint in the default region based on the default credentials provider chain. private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<EnhancedDocument> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.documentSchemaBuilder() .addIndexPartitionKey(TableMetadata.primaryIndexName(),"pk", AttributeValueType.S) .addIndexSortKey(TableMetadata.primaryIndexName(), "sk", AttributeValueType.S) .attributeConverterProviders(AttributeConverterProvider.defaultProvider()) .build()); private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedDocumentClientQuery.class); private void query() { PageIterable<EnhancedDocument> response = DYNAMODB_TABLE.query(QueryEnhancedRequest.builder() .filterExpression(Expression.builder() .expression("#name = :name_val") .expressionNames(Map.of("#name", "name")) .expressionValues(Map.of(":name_val", AttributeValue.fromS("SomeName"))) .build()) .limit(100) .queryConditional(QueryConditional.sortBeginsWith(Key.builder() .partitionValue("id#1") .sortValue("cart#") .build())) .scanIndexForward(false) .build()); LOGGER.info("nr of items: " + response.items().stream().count()); LOGGER.info("First item pk: " + response.items().iterator().next().getString("pk")); LOGGER.info("First item sk: " + response.items().iterator().next().getString("sk")); } }
exemplo Query usando a interface de alto nível

O exemplo a seguir consulta uma tabela chamada YourTableName usando a API do cliente aprimorado do DynamoDB.

import org.slf4j.*; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*; import software.amazon.awssdk.enhanced.dynamodb.model.*; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import java.util.Map; public class DynamoDbEnhancedClientQuery { private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<YourItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromBean(DynamoDbEnhancedClientQuery.YourItem.class)); private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedClientQuery.class); private void query() { PageIterable<YourItem> response = DYNAMODB_TABLE.query(QueryEnhancedRequest.builder() .filterExpression(Expression.builder() .expression("#name = :name_val") .expressionNames(Map.of("#name", "name")) .expressionValues(Map.of(":name_val", AttributeValue.fromS("SomeName"))) .build()) .limit(100) .queryConditional(QueryConditional.sortBeginsWith(Key.builder() .partitionValue("id#1") .sortValue("cart#") .build())) .scanIndexForward(false) .build()); LOGGER.info("nr of items: " + response.items().stream().count()); LOGGER.info("First item pk: " + response.items().iterator().next().getPk()); LOGGER.info("First item sk: " + response.items().iterator().next().getSk()); } @DynamoDbBean public static class YourItem { public YourItem() {} public YourItem(String pk, String sk, String name) { this.pk = pk; this.sk = sk; this.name = name; } private String pk; private String sk; private String name; @DynamoDbPartitionKey public void setPk(String pk) { this.pk = pk; } public String getPk() { return pk; } @DynamoDbSortKey public void setSk(String sk) { this.sk = sk; } public String getSk() { return sk; } public void setName(String name) { this.name = name; } public String getName() { return name; } } }
Interface de alto nível usando classes de dados imutáveis

Quando você executa uma Query com as classes de dados imutáveis de alto nível, o código é o mesmo do exemplo de interface de alto nível, exceto pela construção da classe de entidade YourItem ou YourImmutableItem. Para ter mais informações, consulte o exemplo PutItem.

Interface de alto nível usando classes de dados imutáveis e bibliotecas de geração de código clichê de terceiros

Quando você executa uma Query com as classes de dados imutáveis de alto nível, o código é o mesmo do exemplo de interface de alto nível, exceto pela construção da classe de entidade YourItem ou YourImmutableLombokItem. Para ter mais informações, consulte o exemplo PutItem.

Exemplos de código adicionais

Para conferir exemplos adicionais de como usar o DynamoDB com o SDK para Java 2.x, consulte os seguintes repositórios de exemplos de código:

Programação síncrona e assíncrona

O AWS SDK for Java 2.x fornece clientes síncronos e assíncronos para Serviços da AWS, como o DynamoDB.

As classes DynamoDbClient e DynamoDbEnhancedClient fornecem métodos síncronos que bloqueiam a execução do seu thread até o cliente receber uma resposta do serviço. Esse cliente é a maneira mais simples de interagir com o DynamoDB se você não precisar de operações assíncronas.

As classes DynamoDbAsyncClient e DynamoDbEnhancedAsyncClient fornecem métodos assíncronos que são exibidos imediatamente e devolvem o controle ao thread de chamada sem aguardar uma resposta. O cliente sem bloqueio tem a vantagem de permitir alta simultaneidade em alguns segmentos, o que oferece tratamento eficiente de solicitações de E/S com recursos computacionais mínimos. Isso melhora o throughput e a capacidade de resposta.

O AWS SDK for Java 2.x usa o suporte nativo para E/S sem bloqueio. O AWS SDK for Java 1.x precisou simular E/S sem bloqueio.

Os métodos síncronos são exibidos antes que uma resposta esteja disponível, portanto você precisará de uma maneira de receber a resposta quando ela estiver pronta. Os métodos assíncronos no AWS SDK for Java exibem um objeto CompletableFuture que contém os resultados da operação assíncrona no futuro. Ao chamar get() ou join() nesses objetos CompletableFuture, o código fica bloqueado até que o resultado esteja disponível. Se você fizer essas duas chamadas ao mesmo tempo em que faz a solicitação, o comportamento será semelhante a uma chamada síncrona simples.

Para obter mais informações sobre programação assíncrona, consulte Use asynchronous programming no Guia do desenvolvedor do AWS SDK for Java 2.x.

Clientes HTTP

Para comportar cada cliente, existe um cliente HTTP que lida com a comunicação com os Serviços da AWS. É possível conectar clientes HTTP alternativos, escolhendo um que tenha as características mais adequadas à aplicação. Alguns são mais leves; alguns têm mais opções de configuração.

Alguns clientes HTTP comportam somente o uso síncrono, enquanto outros aceitam apenas o uso assíncrono. Para conferir um fluxograma que pode ajudar você a selecionar o cliente HTTP ideal para sua workload, consulte HTTP client recommendations no Guia do desenvolvedor do AWS SDK for Java 2.x.

A lista a seguir apresenta alguns dos possíveis clientes HTTP:

Cliente HTTP baseado em Apache

A classe ApacheHttpClient oferece suporte a clientes de serviço síncronos. É o cliente HTTP padrão para uso síncrono. Para obter informações sobre como configurar a classe ApacheHttpClient, consulte Configure the Apache-based HTTP client no Guia do desenvolvedor do AWS SDK for Java 2.x.

Cliente HTTP baseado em URLConnection

A classe UrlConnectionHttpClient é outra opção para clientes síncronos. Ela é carregada mais rapidamente do que o cliente HTTP baseado em Apache, mas tem menos recursos. Para obter informações sobre como configurar a classe UrlConnectionHttpClient, consulte Configure the URLConnection-based HTTP client no Guia do desenvolvedor do AWS SDK for Java 2.x.

Cliente HTTP baseado em Netty

A classe NettyNioAsyncHttpClient oferece suporte a clientes assíncronos. É a opção padrão para uso assíncrono. Para obter informações sobre como configurar a classe NettyNioAsyncHttpClient, consulte Configure the Netty-based HTTP client no Guia do desenvolvedor do AWS SDK for Java 2.x.

Cliente HTTP baseado em AWS CRT

As classes AwsCrtHttpClient e AwsCrtAsyncHttpClient mais recentes das bibliotecas AWS Common Runtime (CRT) são outra opção compatível com clientes síncronos e assíncronos. Em comparação com outros clientes HTTP, o AWS CRT oferece:

  • Menor tempo de inicialização do SDK

  • Menor espaço ocupado na memória

  • Tempo de latência reduzido

  • Gerenciamento de integridade da conexão

  • balanceamento de carga do DNS

Para obter informações sobre como configurar as classes AwsCrtHttpClient e AwsCrtAsyncHttpClient, consulte Configure the AWS CRT-based HTTP clients no Guia do desenvolvedor do AWS SDK for Java 2.x.

O cliente HTTP baseado em AWS CRT não é o padrão porque isso romperia a compatibilidade com versões anteriores das aplicações existentes. No entanto, para o DynamoDB, recomendamos que você use o cliente HTTP baseado em AWS CRT para uso sincronizado e assíncrono.

Para conferir uma introdução sobre o cliente HTTP baseado em AWS CRT, consulte Announcing availability of the AWS CRT HTTP Client in the AWS SDK for Java 2.x no blog de ferramentas do desenvolvedor da AWS.

Configurar um cliente HTTP

Ao configurar um cliente, é possível fornecer várias opções de configuração, incluindo:

  • Definir tempos limite para diferentes aspectos das chamadas de API.

  • Habilitar keep-alive de TCP.

  • Controlar a política de novas tentativas ao encontrar erros.

  • Especificar atributos de execução que as instâncias do interceptor de execução podem modificar. Os interceptores de execução podem escrever código que intercepte a execução de suas solicitações e respostas de API. Isso possibilita executar tarefas, como publicar métricas e modificar solicitações em andamento.

  • Adicionar ou manipular cabeçalhos HTTP.

  • Permitir o rastreamento das métricas de performance do lado do cliente. Usar esse recurso ajuda você a coletar métricas sobre os clientes de serviço em sua aplicação e analisar a saída no Amazon CloudWatch.

  • Especificar um serviço executor alternativo a ser usado para agendar tarefas, como novas tentativas assíncronas e tarefas de tempo limite.

Você controla a configuração fornecendo um objeto ClientOverrideConfiguration para a classe Builder do cliente de serviço. Você verá isso em alguns exemplos de código nas seções a seguir.

A ClientOverrideConfiguration fornece opções de configuração padrão. Os diferentes clientes HTTP conectáveis também têm possibilidades de configuração específicas de implementação.

Configuração de tempo limite

É possível ajustar a configuração do cliente para controlar vários tempos limite relacionados às chamadas de serviço. O DynamoDB fornece latências mais baixas em comparação com outros Serviços da AWS. Portanto, talvez você queira ajustar essas propriedades a fim de reduzir os valores de tempo limite para que possa antecipar-se à falha em caso de problema na rede.

É possível personalizar o comportamento relacionado à latência usando ClientOverrideConfiguration no cliente do DynamoDB ou alterando as opções de configuração detalhadas na implementação do cliente HTTP subjacente.

É possível configurar as seguintes propriedades impactantes usando ClientOverrideConfiguration:

  • apiCallAttemptTimeout: o tempo de espera até que uma única tentativa de solicitação HTTP seja concluída antes de desistir e atingir o tempo limite.

  • apiCallTimeout: o tempo que o cliente tem para executar completamente uma chamada de API. Isso inclui a execução do manipulador de solicitações, que consiste em todas as solicitações HTTP, incluindo novas tentativas.

O AWS SDK for Java 2.x fornece valores padrão para algumas opções de tempo limite, como tempo limite de conexão e tempo limite de soquete. O SDK não fornece valores padrão para tempo limite de chamadas de API nem tempos limite de chamadas de API individuais. Se esses tempos limite não estiverem definidos em ClientOverrideConfiguration, o SDK usará o valor do tempo limite do soquete para o tempo limite geral da chamada de API. O tempo limite do soquete tem um valor padrão de trinta minutos.

RetryMode

Outra configuração relacionada à configuração de tempo limite que deve ser considerada é o objeto de configuração RetryMode. Esse objeto de configuração contém uma coleção de comportamentos de novas tentativas.

O SDK para Java 2.x é compatível com os seguintes modos de novas tentativas:

  • legacy: o modo de novas tentativas padrão, se você não o alterar explicitamente. Esse modo de novas tentativas é específico do SDK para Java. Ele é caracterizado por até três novas tentativas, ou mais para serviços, como o DynamoDB que tem até oito novas tentativas.

  • standard: é chamado de “standard” porque é mais consistente com outros SDKs da AWS. Esse modo espera por um período aleatório que varia de 0 ms a 1.000 ms pela primeira tentativa. Se outra tentativa for necessária, esse modo escolherá outro tempo aleatório entre 0 ms e 1.000 ms, e o multiplicará por dois. Se for necessária uma nova tentativa, ele fará a mesma escolha aleatória multiplicada por quatro e assim por diante. Cada espera é limitada a 20 segundos. Esse modo executa novas tentativas em mais condições de falha detectadas do que o modo legacy. Para o DynamoDB, ele executa até três tentativas no total, a menos que você substitua por numRetries.

  • adaptive: baseia-se no modo standard e limita dinamicamente a taxa de solicitações da AWS para maximizar a taxa de êxito. Isso pode ocorrer em detrimento da latência da solicitação. Não recomendamos o modo de novas tentativas adaptável quando a latência previsível for importante.

É possível encontrar uma definição expandida desses modos de novas tentativas no tópico Retry behavior no Guia de referência de SDKs e ferramentas da AWS.

Política de novas tentativas

Todas as configurações de RetryMode têm uma RetryPolicy, que é criada com base em uma ou mais configurações de RetryCondition. TokenBucketRetryCondition é especialmente importante para o comportamento de novas tentativas da implementação do cliente do SDK do DynamoDB. Essa condição limita o número de novas tentativas que o SDK faz usando um algoritmo de bucket de token. Dependendo do modo de novas tentativas selecionado, as exceções de controle de utilização podem ou não subtrair tokens do TokenBucket.

Quando um cliente encontra um erro que pode ser repetido, como uma exceção de controle de utilização ou um erro temporário do servidor, o SDK repete automaticamente a solicitação. É possível controlar quantas vezes e com que rapidez essas novas tentativas acontecem.

Ao configurar um cliente, é possível fornecer uma RetryPolicy compatível com os seguintes parâmetros:

  • numRetries: o número máximo de novas tentativas que devem ser aplicadas antes que uma solicitação seja considerada com falha. O valor padrão é 8, independentemente do modo de novas tentativas usado.

    Atenção

    Certifique-se de alterar esse valor padrão após a devida consideração.

  • backoffStrategy: a BackoffStrategy a ser aplicada às novas tentativas, em que FullJitterBackoffStrategy é a estratégia padrão. Essa estratégia realiza um atraso exponencial entre novas tentativas com base no número ou nas novas tentativas atuais, um atraso base e um tempo máximo de recuo. Depois, adiciona instabilidade para fornecer um pouco de aleatoriedade. O atraso básico usado no atraso exponencial é de 25 ms, independentemente do modo de novas tentativas.

  • retryCondition: a RetryCondition determina se uma solicitação deve ou não ser repetida. Por padrão, ela tenta novamente um conjunto específico de códigos de status HTTP e exceções que acredita serem passíveis de nova tentativa. Para a maioria das situações, a configuração padrão deve ser suficiente.

O código a seguir fornece uma política de novas tentativas alternativa. Ele especifica um total de cinco novas tentativas (seis solicitações no total). A primeira nova tentativa deve ocorrer após um atraso de aproximadamente 100 ms, com cada nova tentativa adicional dobrando esse tempo exponencialmente, até, no máximo, um atraso de 1 segundo.

DynamoDbClient client = DynamoDbClient.builder() .overrideConfiguration(ClientOverrideConfiguration.builder() .retryPolicy(RetryPolicy.builder() .backoffStrategy(FullJitterBackoffStrategy.builder() .baseDelay(Duration.ofMillis(100)) .maxBackoffTime(Duration.ofSeconds(1)) .build()) .numRetries(5) .build()) .build()) .build();

DefaultsMode

As propriedades de tempo limite que ClientOverrideConfiguration e RetryMode não gerenciam são normalmente configuradas implicitamente especificando um DefaultsMode.

O AWS SDK for Java 2.x (versão 2.17.102 ou posterior) introduziu suporte a DefaultsMode. Esse recurso oferece um conjunto de valores padrão para configurações comuns, como configurações de comunicação HTTP, comportamento de novas tentativas, configurações de endpoint regional do serviço e, potencialmente, qualquer configuração relacionada ao SDK. Os clientes que usam esse recurso podem receber novos padrões de configuração personalizados para cenários de uso comuns.

Os modos padrão são padronizados em todos os SDKs da AWS. O SDK para Java 2.x é compatível com os seguintes modos padrão:

  • legacy: fornece configurações padrão que variam de acordo com o SDK da AWS e que existiam antes do estabelecimento de DefaultsMode.

  • standard: fornece configurações padrão não otimizadas para a maioria dos cenários.

  • in-region: baseia-se no modo padrão e inclui configurações personalizadas para aplicações que chamam Serviços da AWS de dentro da mesma Região da AWS.

  • cross-region: baseia-se no modo padrão e inclui configurações com altos tempos limite para aplicações que chamam Serviços da AWS em uma região diferente.

  • mobile: baseia-se no modo padrão e inclui configurações com tempo limite alto, personalizadas para aplicações para dispositivos móveis com latências mais altas.

  • auto: baseia-se no modo padrão e inclui atributos experimentais. O SDK tenta descobrir o ambiente de runtime para determinar automaticamente as configurações apropriadas. A detecção automática é baseada em heurísticas e não fornece 100% de precisão. Se o ambiente de runtime não puder ser determinado, o modo padrão será usado. A detecção automática pode consultar metadados da instância e dados do usuário, o que pode introduzir latência. Se a latência de inicialização for fundamental para seu aplicativo, recomendamos escolher um DefaultsMode explícito.

É possível configurar o modo padrão das seguintes maneiras:

  • Diretamente em um cliente por meio de AwsClientBuilder.Builder#defaultsMode(DefaultsMode).

  • Em um perfil de configuração por meio da propriedade do arquivo de perfil defaults_mode.

  • Globalmente por meio da propriedade do sistema aws.defaultsMode.

  • Globalmente por meio da variável de ambiente AWS_DEFAULTS_MODE.

nota

Para qualquer modo que não seja legacy, os valores padrão fornecidos podem mudar à medida que as práticas recomendadas evoluem. Portanto, se estiver usando um modo diferente de legacy, recomendamos que você realize testes ao atualizar o SDK.

A seção Smart configuration defaults no Guia de referência de SDKs e ferramentas da AWS fornece uma lista de propriedades de configuração e seus valores padrão nos diferentes modos padrão.

Escolha o valor de modo padrão com base nas características da aplicação e no Serviço da AWS com o qual a aplicação interage.

Esses valores são configurados com uma ampla seleção de Serviços da AWS em mente. Para uma implantação típica do DynamoDB em que a aplicação e as tabelas do DynamoDB são implantadas na mesma região, o modo in-region padrão é mais relevante entre os modos padrão standard.

exemplo Configuração do cliente do SDK do DynamoDB ajustada para chamadas de baixa latência

O exemplo a seguir ajusta os tempos limite para valores mais baixos para uma chamada esperada do DynamoDB de baixa latência.

DynamoDbAsyncClient asyncClient = DynamoDbAsyncClient.builder() .defaultsMode(DefaultsMode.IN_REGION) .httpClientBuilder(AwsCrtAsyncHttpClient.builder()) .overrideConfiguration(ClientOverrideConfiguration.builder() .apiCallTimeout(Duration.ofSeconds(3)) .apiCallAttemptTimeout(Duration.ofMillis(500)) .build()) .build();

A implementação do cliente HTTP individual pode fornecer a você um controle ainda mais granular sobre o tempo limite e o comportamento de uso da conexão. Por exemplo, para o cliente baseado em AWS CRT, é possível habilitar ConnectionHealthConfiguration, que permite que o cliente monitore ativamente a integridade das conexões usadas. Para obter mais informações, consulte Advanced configuration of AWS CRT-based HTTP clients no Guia do desenvolvedor do AWS SDK for Java 2.x.

Configuração de keep-alive

Habilitar o keep-alive pode reduzir as latências ao reutilizar conexões. Há dois tipos diferentes de keep-alive: HTTP Keep-Alive e TCP Keep-Alive.

  • O HTTP Keep-Alive tenta manter a conexão HTTPS entre o cliente e o servidor para que solicitações posteriores possam reutilizar essa conexão. Isso ignora a pesada autenticação HTTPS em solicitações posteriores. O HTTP Keep-Alive está habilitado por padrão em todos os clientes.

  • O keep-alive de TCP solicita que o sistema operacional subjacente envie pequenos pacotes pela conexão do soquete a fim de fornecer garantia extra de que o soquete seja mantido ativo e para detectar imediatamente qualquer queda. Isso garante que não haja perda de tempo em uma solicitação posterior com a tentativa de usar um soquete descartado. Por padrão, o keep-alive de TCP está desabilitado em todos os clientes. Os exemplos de código a seguir mostram como habilitar essa opção em cada cliente HTTP. Quando habilitado para todos os clientes HTTP não baseados em CRT, o mecanismo real de keep-alive depende do sistema operacional. Portanto, é necessário configurar valores adicionais de TCP Keep-Alive, como tempo limite e número de pacotes, por meio do sistema operacional. É possível fazer isso usando sysctl no Linux ou macOS, ou usando valores de registro no Windows.

exemplo para habilitar o TCP Keep-Alive em um cliente HTTP baseado em Apache
DynamoDbClient client = DynamoDbClient.builder() .httpClientBuilder(ApacheHttpClient.builder().tcpKeepAlive(true)) .build();
Cliente HTTP baseado em URLConnection

Nenhum cliente síncrono que usa o cliente HTTP baseado em URLConnection HttpURLConnection tem um mecanismo para habilitar o keep-alive.

exemplo para habilitar o TCP Keep-Alive em um cliente HTTP baseado em Apache
DynamoDbAsyncClient client = DynamoDbAsyncClient.builder() .httpClientBuilder(NettyNioAsyncHttpClient.builder().tcpKeepAlive(true)) .build();
exemplo para habilitar o TCP Keep-Alive em um cliente HTTP baseado em AWS CRT

Com o cliente HTTP baseado em AWS CRT, é possível habilitar o TCP Keep-Alive e controlar a duração.

DynamoDbClient client = DynamoDbClient.builder() .httpClientBuilder(AwsCrtHttpClient.builder() .tcpKeepAliveConfiguration(TcpKeepAliveConfiguration.builder() .keepAliveInterval(Duration.ofSeconds(50)) .keepAliveTimeout(Duration.ofSeconds(5)) .build())) .build();

Ao usar o cliente assíncrono do DynamoDB, é possível habilitar o TCP Keep-Alive conforme mostrado no código a seguir.

DynamoDbAsyncClient client = DynamoDbAsyncClient.builder() .httpClientBuilder(AwsCrtAsyncHttpClient.builder() .tcpKeepAliveConfiguration(TcpKeepAliveConfiguration.builder() .keepAliveInterval(Duration.ofSeconds(50)) .keepAliveTimeout(Duration.ofSeconds(5)) .build())) .build();

Tratamento de erros

No que se refere ao tratamento de exceções, o AWS SDK for Java 2.x usa exceções de tempo de execução (não conferidas).

A exceção básica, abrangendo todas as exceções do SDK, é SdkServiceException, que se estende da RuntimeException não conferida do Java. Se você captar isso, captará todas as exceções geradas pelo SDK.

SdkServiceException tem uma subclasse chamada AwsServiceException. Essa subclasse indica qualquer problema na comunicação com o Serviço da AWS. Ela tem uma subclasse chamada DynamoDbException, que indica um problema na comunicação com o DynamoDB. Se você captar isso, captará todas as exceções relacionadas ao DynamoDB, mas nenhuma outra exceção do SDK.

tipos de exceção mais específicos em DynamoDbException. Alguns desses tipos de exceção se aplicam às operações do ambiente de gerenciamento, como TableAlreadyExistsException. Outros se aplicam às operações do plano de dados. Veja a seguir um exemplo de exceção comum do plano de dados:

  • ConditionalCheckFailedException: você especificou uma condição na solicitação que foi avaliada como falsa. Por exemplo, você pode ter tentado realizar uma atualização condicional em um item, mas o valor real do atributo não correspondeu ao valor esperado na condição. Uma solicitação que falhe dessa maneira não será repetida.

Outras situações não têm uma exceção específica definida. Por exemplo, quando suas solicitações têm controle de utilização, a ProvisionedThroughputExceededException específica pode ser gerada, enquanto, em outros casos, DynamoDbExceptionmais genérica é lançada. Nos dois casos, é possível determinar se a exceção foi causada pelo controle de utilização, conferindo se isThrottlingException() retorna true.

Dependendo das necessidades da aplicação, é possível capturar todas as instâncias AwsServiceException ou DynamoDbException. No entanto, muitas vezes você precisa de um comportamento diferente em outras situações. A lógica para lidar com uma falha na verificação de condições é diferente quando se trata de lidar com o controle de utilização. Defina com quais caminhos excepcionais você deseja lidar e não se esqueça de testar os caminhos alternativos. Isso ajuda a garantir que você seja capaz de lidar com todos os cenários relevantes.

Para obter uma lista de erros comuns que você pode encontrar, consulte Tratamento de erros com o DynamoDB. Consulte também Common Errors na Referência de API do Amazon DynamoDB. A Referência de API também apresenta os erros exatos possíveis para cada operação de API, como a operação Query. Para obter informações sobre como lidar com exceções, consulte Exception handling for the AWS SDK for Java 2.x no Guia do desenvolvedor do AWS SDK for Java 2.x.

ID da solicitação da AWS

Cada solicitação inclui um ID de solicitação cuja extração pode ser útil se você estiver trabalhando com AWS Support para diagnosticar um problema. Cada exceção derivada de SdkServiceException tem um método requestId() disponível para recuperar o ID da solicitação.

Registro em log

Usar o registro em log fornecido pelo SDK pode ser útil tanto para capturar mensagens importantes das bibliotecas do cliente quanto para fins de depuração mais detalhada. Os loggers são hierárquicos e o SDK usa software.amazon.awssdk como logger raiz. Você pode configurar o nível com um dos seguintes: TRACE, DEBUG, INFO, WARN, ERROR, ALL ou OFF. O nível configurado é aplicado a esse logger e à hierarquia de loggers.

Para o registro em log, o AWS SDK for Java 2.x usa o Simple Logging Façade for Java (SLF4J). Isso atua como uma camada de abstração em torno de outros loggers, que pode ser usada para conectar o logger de sua preferência. Para ter instruções sobre como conectar loggers, consulte o Manual do usuário do SLF4J.

Cada logger tem um comportamento específico. Por padrão, o logger Log4j 2.x cria um ConsoleAppender, que anexa eventos de log a System.out e assume o nível de log ERROR como padrão.

O logger SimpleLogger incluído no SLF4J, por padrão, emite por padrão System.err e utiliza como padrão o nível de log INFO.

Recomendamos definir o nível como WARN para software.amazon.awssdk para que todas as implantações em produção capturem todas as mensagens importantes das bibliotecas de cliente do SDK e limitem a quantidade de saída.

Se o SLF4J não conseguir encontrar um logger compatível no caminho da classe (sem vinculação ao SLF4J), ele assumirá como padrão uma implementação sem operação. Essa implementação ocasiona o registro em log de mensagens em System.err explicando que o SLF4J não conseguiu encontrar uma implementação de logger no caminho de classe. Para evitar essa situação, é necessário adicionar uma implementação de logger. Para fazer isso, é possível incluir uma dependência no Apache Maven pom.xml em artefatos, como org.slf4j.slf4j-simple ou org.apache.logging.log4j.log4j-slf4j2-imp.

Para obter informações sobre como configurar o registro em log no SDK, incluindo a adição de dependências de registro em log à configuração da aplicação, consulte Logging with the SDK for Java 2.x no Guia do desenvolvedor do AWS SDK for Java.

A configuração a seguir no arquivo Log4j2.xml mostra como ajustar o comportamento de registro em log se você usar o logger Apache Log4j 2. Essa configuração define o nível do logger raiz como WARN. Todos os loggers na hierarquia herdam esse nível de registro, incluindo o logger software.amazon.awssdk.

Por padrão, a saída é enviada para System.out. No exemplo a seguir, ainda substituímos o anexador Log4j de saída padrão para aplicar um Log4j personalizado PatternLayout.

Exemplo de arquivo de configuração do Log4j2.xml

A configuração a seguir registra mensagens em log nos níveis ERROR e WARN no console para todas as hierarquias de logger.

<Configuration status="WARN"> <Appenders> <Console name="ConsoleAppender" target="SYSTEM_OUT"> <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c:%L - %m%n" /> </Console> </Appenders> <Loggers> <Root level="WARN"> <AppenderRef ref="ConsoleAppender"/> </Root> </Loggers> </Configuration>

Registro em log de ID de solicitação da AWS

Quando algo dá errado, você pode encontrar os IDs de solicitação nas exceções. No entanto, se você quiser os IDs das solicitações que não estão gerando exceções, poderá usar o registro em log.

O logger software.amazon.awssdk.request exibe os IDs de solicitação no nível DEBUG. O exemplo a seguir estende o configuration example anterior para manter o nível do logger raiz em ERROR, o software.amazon.awssdk em nível WARN e o software.amazon.awssdk.request em nível DEBUG. A definição desses níveis ajuda a capturar os IDs de solicitação e outros detalhes relacionados à solicitação, como o endpoint e o código de status.

<Configuration status="WARN"> <Appenders> <Console name="ConsoleAppender" target="SYSTEM_OUT"> <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c:%L - %m%n" /> </Console> </Appenders> <Loggers> <Root level="ERROR"> <AppenderRef ref="ConsoleAppender"/> </Root> <Logger name="software.amazon.awssdk" level="WARN" /> <Logger name="software.amazon.awssdk.request" level="DEBUG" /> </Loggers> </Configuration>

Aqui está um exemplo da saída do log:

2022-09-23 16:02:08 [main] DEBUG software.amazon.awssdk.request:85 - Sending Request: DefaultSdkHttpFullRequest(httpMethod=POST, protocol=https, host=dynamodb.us-east-1.amazonaws.com, encodedPath=/, headers=[amz-sdk-invocation-id, Content-Length, Content-Type, User-Agent, X-Amz-Target], queryParameters=[]) 2022-09-23 16:02:08 [main] DEBUG software.amazon.awssdk.request:85 - Received successful response: 200, Request ID: QS9DUMME2NHEDH8TGT9N5V53OJVV4KQNSO5AEMVJF66Q9ASUAAJG, Extended Request ID: not available

Paginação

Algumas solicitações, como Query e Scan, limitam o tamanho dos dados retornadas em uma única solicitação e exigem que você faça solicitações repetidas para extrair as páginas subsequentes.

É possível controlar o número máximo de itens a serem lidos em cada página com o parâmetro Limit. Por exemplo, é possível usar o parâmetro Limit para recuperar somente os últimos dez itens. Esse limite especifica quantos itens devem ser lidos da tabela antes da aplicação de qualquer filtragem. Se você quiser exatamente dez itens após a filtragem, não há como especificar isso. Só é possível controlar a contagem pré-filtrada e verificar no lado do cliente quando tiver realmente recuperado dez itens. Independentemente do limite, as respostas sempre têm um tamanho máximo de 1 MB.

Uma LastEvaluatedKey pode ser incluída na resposta de API. Isso indica que a resposta terminou porque atingiu um limite de contagem ou um limite de tamanho. Essa chave é a última avaliada para essa resposta. Interagindo diretamente com a API, é possível recuperar essa LastEvaluatedKey e transmiti-la para uma chamada de acompanhamento como ExclusiveStartKey para ler a próxima parte desse ponto de partida. Se não for retornada nenhuma LastEvaluatedKey, significa que não há mais itens que correspondam à chamada de API Query ou Scan.

O exemplo a seguir usa a interface de nível inferior para limitar os itens a cem com base no parâmetro keyConditionExpression.

QueryRequest.Builder queryRequestBuilder = QueryRequest.builder() .expressionAttributeValues(Map.of( ":pk_val", AttributeValue.fromS("123"), ":sk_val", AttributeValue.fromN("1000"))) .keyConditionExpression("pk = :pk_val AND sk > :sk_val") .limit(100) .tableName(TABLE_NAME); while (true) { QueryResponse queryResponse = DYNAMODB_CLIENT.query(queryRequestBuilder.build()); queryResponse.items().forEach(item -> { LOGGER.info("item PK: [" + item.get("pk") + "] and SK: [" + item.get("sk") + "]"); }); if (!queryResponse.hasLastEvaluatedKey()) { break; } queryRequestBuilder.exclusiveStartKey(queryResponse.lastEvaluatedKey()); }

O AWS SDK for Java 2.x pode simplificar essa interação com o DynamoDB, fornecendo métodos de paginação automática que fazem várias chamadas de serviço para obter as próximas páginas de resultados automaticamente. Isso simplifica o código, mas elimina uma parte do controle sobre o uso de recursos que você manteria lendo as páginas manualmente.

Usando os métodos Iterable disponíveis no cliente do DynamoDB, como QueryPaginator e ScanPaginator, o SDK cuida da paginação. O tipo de retorno desses métodos é um iterável personalizado que você pode usar para percorrer todas as páginas. O SDK lida internamente com as chamadas de serviço para você. Usando a API do Java Stream, é possível lidar com o resultado de QueryPaginator, conforme mostrado no exemplo a seguir.

QueryPublisher queryPublisher = DYNAMODB_CLIENT.queryPaginator(QueryRequest.builder() .expressionAttributeValues(Map.of( ":pk_val", AttributeValue.fromS("123"), ":sk_val", AttributeValue.fromN("1000"))) .keyConditionExpression("pk = :pk_val AND sk > :sk_val") .limit(100) .tableName("YourTableName") .build()); queryPublisher.items().subscribe(item -> System.out.println(item.get("itemData"))).join();

Anotações de classes de dados

O SDK para Java fornece várias anotações que você pode colocar nos atributos da sua classe de dados. Essas anotações influenciam a forma como o SDK interage com os atributos. Ao adicionar uma anotação, é possível fazer com que um atributo se comporte como um contador atômico implícito, mantenha um valor de carimbo de data e hora gerado automaticamente ou rastreie o número da versão de um item. Para ter mais informações, consulte Data class annotations.