Desenvolvimento em Java 2.0: NoSQL

Modelagem de dados sem esquema com Bigtable e Groovy's Gaelyk

Armazenamentos de dados NoSQL, como Bigtable e CouchDB, estão se movendo da margem para o centro na Web 2.0, pois eles resolvem o problema da escalabilidade, em uma escala sólida. Google e Facebook são apenas dois nomes importantes que aderiram ao NoSQL e ainda estamos apenas começando. Armazenamentos de dados sem esquemas são fundamentalmente diferentes de bancos de dados relacionais tradicionais, mas alavancá-los é mais fácil do que você imagina, especialmente se você começar com um modelo de domínio, ao invés de um modelo relacional.

Andrew Glover, Author and developer

Andrew GloverAndrew Glover é um desenvolvedor, autor, palestrante e empreendedor com grande interesse em behavior-driven development (BDD - Desenvolvimento Guiado por Comportamento), Continuous Integration (CI - Integração contínua) e no desenvolvimento ágil de software. É possível acompanhá-lo em seu blog.



28/Mai/2010

Sobre esta série

O panorama do desenvolvimento em Java passou por mudanças radicais desde que a tecnologia Java surgiu. Graças a estruturas livres maduras e a infraestruturas de implementação confiáveis para aluguel, agora é possível montar, testar, executar e manter aplicativos Java de maneira rápida e barata. Nesta série, Andrew Glover explora a gama de tecnologias e ferramentas que torna possível esse novo paradigma de desenvolvimento em Java.

Bancos de dados relacionais dominaram o armazenamento de dados por mais de 30 anos, mas a crescente popularidade de bancos de dados sem esquemas (ou NoSQL) sugere que há uma mudança a caminho. Enquanto o RDDMS fornece uma base sólida para armazenar dados em arquiteturas cliente-servidor tradicionais, ele não escala de maneira fácil (ou barata) para nós múltiplos. Esse é um ponto fraco infeliz na era de aplicativos da Web altamente escaláveis, como o Facebook e o Twitter.

Considerando que as alternativas mais antigas ao banco de dados relacional (lembra-se dos bancos de dados orientados a objetos?) falhavam ao resolver problemas realmente urgentes, os bancos de dados NoSQL como o Bigtable do Google o SimpleDB da Amazon surgiram como uma resposta direta à demanda por alta escalabilidade da Web. Em essência, o NoSQL poderia ser um grande aplicativo para um grande problema — um problema que desenvolvedores de aplicativos da Web podem encontrar cada vez mais, não menos, com a evolução da Web 2.0.

Nesta parte de Desenvolvimento Java 2.0, introduzirei a modelagem de dados sem esquema, que é o principal obstáculo do NoSQL para muitos desenvolvedores treinados na mentalidade relacional. Como será aprendido, começar com um modelo de domínio (ao invés de um modelo relacional) é a chave para facilitar sua entrada. Caso esteja usando o Bigtable, como usa meu exemplo, é possível solicitar a ajuda do Gaelyk: uma extensão de estrutura leve para o Google App Engine.

NoSQL: Uma nova mentalidade?

Quando desenvolvedores falam sobre bancos de dados não relacionais ou NoSQL, a primeira coisa que é frequentemente dita é que eles necessitam mudar sua mentalidade. Em minha opinião, isso depende, na verdade, de sua abordagem inicial sobre modelagem de dados. Caso esteja acostumado a criar aplicativos modelando a estrutura do banco de dados primeiro (ou seja, imaginar tabelas e seus relacionamentos primeiro), então a modelagem de dados com um armazenamento de dados sem esquemas, como o Bigtable, necessitará que você repense na maneira como faz as coisas. No entanto, caso você crie seus aplicativos começando com um modelo de domínio, então, você sentirá que a estrutura sem esquemas do Bigtable é mais natural.

Construído para escalar

Novas soluções surgem com os novos problemas de aplicativos da Web altamente escaláveis. O Facebook não conta com um banco de dados relacional para suas necessidades de armazenamento; ao invés disso, ele usa um armazenamento de chave/valor — essencialmente, um HashMap de alto desempenho. A solução interna, chamada de Cassandra, também é usada pelo Twitter e Digg e foi recentemente doada à Apache Software Foundation. A Google é uma outra entidade da Web cujo crescimento explosivo levou à procura de armazenamento de dados não relacional — Bigtable é o resultado.

Armazenamentos de dados não relacionais não possuem tabelas de junção ou chaves primárias, nem mesmo a noção de chaves estrangeiras (apesar de que chaves de ambos os tipos estejam presentes em uma forma mais livre). Então, você provavelmente terminará frustrado caso tente usar modelagem relacional como uma base para a modelagem de dados em um banco de dados NoSQL. Começar por um modelo de domínio simplifica as coisas; na verdade, descobri que a flexibilidade da estrutura sem esquemas por debaixo do modelo de domínio é animadora.

A complexidade relativa de mudar de um modelo de dados relacional para um modelo de dados sem esquemas depende de sua abordagem: ou seja, se você começa com uma estrutura relacional ou baseada no domínio. Ao migrar para um armazenamento de dados como o CouchDB ou Bigtable, você realmente perde a engenhosidade de uma plataforma de persistência estabelecida, como a Hibernate (até agora, pelo menos). Por outro lado, há o efeito green-pasture de ser capaz de construir-se por si próprio. E, no processo, você aprenderá, de maneira aprofundada, sobre os armazenamentos de dados sem esquema.


Entidades e relacionamentos

Um armazenamento de dados sem esquema oferece a flexibilidade de criar um modelo de domínio com objetos primeiro (algo que estruturas mais novas como o Grails facilitam automaticamente). O avanço de seu trabalho torna-se, então, mapear seu domínio para o armazenamento de dados subjacente, que no caso do Google App Engine, não poderia ser mais fácil.

No artigo "Java development 2.0: Gaelyk for Google App Engine," introduzi o Gaelyk, uma estrutura baseada em Groovy que facilita trabalhar com o armazenamento de dados subjacente do Google. Uma grande parte desse artigo está focada na alavancagem do objeto Entity do Google. O exemplo a seguir (do artigo) mostra como entidades de objetos funcionam no Gaelyk.

Listagem 1. Persistência do objeto com Entidade
def ticket = new Entity("ticket")
ticket.officer = params.officer
ticket.license = params.plate
ticket.issuseDate = offensedate
ticket.location = params.location
ticket.notes = params.notes
ticket.offense = params.offense

Estrutura por objeto

O padrão de favorecer mais o modelo do objeto que a estrutura do banco de dados aparece em estruturas de aplicativos da Web modernas, como Grails e Ruby on Rails, que enfatizam a estrutura de um modelo do objeto e tratam da criação do esquema do banco de dados subjacente para você.

Essa abordagem quanto à persistência do objeto funciona, mas é fácil notar como ela pode se tornar fatigante caso usasse muito entidades de ticket — por exemplo, caso as estivesse criando (ou procurando) em vários servlets. Ter um servlet comum (ou Groovlet) para cuidar das tarefas para você aliviaria um pouco dessa responsabilidade. Uma opção mais natural — como demonstrarei — seria modelar um objeto Ticket.


De volta às corridas

Ao invés de refazer o exemplo dos tickets da introdução ao Gaelyk, farei algo novo e usarei um tema em execução neste artigo e construirei um aplicativo para demonstrar as técnicas discutidas.

Como mostra o diagrama many-to-many na Figura 1, uma Race possui muitos Runners e um Runner pode pertencer a muitas Races.

Figura 1. Corrida e corredores
A many-to-many diagram showing the relationship of Races to Runners.

Se eu fosse usar uma estrutura de tabela relacional para criar esse relacionamento, seriam necessárias, pelo menos, três tabelas: sendo a terceira uma tabela de junção ligando um relacionamento many-to-many. Estou feliz por não estar preso ao modelo de dados relacional. Ao invés disso, usarei o Gaelyk (e o código Groovy) para mapear esse relacionamento many-to-many para a abstração do Bigtable do Google para o Google App Engine. O fato de o Gaelyk permitir que uma Entity seja tratada como um Map torna o processo bem simples.

Escalando com Shards

Sharding é uma forma de particionamento que replica uma estrutura de tabela nos nós, mas divide dados entre elas de maneira lógica. Por exemplo, um nó poderia ter todos os dados relacionados a contas que residem nos EUA e outro poderia ter todas as contas que residem na Europa. O desafio dos shards ocorre quando os nós possuem relacionamentos — ou seja, junções de shards cruzados. É um problema difícil de ser resolvido e, em muitos casos, acaba não sendo suportado. (Consulte a seção de Recursos para um link para a minha discussão com Max Ross, do Google, sobre sharding e o desafio da escalabilidade com bancos de dados relacionais.)

Um dos encantos do armazenamento de dados sem esquemas é que eu não tenho que saber tudo logo de início, ou seja, é possível acomodar mudanças mais facilmente do que seria possível com um esquema de banco de dados relacional. (Observe que não estou sugerindo que não é possível modificar um esquema; estou apenas dizendo que é possível fazer mudanças de maneira mais fácil sem um esquema.) Não irei definir propriedades nos meus objetos de domínio — deixarei isso para a natureza dinâmica do Groovy (que permite, em essência, tornar meus objetos do domínio em intermediários na comunicação com os objetos Entity do Google). Ao invés disso, usarei meu tempo para descobrir como eu quero encontrar objetos e manipular relacionamentos. Isso é algo que o NoSQL e as várias estruturas alavancando armazenamentos de dados sem esquemas ainda não possuem integrado.

A classe base Model

Começarei criando uma classe base que mantém uma instância de um objeto Entity. Então, permitirei que subclasses possuam propriedades dinâmicas que serão adicionadas à instância Entity correspondente através do método setProperty conveniente do Groovy. setProperty é invocado para qualquer método setter que realmente não exista em um objeto. (Se isso soar estranho, não se preocupe, pois lhe fará sentido ao vê-lo em ação.)

A Listagem 2 mostra minha primeira tentativa com uma instância Model para meu aplicativo de exemplo:

Listagem 2. Uma classe base Model simples
package com.b50.nosql

import com.google.appengine.api.datastore.DatastoreServiceFactory
import com.google.appengine.api.datastore.Entity

abstract class Model {

 def entity
 static def datastore = DatastoreServiceFactory.datastoreService

 public Model(){
  super()
 }

 public Model(params){
  this.@entity = new Entity(this.getClass().simpleName)
  params.each{ key, val ->
   this.setProperty key, val
  }
 }

 def getProperty(String name) {
  if(name.equals("id")){
   return entity.key.id
  }else{
   return entity."${name}"
  }
 }

 void setProperty(String name, value) {
  entity."${name}" = value
 }

 def save(){
  this.entity.save()
 }	
}

Observe como a classe abstrata define um construtor que toma um Map das propriedades — é sempre possível adicionar mais construtores mais tarde e é o que farei em breve. Essa configuração é bem conveniente para estruturas da Web, que frequentemente agem fora dos parâmetros que estão sendo enviados de um formulário. Gaelyk e Grails englobam tais parâmetros em um objeto chamado params. O construtor repete esse Map e invoca o método setProperty para cada par de chaves/valores.

Olhando para o método setProperty é possível ver que a chave está definida para o nome da propriedade daentity subjacente, enquanto que o valor correspondente é o valor da entity.

Truques do Groovy

Como mencionado anteriormente, a natureza dinâmica do Groovy permite capturar chamadas de método para propriedades que não existam através de métodos get e setProperty. Assim, subclasses do Model na Listagem 2 não têm que definir propriedades próprias — elas simplesmente delegam qualquer chamada para uma propriedade para um objeto entity subjacente.

O código na Listagem 2 faz algumas outras coisas que são exclusivas do Groovy e que valem a pena ser destacadas. Primeiramente, posso ignorar o método acessador de uma propriedade pré-anexando @ à propriedade. Tenho que fazer isso para a referência do objeto entity no construtor, caso contrário, invocaria o método setProperty. Invocar setProperty nessa junção interromperia, obviamente, o padrão, já que a variável entity no método setProperty seria null.

Em segundo lugar, a chamada this.getClass().simpleName, no construtor, define o "tipo" de entity— a propriedade simpleName irá produzir um nome de subclasse sem um prefixo do pacote (observe que simpleName é, realmente, uma chamada para getSimpleName, mas que o Groovy permite que eu tente acessar uma propriedade sem a chamada de método JavaBeans-esque correspondente.)

Finalmente, se uma chamada é realizada para a propriedade id (ou seja, a chave do objeto), o método getProperty é inteligente o bastante para perguntar à key subjacente por seu id. No Google App Engine, as propriedades da key das entities são automaticamente geradas.

A subclasse Race

Definindo a subclasse Race é tão fácil quanto parece, como mostra a Listagem 3:

Listagem 3. Uma subclasse Race
package com.b50.nosql

class Race extends Model {
 public Race(params){
  super(params)
 }
}

Quando uma subclasse é instanciada com uma lista de parâmetros (ou seja, um Map contendo pares de chave/valores) uma entity correspondente é criada na memória. Para persisti-la, necessito somente invocar o método save.

Listagem 4. Criando uma instância Race e salvando-a em um armazenamento de dados do GAE
import com.b50.nosql.Runner

def iparams = [:]
                              
def formatter = new SimpleDateFormat("MM/dd/yyyy")
def rdate = formatter.parse("04/17/2010")
              
iparams["name"] = "Charlottesville Marathon"
iparams["date"] = rdate
iparams["distance"] = 26.2 as double

def race = new Race(iparams)
race.save()

Na Listagem 4, que é um Groovelet, um Map (chamado iparams) é criado com três propriedades — um nome, uma data e uma distância para a corrida. (Observe que no Groovy, um Map é criado através de [:].) Uma nova instância de Race é criada e, consequentemente, salva no armazenamento de dados subjacente através do método save.

É possível verificar o armazenamento de dados subjacente através do console do Google App Engine para ver que os dados estão realmente lá, como mostra a Figura 2:

Figura 2. Visualizando a Race recém-criada
Viewing the newly created Race in the Google App Engine console.

Métodos localizadores produzem Entidades persistidas

Agora que possuo uma Entity salva, seria útil ter a capacidade de recuperá-la; subsequentemente, é possível adicionar um método "localizador". Neste caso, o transformarei em um método de classes (static) e permitirei que as Races sejam encontradas pelo nome (ou seja, farei uma busca baseada na propriedade name). Mais tarde, é sempre possível adicionar outros localizadores para buscar por outras propriedades.

Também adotarei uma convenção para meus localizadores, especificando que qualquer localizador sem a palavra all em seu nome tem o objetivo de encontrar one instance, ou seja, uma instância. Localizadores com a palavra all (como em findAllByName) podem retornar uma Collection ou uma List de instâncias. A Listagem 5 mostra o localizador findByName:

Listagem 5. Um localizador simples efetuando uma busca baseada em um nome de Entidade
static def findByName(name){
 def query = new Query(Race.class.simpleName)
 query.addFilter("name", Query.FilterOperator.EQUAL, name)
 def preparedQuery = this.datastore.prepare(query)
 if(preparedQuery.countEntities() > 1){
  return new Race(preparedQuery.asList(withLimit(1))[0])
 }else{
  return new Race(preparedQuery.asSingleEntity())
 }
}

Esse localizador simples usa tipos Query e PreparedQuery do Google App Engine para encontrar uma entidade do tipo "Race", cujo nome corresponda (exatamente) ao que estiver definido. Caso mais de uma Race corresponda a esse critério, o localizador retornará o primeiro de uma lista, como instruído pelo limite de paginação de 1 (withLimit(1)).

O findAllByName correspondente seria similar, mas com um parâmetro adicionado de quantos você deseja?, como mostra a Listagem 6:

Listagem 6. Encontrar todos por nome
static def findAllByName(name, pagination=10){
 def query = new Query(Race.class.getSimpleName())
 query.addFilter("name", Query.FilterOperator.EQUAL, name)
 def preparedQuery = this.datastore.prepare(query)
 def entities = preparedQuery.asList(withLimit(pagination as int))
 return entities.collect { new Race(it as Entity) }
}

Como o localizador previamente definido, findAllByName encontra instâncias Race por nome, mas ele retorna todas as Races. A propósito, método collect Groovy é mais engenhoso: ele permite que eu inclua um loop correspondente que cria instâncias Race. Observe como o Groovy também permite valores padrões para parâmetros de método; assim, caso eu não defina um segundo valor pagination terá o valor 10.

Listagem 7. Localizadores em ação
def nrace = Race.findByName("Charlottesville Marathon")
assert nrace.distance == 26.2

def races = Race.findAllByName("Charlottesville Marathon")
assert races.class == ArrayList.class

Os localizadores na Listagem 7 funcionam da maneira esperada: findByName retorna uma instância, enquanto findAllByName uma Collection (considerando que haja mais de uma "Maratona de Charlottesville").

Os objetos Runner não são muito diferentes

Agora que me sinto confortável para criar e encontrar instâncias de Race, estou pronto para criar um objeto Runner rápido. O processo é tão fácil como foi a criação da instância Race inicial; simplesmente estendo o Model, como mostra a Listagem 8:

Listagem 8. Um Runner é muito fácil
package com.b50.nosql

class Runner extends Model{
 public Runner(params){
  super(params)
 }
}

Olhando para a Listagem 8, tenho a sensação de que estou quase na linha de chegada. Ainda tenho que criar os links entre os corredores e as corridas. E, é claro, farei a modelagem como relacionamentos many-to-many, pois espero que meus corredores realizem mais de uma corrida.


Modelagem do domínio sem um esquema

A abstração do Google App Engine sobre o Bigtable não é orientada ao objeto; ou seja, não é possível salvar relacionamentos dessa maneira, mas é possível compartilhar chaves. Consequentemente, a fim de modelar o relacionamento entre Races e Runners, armazenarei uma lista de chaves Runner dentro de cada instância de Race e vice-versa.

No entanto, terei que adicionar um pouco de lógica em meu mecanismo de compartilhamento de chaves, pois quero que minha API resultante seja natural — não quero pedir a uma Race por uma lista de chaves Runner, eu quero uma lista de Runners. Felizmente, isso não é difícil.

Na Listagem 9, adicionei dois métodos à instância Race. Quando uma instância Runner é passada para o método addRunner seu id correspondente é adicionado à Collection de ids na propriedade runners da entity subjacente. Caso haja uma collection existente de runners, a nova chave instância Runner é adicionada a ela; caso contrário, uma nova Collection é criada e chave do Runner (a propriedade id na entidade) é adicionada a ela.

Listagem 9. Adicionando e recuperando corredores
def addRunner(runner){
 if(this.@entity.runners){
  this.@entity.runners << runner.id
 }else{
  this.@entity.runners = [runner.id]
 }
}

def getRunners(){
 return this.@entity.runners.collect {
  new Runner( this.getEntity(Runner.class.simpleName, it) )
 }
}

Quando o método getRunners na Listagem 9 é invocado, uma coleta de instâncias Runner é criada a partir da coleção subjacente de ids. Assim, um novo método (getEntity) é definido na classe Model como mostra a Listagem 10:

Listagem 10. Criando uma entidade a partir de um ID
def getEntity(entityType, id){
 def key = KeyFactory.createKey(entityType, id)			
 return this.@datastore.get(key)
}

O método getEntity usa a classe KeyFactory do Google para criar a chave subjacente que pode ser usada para encontrar uma entidade individual dentro do armazenamento de dados.

Finalmente, um novo construtor que aceita um tipo de entidade é definido, como mostra a Listagem 11:

Listagem 11. Um construtor recém-adicionado
public Model(Entity entity){
 this.@entity = entity
}

Como é possível ver nas Listagens 9, 10, e 11 e no modelo de objeto da Figura 1, é possível adicionar um Runner a qualquer Race e também é possível obter uma lista de instâncias Runner de qualquer Race. Na Listagem 12, crio uma ligação similar do lado da equação do Runner. A Listagem 12 mostra os novos métodos da classe do Runner.

Listagem 12. Corredores e suas corridas
def addRace(race){
 if(this.@entity.races){
  this.@entity.races << race.id
 }else{
  this.@entity.races = [race.id]
 }
}

def getRaces(){
 return this.@entity.races.collect {
  new Race( this.getEntity(Race.class.simpleName, it) )
 }
}

Dessa maneira, consegui modelar dois objetos de domínio com um armazenamento de dados sem esquemas.

Terminando a corrida com alguns corredores

Agora, tudo que necessito criar é uma instância Runner e adicioná-la a uma Race. Caso queira que o relacionamento seja bidirecional, como meu modelo de objeto na Figura 1 mostra, também posso adicionar instâncias Race aos Runners, como mostra a Listagem 13:

Listagem 13. Corredores com suas corridas
def runner = new Runner([fname:"Chris", lname:"Smith", date:34])
runner.save()

race.addRunner(runner)
race.save()

runner.addRace(race)
runner.save()

Após adicionar um novo Runner à race e a chamada para save da Race, o armazenamento de dados foi atualizado com uma lista de IDs, como mostra a captura de tela na Figura 3:

Figura 3. Visualizando a nova propriedade de corredores em uma corrida
Viewing the new property of runners in a race.

Examinando de perto os dados no Google App Engine, é possível ver que uma entidade Race possui agora uma list de Runners, como mostra a Figura 4.

Figura 4. Visualizando a nova lista de corredores
Viewing the new list of runners.

Do mesmo modo, antes de adicionar uma Race a uma instância Runner recém-criada, a propriedade não existe, como mostra a Figura 5.

Figura 5. Um corredor sem uma corrida
A runner without a race

Porém, depois de associar uma Race a um Runner, o armazenamento de dados adiciona a nova list de ids de races.

Figura 6. Um corredor fora das corridas
A runner off to the races.

A flexibilidade do armazenamento de dados sem esquema é animadora — propriedades são adicionadas automaticamente ao armazenamento subjacente sob demanda. Como desenvolvedor, não preciso atualizar ou alterar um esquema, muito menos implementar!


Prós e contras do NoSQL

Há, é claro, prós e contras da modelagem de dados sem esquemas. Uma vantagem do aplicativo Back to the Races é que ele é bastante flexível. Caso eu decida adicionar uma nova propriedade ao Runner (como um SSN), não há muito o que ser feito —na verdade, se eu incluí-la nos parâmetros do construtor, ela estará lá. O que acontece com as instâncias mais antigas que ainda não foram criadas com um SSN? Nada. Elas possuem um campo que é null.

Leitores rápidos

A velocidade é um fator importante no argumento NoSQL versus relacional. Para um Web site moderno passando dados para, potencialmente, milhões de usuários (pense no crescente número de 400 milhões de usuários do Facebook), o modelo relacional é simplesmente muito lento, sem mencionar caro. Os armazenamentos de dados NoSQL, em comparação, são extremamente rápidos no que diz respeito a leituras.

Por outro lado, está claro que troquei consistência e integridade por eficiência. A arquitetura de dados atual do aplicativo não me deixa com nenhuma restrição — seria possível, teoricamente, criar um número infinito de instâncias do mesmo objeto. Sob a manipulação de chaves do Google App Engine, todas elas teriam chaves únicas, mas todo o restante seria idêntico. O pior é que exclusões em cascata não existem, então, se eu usasse a mesma técnica para modelar relacionamentos one-to-many e o pai fosse removido, eu terminaria com um filho ultrapassado. É claro que seria possível implementar minha própria verificação de integridade — mas isso é importante: Eu mesmo teria que fazer isso (assim como fiz todo o restante).

Usar armazenamentos de dados sem esquemas requer disciplina. Caso crie vários tipos de Races— algumas com nomes, outras sem nome, algumas com uma propriedade date e outras com uma propriedade race_date— estaria dando um tiro em meu próprio pé (e no pé de todas as outras pessoas que alavancarem meu código).

É claro que ainda é possível usar JDO e JPA com o Google App Engine. Tendo usando tanto os modelos relacionais quanto o sem esquemas em projetos múltiplos, posso dizer que a API de baixo nível do Gaelyk é a mais flexível e divertida para se trabalhar. Uma outra vantagem de usar o Gaelyk é obter um entendimento melhor de armazenamentos de dados Bigtable e sem esquemas, no geral.


Concluindo

Tendências vem e vão e, algumas vezes, é seguro ignorá-las (conselho sábio vindo de um rapaz com um guarda-roupa cheio de ternos antiquados). Mas o NoSQL se parece menos com uma tendência e mais como uma base emergente para o desenvolvimento de aplicativos da Web altamente escaláveis. No entanto, os bancos de dados NoSQL não substituirão o RDBMS; eles irão suplementá-lo. Uma grande quantidade de ferramentas e estruturas bem sucedidas residem sobre bancos de dados relacionais e os próprios RDBMSs parecem não correr risco de perder popularidade.

O que os bancos de dados NoSQL fazem é, finalmente, apresentar uma alternativa adequada ao modelo de dados objeto-relacional. Eles mostram que algo diferente é possível e — para casos de uso específico e altamente atrativo — melhor Bancos de dados sem esquemas são mais adequados para aplicativos da Web multinós que necessitam de recuperação de dados rápida e escalabilidade. Como um efeito colateral, eles estão ensinando desenvolvedores a abordarem a modelagem de dados a partir de uma perspectiva orientada ao domínio, ao invés de uma perspectiva relacional.

Recursos

Aprender

Obter produtos e tecnologias

  • Gaelyk: Comece a usar a estrutura leve de desenvolvimento de aplicativos Groovy para o Google App Engine.

Discutir

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, Software livre
ArticleID=493194
ArticleTitle=Desenvolvimento em Java 2.0: NoSQL
publish-date=05282010