Desenvolvimento Java 2.0: MongoDB: Um armazenamento de dados NoSQL com movimentos (apenas os corretos) de RDBMS

Crie e consulte documentos usando código Java e Groovy

Se você está explorando o mundo de bancos de dados NoSQL, o MongoDB — às vezes anunciado como o RDBMS de NoSQL — merece um lugar na sua lista. Aprenda tudo sobre API customizada, shell interativa e suporte a consultas dinâmicas do estilo RDBMS, assim como cálculos MapReduce rápidos e fáceis. Depois comece a criar, localizar e manipular dados usando o driver de linguagem nativa Java do MongoDB™ e um wrapper Groovy muito útil chamado GMongo.

Andrew Glover, Author and developer, Beacon50

Andrew Glover is a developer, author, speaker, and entrepreneur with a passion for behavior-driven development, Continuous Integration, and Agile software development. He is the founder of the easyb Behavior-Driven Development (BDD) framework and is the co-author of three books: Continuous Integration, Groovy in Action, and Java Testing Patterns. You can keep up with him at his blog and by following him on Twitter.



28/Set/2010

Bancos de dados orientados a documentos como o MongoDB e o CouchDB são muito diferentes de bancos de dados relacionais, já que eles não armazenam dados em tabelas. Ao invés disso, eles armazenam dados na forma de documentos. Sob a perspectiva de um desenvolvedor, dados orientados a documento (ou sem esquema) são mais fáceis e muito mais flexíveis de se gerenciar do que os dados relacionais. Ao invés de armazenar dados em um esquema rígido de tabelas, linhas e colunas, unidos por relacionamentos, os documentos são gravados individualmente, contendo qualquer dado necessário.

Mais sobre o MongoDB a partir da origem

Saiba mais sobre esse banco de dados de documento de software livre por Eliot Horowitz, diretor técnico da 10gen, neste propício podcast técnico. Ouça agora.

Dentre os bancos de dados orientados a documento de software livre, o Mongo DB é muitas vezes anunciado como banco de dados de NoSQL com recursos de RDBMS. Um exemplo disso é o suporte do MongoDB a consultas dinâmicas que não exigem funções de MapReduce predefinidas. O MongoDB também apresenta um shell interativo que torna seu armazenamento de dados agradavelmente fácil, e seu suporte a fragmentação pronto para uso permite uma alta escalabilidade por múltiplos nós.

A API do MongoDB é uma mistura nativa de objetos JSON e funções de JavaScript. Os desenvolvedores interagem com o MongoDB através do programa de shell, que permite argumentos de linha de comandos, ou usando um driver de linguagem para acessar instâncias de armazenamento de dados. No entanto, não há um driver do tipo JDBC, o que significa que você não precisa lidar com ResultSet ou PreparedStatements.

A velocidade é outra vantagem do Mongo DB, principalmente em relação a como ele trata as gravações: elas são armazenadas na memória e mais tarde, através de encadeamento de segundo plano, gravadas em disco.

Sobre esta série

O cenário de desenvolvimento Java mudou radicalmente desde o surgimento da tecnologia Java. Graças a estruturas de software livre desenvolvidas e infraestruturas de implementação para aluguel confiáveis, agora é possível montar, testar, executar e fazer a manutenção de aplicativos Java de forma fácil e barata. Nesta série, Andrew Glover explora o espectro de tecnologias e ferramentas que torna possível esse novo paradigma de desenvolvimento Java.

Neste artigo, você conhecerá o MongoDB. Farei minha apresentação ao CouchDB (consulte Recursos), usando mais uma vez o exemplo de um bilhete de estacionamento para demonstrar a flexibilidade de armazenamento de dados sem esquema. Como a API e o suporte do MongoDB para consultas dinâmicas são dois de seus principais argumentos de vendas, focarei nesses argumentos, guiando-os através de exemplos que demonstrem o shell do MongoDB e o driver de linguagem Java em ação. Posteriormente, também apresentarei o GMongo, um wrapper Groovy que tira um pouco da verbosidade da implementação do MapReduce do MongoDB, — que também vem a ser um destaque dessa opção de NoSQL em particular.

Por que escolher um armazenamento sem esquema?

O armazenamento sem esquema não é apropriado para todo domínio, então é uma boa ideia entender por que você deve escolher uma abordagem orientada a documento ao invés de uma abordagem relacional. A flexibilidade de documentos faz sentido em domínios em que os dados podem ser representados em formas variadas, mas com o mesmo modelo básico. Um exemplo clássico é um cartão de visita. Dada uma pilha de cartões de visita, você verá que eles apresentam diferentes dados: alguns incluem um número de fax ou URL da empresa, outros um endereço para correspondência, ou dois números de telefone, ou até mesmo um identificador do Twitter. Os dados variam, mas o modelo, ou função, é o mesmo — cartões de visita contêm informações de contato.

Modelar um cartão de visita em termos relacionais é possível, mas complicado. Em um banco de dados relacional, você acaba tendo muitos registros com um valor nulo na coluna de fax (por exemplo) para cada um ou dois registros que usam esse valor. Também é preciso especificar tipos de coluna em um sistema relacional, de modo que você possa ficar limitado, digamos, pelo tamanho do campo de endereço. (Aposto que você nunca imaginou que teria que armazenar o endereço de alguém que mora em Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch, não é? Apesar de essa cidade existir.)

Modelar um cartão de visita com um armazenamento de dados orientado a documento é uma tarefa muito mais fácil. Não ter um esquema significa que o documento pode conter qualquer dado necessário, de qualquer tamanho. Dada a natureza dos cartões de visita, faz sentido modelá-los como documentos com propriedades variadas.

O armazenamento de dados sem esquema, na maior parte, não suporta ACID (Atomicity, Consistency, Isolation, and Durability) totalmente, no entanto, pode apresentar um desafio em domínios em que a confiabilidade e a durabilidade são essenciais. Defensores da abordagem NoSQL argumentam que ACID só funciona enquanto você não considerar a indisponibilidade, que é inevitável quando você começa a introduzir múltiplos nós em um esforço para ser escalável. O resultado pode ser que o armazenamento de dados sem esquema tenda a ser escalável mais facilmente do que o relacional, tornando o armazenamento orientado a documento uma boa opção para aplicativos baseados na Web.


Introdução ao MongoDB

Começar a usar o MongoDB não poderia ser mais fácil, especialmente porque ele oferece downloads para sistemas operacionais de destino. Se quiser configurar o MongoDB para um Mac OS X, por exemplo, é só fazer o download do binário apropriado, descompactá-lo, criar um diretório de dados (no qual o MongoDB grava o conteúdo do seu armazenamento de dados), e depois iniciar uma instância através do comandomongodb . (Certifique-se de avisar ao processo o local em que você gostaria de gravar seus dados!)

Na listagem 1, estou disparando o MongoDB e dizendo a ele para armazenar meus dados no diretório data/db . Você observará que também estou passando o sinalizador verbose— quanto mais v, maior a verbosidade.

Listagem 1. MongoDB para Mac OS X
iterm$ ./bin/mongod — dbpath ./data/db/ ——vvvvvvvv

Assim que tiver iniciado o MongoDB, você pode rapidamente pegar o jeito de trabalhar com seu shell interativo. Basta invocar o comando mongo e você verá algo semelhante ao que está na listagem 2:

Listagem 2. Iniciando o shell do MongoDB
iterm$ ./bin/mongo
MongoDB shell version: 1.6.0
connecting to: test
>

Quando iniciar o shell, você verá que está inicialmente conectado ao armazenamento de dados de "teste". Irei usá-lo por enquanto para demonstrar como criar e localizar documentos, o que envolve gravar um pouco de JavaScript e JSON.


Criação e localização de documentos

Assim como o CouchDB, o MongoDB facilita a criação de documentos com JSON (embora eles sejam armazenados como BSON, uma forma binária de JSON dinamizada para uma maior eficiência). Para criar um bilhete de estacionamento no shell interativo, basta criar um documento JSON como o da listagem 3:

Listagem 3. Um documento JSON simples
> ticket =  { officer: "Kristen Ree" , location: "Walmart parking lot", vehicle_plate: 
  "Virginia 5566",  offense: "Parked in no parking zone", date: "2010/08/15"}

Ao pressionar Return, o MongoDB responderia com um documento JSON formatado como o da listagem 4:

Listagem 4. Resposta do MongoDB
{
  "officer" : "Kristen Ree",
  "location" : "Walmart parking lot",
  "vehicle_plate" : "Virginia 5566",
  "offense" : "Parked in no parking zone",
  "date" : "2010/08/15"
}

Acabei de criar uma representação JSON de um bilhete de estacionamento e a chamei de "ticket". Para prosseguir com esse documento, tenho que associá-lo a uma collection, que é semelhante ao esquema em termos relacionais. Na listagem 5, associo o bilhete com uma coleção tickets :

Listagem 5. Salvar uma instância de bilhete
> db.tickets.save(ticket)

Observe que no MongoDB, minha coleção tickets não precisa ser criada antecipadamente. As coleções são criadas na primeira vez em que são mencionadas.

Vá em frente e crie mais alguns bilhetes agora. Tornarei sua localização na próxima seção um pouco mais interessante.

Localização de documentos

Localizar todos os documentos em uma coleção é fácil: é só chamar o comando find como mostrado na listagem 6:

Listagem 6. Localização de todos os documentos no MongoDB
> db.tickets.find()
{ "_id" : ObjectId("4c7aca17dfb1ab5b3c1bdee8"), "officer" : "Kristen Ree", "location" : 
  "Walmart parking lot", "vehicle_plate" : "Virginia 5566", "offense" : 
  "Parked in no parking zone", "date" : "2010/08/15" }
{ "_id" : ObjectId("4c7aca1ddfb1ab5b3c1bdee9"), "officer" : "Kristen Ree", "location" : 
  "199 Baldwin Dr", "vehicle_plate" : "Maryland 7777", "offense" : 
  "Parked in no parking zone", "date" : "2010/08/29" }

O comando find sem quaisquer parâmetros simplesmente retorna todos os documentos em uma coleção em particular, que, neste caso, é chamada de tickets.

Observe que na listagem 6 o MongoDB criou um ID para cada documento, como anunciado pela chave_id .

É possível pesquisar em chaves individuais em documentos JSON. Por exemplo, se eu quisesse localizar todos os bilhetes emitidos em um estacionamento do Walmart, usaria a consulta na listagem 7:

Listagem 7. Localização com consultas
> db.tickets.find({location:"Walmart parking lot"})

É possível pesquisar em qualquer chave disponível em um documento JSON (neste caso, offense, _id, date, e assim por diante). Outra opção (mostrada na listagem 8) é usar uma expressão regular para pesquisar em um valor da chave (como location), que funciona quase do mesmo modo que a instrução LIKE em SQL:

Listagem 8. Localização com regex
> db.tickets.find({location:/walmart/i})

O delimitador i depois da instrução regex (que, neste caso, é simplesmente a frase walmart) significa que a instrução não faz distinção de maiúsculas e minúsculas.


Driver Java do MongoDB

O driver de linguagem Java do MongoDB destina-se a extrair muito do código JSON e JavaScript, que foi visto na seção anterior, de modo que você fica com uma API Java direta. Para começar a trabalhar com o driver Java do MongoDB, basta fazer o download e colocar o arquivo .jar no seu caminho de classe (consulte Recursos).

Digamos que agora você queira criar um outro bilhete na coleção tickets , que é armazenado no armazenamento de dados test . Usando o driver Java, primeiro você se conectará a uma instância do MongoDB, depois irá capturar o banco de dados test e a coleção tickets como mostrado na listagem 9:

Listagem 9. Uso do driver Java do MongoDB
Mongo m = new Mongo();
DB db = m.getDB("test");
DBCollection coll = db.getCollection("tickets");

Para criar um documento JSON usando o driver Java, basta criar um BasicObject e nomes e valores associados a ele, como na listagem 10:

Listagem 10. Criar um documento com o driver Java
BasicDBObject doc = new BasicDBObject();

doc.put("officer", "Andrew Smith");
doc.put("location", "Target Shopping Center parking lot");
doc.put("vehicle_plate", "Virginia 2345");
doc.put("offense", "Double parked");
doc.put("date", "2010/08/13");

coll.insert(doc);

Localizar documentos e iterar sobre o cursor resultante também é bastante fácil com o driver Java, como demonstrado na listagem 11:

Listagem 11. Localização de documentos com o driver Java
DBCursor cur = coll.find();
while (cur.hasNext()) {
 System.out.println(cur.next());
}

Poucas bibliotecas de MongoDB estão disponíveis para desenvolvedores Java, incluindo uma abstração ótima em Groovy, que é construída na parte superior do driver Java. Na próxima seção, construirei um aplicativo que permite que você veja o driver Java padrão e o driver mais parecido com o Groovy em ação. Esse excelente aplicativo também demonstrará a funcionalidade MapReduce do MongoDB, que usarei para processar uma coleção de documentos.


Análise do Twitter com MongoDB

Dados que apenas se instalam em um banco de dados não são tão interessantes: o mais interessante é como fazemos uso deles. Com esse aplicativo, primeiro vou capturar algumas informações do Twitter e armazená-las no MongoDB. Depois calcularei duas métricas: quem mais envia retweets e quais dos meus tweets têm mais retweets.

Para executar esse aplicativo, primeiro preciso de uma maneira de criar uma interface com o Twitter e capturar dados. Para isso, usarei uma ótima biblioteca chamada Twitter4J, que extrai mais ou menos a API RESTful do Twitter em uma API Java simples (consulte Recursos). Usarei essa API para localizar meus retweets. Quando tiver os dados, irei formatá-los em um documento JSON, algo parecido com o que é mostrado na listagem 12:

Listagem 12. Retweets armazenados através de JSON
{ 
  "user_name" : "twitter user",
  "tweet" : "Podcast ...", 
  "tweet_id" :  9090...., 
  "date" : "08/12/2010" 
}

Na listagem 13, uso o driver Java nativo do Mongo DB junto com a Twitter4J no meu aplicativo de driver simples (também gravado em código Java), que irá capturar e depois armazenar os dados no MongoDB:

Listagem 13. Inserção de dados do Twitter no MongoDB
Mongo m = new Mongo();
DB db = m.getDB("twitter_stats");
DBCollection coll = db.getCollection("retweets");

Twitter twitter = new TwitterFactory().getInstance("<some user name>", "<some password>");
List<Status> statuses = twitter.getRetweetsOfMe();
for (Status status : statuses) { 
  ResponseList<User> users = twitter.getRetweetedBy(status.getId());
  
  for (User user : users) {
    BasicDBObject doc = new BasicDBObject();
    doc.put("user_name", user.getScreenName());
    doc.put("tweet", status.getText());
    doc.put("tweet_id", status.getId());
    doc.put("date", status.getCreatedAt());
    coll.insert(doc);
 }
}

Observe que o banco de dados "twitter_stats" na listagem 13 foi criado on demand, já que ele não existia antes do driver ser executado. O mesmo é verdadeiro para a coleção "retweets". Quando tanto o banco de dados quanto a coleção forem criados, o objeto Twitter da Twitter4J é obtido, seguido dos 20 retweets mais recentes.

Os objetos da Lista de Status retornados da Twitter4J representam agora os meus retweets. Cada um é consultado em busca de dados pertinentes, depois uma instância BasicDBObject do MongoDB é criada e preenchida com dados relevantes. Finalmente, cada documento é continuado.


MapReduce do MongoDB

Quando tiver armazenado todos esses dados, estarei pronto para começar a manipulá-los. Obter as informações que desejo exige duas operações em lote: primeiro, somarei o número de vezes que cada usuário do Twitter é listado. Depois somarei o número de vezes que cada tweet (ou tweet_id) aparece como pop-up.

O MongoDB dinamiza o MapReduce para manipulação de dados em lote. Em um alto nível, o algoritmo MapReduce divide um problema em duas etapas: a função Map foi elaborada para pegar uma entrada grande e dividi-la em pedaços menores, e depois passar esses dados para outros processos que possam fazer alguma coisa com eles. A função Reduce é destinada a levar as respostas individuais de Map para uma única saída final.

Como a API principal do MongoDB é JavaScript, as funções MapReduce precisam ser escritas em JavaScript. Assim, mesmo usando o driver Java, ainda preciso escrever JavaScript para funções MapReduce, embora eu pudesse definir o JavaScript em uma cadeia de caractere ou em um objeto semelhante ao BasicDBObject. Vou simplificar as coisas um pouco mais e salvar um pouco de codificação, com a ajuda da pequena biblioteca de wrapper, na parte superior do driver padrão do MongoDB. O wrapper — chamado GMongo — é escrito e destinado a ser usado em Groovy. Ainda terei que gravar funções MapReduce em Javascript, mas o recurso multilinhas do Groovy tornará a tarefa um pouco menos confusa, especialmente porque não terei que deixar escapar a cadeia de caractere.

Funções MapReduce em JavaScript

Para descobrir quem envia mais retweets, tenho que fazer duas coisas: primeiro preciso escrever uma função map que se encaixe na propriedade user_name da estrutura do meu documento JSON. Isso é bastante fácil, como mostrado na listagem 14:

Listagem 14. Uma função Map simples gravada em JavaScript
function map() {
  emit(this.user_name, 1); 
}

Minha função map é direta — , ela simplesmente captura a propriedade user_name de todos os documentos passados para ela. A chamada para emit é requerida, e seu segundo parâmetro é um valor. Esse valor é basicamente a contagem da chave, que, para um documento individual, é 1. Você verá como o valor de contagem funciona quando usá-lo para somar.

Assim, na listagem 14, chamei a função emit com minha chave (a propriedade user_name ) e um valor. A variável this no contexto da minha função representa o próprio documento JSON.

Em seguida, preciso definir uma função reduce (mostrada na listagem 15), que pega todos os documentos agrupados de modo correspondente e soma os valores:

Listagem 15. Uma função Reduce gravada em JavaScript
function reduce(key, vals) {
  var sum = 0;
  for(var i in vals) sum += vals[i];
  return sum;
}

Como é possível ver na listagem 15, as variáveis key e vals passadas para reduce representam algo como function reduce("asmith", [1,1,1,1]), significando, é claro, que um user_name de asmith retornou em quatro documentos diferentes. A. Smith retransmitiu tweets enviados por mim quatro vezes!

Confirmo isso iterando sobre a variável vals , que retorna um simples sum.

Funções MapReduce em Groovy

Em seguida, gravarei um script Groovy que usa GMongo e depois plugarei nas minhas funções map e reduce apropriadamente, como mostrado na listagem 16:

Listagem 16. Um script Groovy para MapReduce
  mongo = new GMongo()
def db = mongo.getDB("twitter_stats")

def res = db.retweets.mapReduce(
    """
    function map() {
        emit(this.user_name, 1); 
    }
    """,
    """
    function reduce(key, vals) {
        var sum = 0;
        for(var i in vals) sum += vals[i];
        return sum;
    }
    """,
    "result",
    [:] 
)

def cursor = db.result.find().sort(new BasicDBObject("value":-1))
       
cursor.each{
  println "${it._id} has retweeted you ${it.value as int} times"
}

Na listagem 16, primeiro eu crio uma instância de GMongo e obtenho o armazenamento de dados "twitter_stats", que é muito semelhante ao que você veria se eu estivesse usando o driver Java padrão.

Em seguida, faço a chamada de método mapReduce na coleção retweets . O driver GMongo permite que eu mencione a coleção diretamente, em vez de obtê-la, como tive que fazer na listagem 13. O método mapReduce pega quatro parâmetros:
os dois primeiros são Strings representando as funções map e reduce definidas em JavaScript. O terceiro parâmetro é o nome do objeto que contém os resultados de MapReduce. O quarto parâmetro é qualquer consulta de entrada necessária para completar a operação — , eu poderia passar para a função MapReduce, por exemplo, apenas certos documentos JSON (como documentos em um certo intervalo de data, por exemplo) ou partes deles.

Consulto, então, o objeto result (que é um documento JSON) e emito uma chamada sort . A chamada sort na listagem 16 requer um documento JSON parecido com {value:-1}, o que significa que desejo uma classificação decrescente, com o valor máximo na parte superior. O objeto cursor é basicamente um agente iterativo, de modo que posso usar o engenhoso each do Groovy diretamente para imprimir um pequeno relatório. Meu relatório listará os retweeters superiores, classificados de mais para menos.

Tente executar o script na listagem 16 e você verá algo como a saída na listagem 17:

Listagem 17. Saídas de MapReduce
bglover has retweeted you 3 times
bobama has retweeted you 3 times
sjobs has retweeted you 2 times
...

Agora sei quem mais retransmite meus tweets, mas que tal um relatório que mostre quais tweets foram mais retransmitidos? Isso é um exercício simples: basta definir uma função map que se desencaixe da propriedade tweet em vez de user_name, como mostrado na listagem 18:

Listagem 18. Outra função Map
function map() {
  emit(this.tweet, 1); 
}

Como bônus adicional, observe que a função reduce pode ser a mesma porque ela apenas soma chaves agrupadas!


Conclusão

Este foi um tour rápido e arrebatador pelo MongoDB, tocando apenas a superfície do que ele pode fazer. No entanto, espero que você tenha visto que a natureza sem esquema do MongoDB permite muita flexibilidade. Isso se torna especialmente útil em domínios nos quais os elementos de dados podem variar, mas são geralmente relacionados — como o exemplo do cartão de visita que apresentei no início do artigo.

Embora tanto o MongoDB quanto o CouchDB suportem flexibilidade sem esquema, eles são bastante diferentes em outras frentes: os recursos do tipo RDBMS do MongoDB tornam mais fácil, — e mais familiar também, trabalhar a partir de uma perspectiva de RDBMS. O MongoDB permite que você execute consultas dinâmicas em uma linguagem nativa como Java, Ruby ou PHP. Você tem tudo isso e mais o poder do MapReduce.

Bancos de dados orientados a documento não são para qualquer domínio. Domínios com transações grandes que lidam com dados financeiros provavelmente serão mais bem servidos por um RDBMS tradicional formado em ACID. Mas, para aplicativos que requerem um rendimento de alto desempenho e um modelo de dados flexível, vale a pena dar uma olhada no MongoDB.

Recursos

Aprender

Obter produtos e tecnologias

Discutir

  • Participe da comunidade My developerWorks. Entre em contato com outros usuários do developerWorks, enquanto explora os blogs, fóruns, grupos e wikis orientados ao desenvolvedor.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=555929
ArticleTitle=Desenvolvimento Java 2.0: MongoDB: Um armazenamento de dados NoSQL com movimentos (apenas os corretos) de RDBMS
publish-date=09282010