Back-End

7 jul, 2017

Alta produtividade em Java com NoSQL

Publicidade

Gostaria de compartilhar com vocês uma nova API Java para trabalhar com banco de dados NoSQL, mas antes de começar a falar, vou explicar alguns conceitos.

O que é NoSQL?

NoSQL é um termo usado para uma estrutura de banco de dados diferente, na qual estamos acostumados a utilizar, os não relacionais. O maior diferencial entre esse tipo de banco e os tradicionais são a velocidade e alta escalabilidade. Hoje, temos 5 modelos diferentes, sendo eles:

  • Chave-Valor
  • Documento
  • Família de colunas
  • Grafos

O quinto é questionável, pois ele consiste em dois modelos diferentes juntos no mesmo banco.

Chave-Valor

Esse modelo é o mais simples entre os bancos de dados NoSQL. Sua estrutura é muito semelhante ao Map do Java.

Exemplos:

Documento

Talvez seja o mais conhecido por conta da popularidade do MongoDB e, como consequência, acaba sendo porta de entrada para quem quer fugir dos modelos tradicionais.
Seu diferencial é a possibilidade de armazenar dados semi-estruturados, ou seja, você não precisa ter um schema pré-definido.

Exemplos:

Família de colunas

Projetado para lidar com uma enorme massa de dados de forma distribuída e com um alto grau de escalabilidade, ideal para sistemas analíticos, como internet das coisas.

Exemplos:

Grafos

Se baseia em uma estrutura de dados que conecta um conjunto de vértices em um conjunto de arestas.

Exemplos:

Problema

Diferente das aplicações que usam bancos tradicionais, não temos uma API padrão de comunicação para trabalhar com NoSQL. Como consequência, temos que implementar na mão para cada banco de dados, independente​ se possuem a mesma estrutura, um novo banco, uma nova API.

Outro grande problema é o lock-in vendor, ou seja, sua aplicação fica presa àquele fornecedor do banco e caso precise mudar, terá que alterar todo seu código que implementa essa API.

Soluções existentes

Alguns frameworks já disponibilizam uma API de alto nível, como é o caso do Spring data, Hibernate OGM e TopLink.

O problema é que cada um implementa a camada de comunicação do seu jeito, sendo impossível trocar de um framework para outro.

Solução

Uma solução seria separar em duas camadas: uma de comunicação responsável por se comunicar com os bancos e outra, de abstração que será responsável pela integração com a camada de comunicação e com outras tecnologias como CDI, Spring etc.

Benefícios:

  • Não ficará presso a um fornecedor
  • Divisão dos problemas
  • Migração mais simples

Diana

Diana tem como objetivo ser a camada de baixo nível, provendo uma API para os quatro modelos de bancos NoSQL e, futuramente, se tornar uma especificação Java.

Diana não tem como objetivo ser uma nova API JPA, ou uma camada de abstração, para isso está sendo desenvolvido um outro projeto (Artemis), que tem como foco integrar com outras tecnologias como o CDI.

Como irá funcionar?

A ideia é que os fornecedores implementem Diana de acordo com o modelo de seu banco de dados, provendo um driver, assim como no JDBC.

Exemplo de uma aplicação utilizando Diana com MongoDB

Importando dependência:

<dependency>  
  <groupId>org.jnosql.diana</groupId>
  <artifactId>mongodb-driver</artifactId>
  <version>0.0.1</version>
</dependency>

Obs: Atualmente, é necessário adicionar o repositório do JNoSQL para ter acesso as suas dependências:

<repositories>  
  <repository>
    <id>jnsoql-repo</id>
    <name>JNoSQL Maven Repository</name>
    <url>https://dl.bintray.com/jnosql/maven/</url>
    <layout>default</layout>
  </repository>
</repositories>

É necessário também um arquivo chamado diana-mongodb.properties no classpath com as configurações do banco:

mongodb-server-host-1=localhost:27017

Criando um Configuration com Spring

import org.jnosql.diana.api.document.DocumentCollectionManager;  
import org.jnosql.diana.api.document.DocumentCollectionManagerFactory;  
import org.jnosql.diana.api.document.DocumentConfiguration;  
import org.jnosql.diana.mongodb.document.MongoDBDocumentConfiguration;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;

@Configuration
public class DocumentManagerConfiguration {

    private DocumentCollectionManagerFactory managerFactory;

    /**
    * DocumentConfiguration: Classe responsável pela 
    * configuração e conexão com o banco de dados.
    **/
    @PostConstruct
    public void init() {
        DocumentConfiguration configuration = new MongoDBDocumentConfiguration(); // Implementação do banco
        managerFactory = configuration.get();
    }

    /**
    * DocumentCollectionManagerFactory: Classe responsável 
    * pela criação de uma interface Manager
    **/
    @Bean
    public DocumentCollectionManagerFactory documentCollectionManagerFactory() {
        return managerFactory;
    }

    /**
    * DocumentCollectionManager: Assim como no JPA, essa 
    * classe é a responsável pelo gerenciamento da entidade.
    **/
    @Bean
    public DocumentCollectionManager documentCollectionManager() {
        return managerFactory.get("postman"); // nome da base de dados
    }

    @PreDestroy
    public void destroy() {
        managerFactory.close();
    }
}

Criando um Repository

package org.jnosql.diana.domain.model;

import org.jnosql.diana.api.document.Document;  
import org.jnosql.diana.api.document.DocumentCollectionManager;  
import org.jnosql.diana.api.document.DocumentEntity;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Repository;

import java.util.List;

import static java.util.Arrays.asList;

@Repository
public class TemplateRepositoryImpl implements TemplateRepository {

    @Autowired
    private DocumentCollectionManager manager;

    @Override
    public void save(Template template) {
        /*
        * Document: Um DocumentEntity é composto por diversos documentos,
        * ele é composto por uma tupla em que a chave é o nome e o valor
        * é a informação propriamente dita. Essa informação poderá ser
        * qualquer valor, variando de cada driver, inclusive um outro
        * documento ou subdocumento.
        * */
        List<Document> documents = asList(
                Document.of("_id", template.getId()),
                Document.of("token", template.getToken()),
                Document.of("layout", template.getLayout())
        );

        /*
        * DocumentEntity: uma entidade de uma coleção de documentos
        * */
        DocumentEntity entity = DocumentEntity.of("templates", documents);

        manager.save(entity);
    }
}

Referências