• JP

Tutorial DynamoDB utilizando AWS SDK e Java

O DynamoDB é um banco de dados chave-valor e documentos. Indicado para aplicações que necessitam de um super desempenho em leituras e escritas com baixa latência. O DynamoDB pode processar mais de 20 trilhões de solicitações por dia e mais de 20 milhões de solicitações por segundo.


A estrutura do DynamoDB é um pouco diferente dos bancos relacionais, não se cria um banco e suas tabelas. Todas as tabelas são criadas de forma descentralizadas. Para conectar nestas tabelas, basta utilizar a SDK da AWS. Não existe um driver JDBC que nem usamos em bancos relacionais para conecta-los.


Para este tutorial vamos criar alguns exemplo de acesso ao DynamoDB criando e lendo itens utilizando a AWS SDK, Java como linguagem de programação e Maven como Build Tool.


Maven

<dependencies>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-dynamodb</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-bom</artifactId>
            <version>1.11.327</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

* Adicionamos a dependência do Lombok para auxiliar na criação do modelo a seguir.


Criando o modelo


Vamos criar o modelo que utilizaremos como uma instância dos itens que serão adicionados no DynamoBD.

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import lombok.Data;

@Data
@DynamoDBTable(tableName = "Books")
public class Book {

    @DynamoDBHashKey
    @DynamoDBAttribute(attributeName = "isbn")
    private String isbn;

    @DynamoDBAttribute(attributeName = "title")
    private String title;

    @DynamoDBAttribute(attributeName = "pages")
    private int pages;

    @DynamoDBAttribute(attributeName = "publishing_company")
    private String publishingCompany;

    @DynamoDBAttribute(attributeName = "year")
    private int year;
}

Entendendo as anotações da classe Book:


  • @Data

  • Anotação do Lombok que substituirá os getters e setters para cada campo. Estamos utilizando o Lombok apenas para deixar o código menos verboso. Não possui relação direta com o DynamoDB.


  • @DynamoDBTable

  • Anotação da SDK da AWS DynamoDB que define a classe como uma tabela destino do DynamoBD. Veja que é possível passar como parâmetro o nome da tabela que será Books.


  • @DynamoDBHashKey

  • Esta anotação define uma chave de partição, de forma resumida, seria uma espécie de chave primária. Para o nosso exemplo, o campo isbn será definida como campo chave na tabela Books no DynamoDB.


  • @DynamoDBAttribute

  • Na prática, cada campo mapeado no modelo será um campo da tabela de destino Books no DynamoDB. A anotação @DynamoDBAttribute pode ser utilizada para caso você precise definir um nome diferente do que foi declarado no modelo. Por ex: O campo publishingCompany será definido na tabela como publishing_company e os demais serão criadas conforme foram declarados no modelo. Nesse caso, mapeamos todos eles para manter um padrão.

Existem várias outras anotações que podem ser utilizadas, basta acessar a documentação para entender um pouco mais.


Criando a configuração de acesso ao Dynamo


O próximo código será utilizado para conectar ao serviço do DynamoDB na AWS e criar uma instância responsável por manipular os itens.

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;

public class DynamoConfig {

    private static AmazonDynamoDB amazonDynamoDB;

    private static AmazonDynamoDB getAmazonDynamoDB(){

        BasicAWSCredentials credentials =
                new BasicAWSCredentials(
                "ACCESS-KEY","SECRET-ACCESS-KEY"););

        AWSCredentialsProvider awsCredentialsProvider =
                new AWSStaticCredentialsProvider(credentials);

        return AmazonDynamoDBClientBuilder.standard()
                .withCredentials(awsCredentialsProvider)
                .withRegion(Regions.US_EAST_1)
                .build();
    }

    public static DynamoDBMapper dynamoDBMapper(){
        if(amazonDynamoDB == null){
            amazonDynamoDB = getAmazonDynamoDB();
        }
        return new DynamoDBMapper(amazonDynamoDB);
    }
}

Entendendo a classe DynamoConfig:


  • O objeto amazonDynamoDB é uma instância do tipo AmazonDynamoDB que será responsável em recuperar os recursos necessários para o acesso ao DynamoDB.


  • O método getAmazonDynamoDB( ) conectará na AWS através de suas credencias de sua conta na região US-EAST-1. Caso utilize outra região é só alterar o enum Regions.


  • O método getDynamoDBMapper( ) criará um um objeto do tipo DynamoDBMapper. Perceba que ele cria uma instância a partir do objeto amazonDynamoDB, responsável por manter a conexão com o serviço do AWS DynamoDB. Um objeto do tipo DynamoDBMapper permite invocar operações do tipo CRUD, segue alguns dos métodos disponíveis:

  1. save

  2. load

  3. delete

  4. query

  5. scan

  6. scanPage

As operações estão disponíveis nesta doc.


Criando o DAO

import com.amazonaws.services.dynamodbv2
        .datamodeling.DynamoDBMapper;
import config.DynamoConfig;
import model.Book;

public class BookDAO {

    private DynamoDBMapper dynamoDBMapper;

    public BookDAO(){
        dynamoDBMapper = DynamoConfig.dynamoDBMapper();
    }

    public void save(Book book){
        dynamoDBMapper.save(book);
    }

    public void delete(Book book){
        dynamoDBMapper.delete(book);
    }

    public Book findByIsbn(String isbn){
        return dynamoDBMapper
                .load(Book.class, isbn);
    }
}

A classe BookDAO é a classe que contém os métodos responsáveis em acessar o DynamoBD e manipular os itens.


Entendendo a classe BookDAO:


  • Conforme falamos anteriormente, o objeto dynamoMapper fornece as operações necessárias para manipular um item


  • Método save(Book book) persistirá o item na tabela Books


  • Método delete(Book book) é responsável por remover o item da tabela Books


  • Método findByIsbn(String isbn) executa uma consulta nos itens filtrando pela chave isbn. Dentro deste método invocamos o método load(Class<T> clazz, Object hashKey) do DynamoMapper que nos obriga a passar o tipo do objeto e a chave de busca.


Executando o código


Antes de executar o código, vamos criar a tabela Books utilizando o console do DynamoDB.


  • Clique em Criar Tabela e uma nova página será aberta


  • No campo Nome da Tabela, digite Books

  • No campo Chave primária, digite isbn.


Para finalizar, clique em Criar.


Após executar as etapas anteriores, vamos criar um código que executará um fluxo de criação de um Item, consulta e por fim, vamos deletá-lo.


import dao.BookDAO;
import model.Book;

public class RunDynamo {

    public static void main(String[] args) {

        BookDAO bookDAO = new BookDAO();

        System.out.println("Criando o objeto book");

        Book book = new Book();
        book.setIsbn("isbn-1");
        book.setTitle("Entendendo DynamoDB");
        book.setPublishingCompany("Editora Abril");
        book.setYear(2021);
        book.setPages(300);

        System.out.println("Salvando o objeto book");

        bookDAO.save(book);

        System.out.println("Buscando o objeto book por Isbn");

        Book bookSearch = bookDAO.findByIsbn("isbn-1");

        System.out.println("
        Resultado da consulta: " + bookSearch);

        System.out.println("Removendo o objeto book");
        bookDAO.delete(bookSearch);
    }
}

Após executar o código, verifique no console o resultado do fluxo.

Paginando uma consulta utilizando scanPage


O método scanPage faz parte do pacote de operações disponíveis na classe DynamoDBMapper. Utilizando este método é possível criar consultas mais completas utilizando paginação.


No próximo exemplo vamos utilizar este método junto com expressões que pode deixar bem flexível a consulta utilizando diversos filtros.

public List<Book> listBooksPagination(Integer year, String title, Integer pagination){

    Map<String, AttributeValue> attributeValueMap
            = new HashMap<>();

    attributeValueMap.put(":year", new AttributeValue()
            .withN(year.toString()));

    attributeValueMap.put(":title", new AttributeValue()
            .withS(title));

    DynamoDBScanExpression dbScanExpression
            = new DynamoDBScanExpression()
            .withFilterExpression("year = :year and
                                   title = :title")
            .withExpressionAttributeValues(attributeValueMap)
            .withLimit(pagination);

    List<Book> listBooks = new ArrayList<>();

    do {

        ScanResultPage<Book> bookScanResultPage
                = dynamoDBMapper.scanPage(Book.class,
                dbScanExpression);

        bookScanResultPage.getResults().forEach(
                result -> listBooks.add(result)
        );

    } while (dbScanExpression.getExclusiveStartKey() != null);
    return listBooks;
}

O método acima foi criada para exemplificar uma forma mais dinâmica e completa para fazer consultas no DynamoDB de forma mais segura. Utilize o scanPage ao invés do scan. Pois este faz uma leitura de toda a tabela que pode sair um pouco caro, lembrando que uma das formas de cobrança do DynamoDB são as quantidades de consultas. Imagine uma tabela com milhões de registro, a cobrança pode ser bem alta.


É isso, curtiu? Até mais!

Posts recentes

Ver tudo