Java development 2.0: Mineração no Twitter com Objectify-Appengine, Parte 1

Modelagem de domínio de objeto para armazenamentos de dados não relacionais

Objectify-Appengine é uma classe emergente de ferramentas que estendem a conveniência do NoSQL, neste caso fornecendo uma camada de mapeamento estilo Hibernate entre o aplicativo e o armazenamento de dados GAE. Obtenha uma introdução este mês à API prática do Objectify, simples para o JPA (mas não dependente dele). Andrew Glover percorre as etapas de mapear retweets do Twitter no Bigtable, em preparação à sua implementação no Google App Engine.

Andrew Glover, Author and developer, Beacon50

Andrew Glover é desenvolvedor, autor, palestrante e empreendedor com uma paixão por desenvolvimento direcionado por comportamento, integração contínua e desenvolvimento de software Agile. Ele é o fundador da easyb , estrutura Behavior-Driven Development (BDD) e é o coautor de três livros: Continuous Integration, Groovy in Action e Java Testing Patterns. Você pode acompanhá-lo em seu blog e seguindo-o no Twitter.



13/Dez/2010

Não é segredo aos leitores desta série que os armazenamentos de dados NoSQL inspiraram uma explosão de inovação no mundo Java™ nos dois últimos anos. Além dos próprios armazenamentos de dados (como CouchDB, MongoDB e Bigtable), começamos a ver ferramentas que estendem sua utilidade. As bibliotecas de mapeamento semelhantes a ORM conduzem esse pacote tratando um dos desafios perniciosos do NoSQL: como mapear de maneira eficiente objetos Java antigos simples (a moeda comum dos armazenamentos de dados sem esquema) e tornar esses objetos úteis, muito parecido ao que Hibernate faz para os armazenamentos de dados relacionais.

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 5 coisas, Andrew Glover explora o espectro de tecnologias e ferramentas que torna possível esse novo paradigma de desenvolvimento Java.

SimpleJPA é um exemplo nessa categoria: uma biblioteca de persistência que permite aos objetos anotados JPA trabalhar quase sem interrupção com o SimpleDB da Amazon. Eu apresentei o SimpleJPA algumas colunas atrás, mas observei que embora seja baseado em JPA, não implementa a especificação JPA integral. Isso se deve ao fato de que JPA é desejado para trabalhar com bancos de dados relacionais, que SimpleDB (e seu ajudante, SimpleJPA) evita. Outros projetos nem mesmo tentam imitar a especificação JPA completa: apenas emprestam dela o que desejam. Um desses projetos — Objectify-Appengine — é o assunto da coluna deste mês.

Objectify-Appengine: Uma biblioteca de mapeamento de objeto não relacional

Objectify-Appengine ou Objectify é uma biblioteca semelhante a ORM que simplifica a persistência de dados no Bigtable e, desse modo, no GAE. Como camada de mapeamento, Objectify insere a si próprio por meio de uma API elegante entre o POJOs e o equipamento pesado da Google. Use um subconjunto familiar de anotações JPA (embora Objectify não implemente a especificação integral) junto com um punhado de anotações do ciclo de vida, para persistir e recuperar dados na forma de objetos Java. Em essência, Objectify é um Hibernate peso leve projetado expressamente para o Bigtable da Google.

Semelhante a ORM?

Mapeamento relacional de objeto é a maneira mais comum de superar a chamada incompatibilidade de impedância entre modelos de dados orientados a objetos e bancos de dados relacionais (consulte Recursos ). No mundo não relacional não existe incompatibilidade de impedância, por isso Objectify não é realmente uma biblioteca ORM: é mais como uma biblioteca ONRM (object non-relational mapping). "Semelhante a ORM" é uma redução conveniente para aqueles de nós que sofrem de fadiga de acrônimos.

Objectify é semelhante a Hibernate no sentido que permite mapear e aproveitar POJOS com relação a Bigtable, que você visualiza como uma abstração em GAE. Além de um subconjunto de anotações JPA, Objectify emprega anotações próprias que tratam dos recursos exclusivos do armazenamento de dados GAE. Objectify também permite relacionamentos e expõe uma interface de consulta que suporta as noções GAE de filtragem e classificação.

Nas próximas sessões desenvolvemos um aplicativo de exemplo que permite experimentar mapeamento e persistência de dados com Objectify, usando o Bigtable da Google para armazenar dados de aplicativo. Na segunda metade deste artigo, aproveitaremos nossos dados em um aplicativo da Web GAE.


Grande figura, Bigtable

Estou dando uma folga ao domínio "corridas e corredores" e podemos ignorar os chamados de estacionamento também. Em vez disso, iremos fazer mineração no Twitter — outro domínio de aplicativo familiar para os que leram a introdução do mês passado ao MongoDB. Desta vez investigaremos não apenas quem nos retweeted (ou a mim ou a você) no Twitter, mas qual dos nossos retweeters principais são os mais influentes.

Para este aplicativo precisaremos criar duas classes de domínio: Retweet eUser. O objeto Retweet representa obviamente um retweet de uma conta do Twitter. O objeto User representa o usuário do Twitter em cujos dados da conta estamos fazendo mineração. (Observe que esse objeto User é diferente do objeto GAE User .) Cada Retweet tem um relacionamento com um User.

Sobre Bigtable

Bigtable é um armazenamento de dados NoSQL orientado a colunas, acessível através do Google App Engine. Em vez dos esquemas encontrados em um banco de dados relacional, Bigtable é basicamente um mapa de persistência distribuído de forma massiva — que permite consultas sobre chaves e atributos dos valores de dados subjacentes. Bigtable para GAE é muito semelhante a SimpleDB para serviços da Web Amazon.

Objectify aproveita a API Entity de nível baixo do Google para mapear intuitivamente objetos de domínio para o armazenamento de dados GAE. Apresentei a API Entity em um artigo anterior (consulte Recursos ), por isso não vou discuti-la muito aqui. O principal que se deve saber é que na API Entity os nomes de domínio tornam-se o tipo kind— ou seja, User mapeará logicamente para um User kind— que é semelhante a uma tabela em termos relacionais. (Para uma analogia mais próxima, pense em um kind como um mapa que contém chaves e valores.) Os atributos de domínio são essencialmente nomes de colunas em termos relacionais e os valores de atributos são valores de colunas. Ao contrário do SimpleDB da Amazon, o GAE suporta um rico conjunto de tipos de dados incluindo blobs (consulte Recursos ) e todos os tipos de números, datas e listas.


Definição de classe no Objectify

O objeto User será bem básico: apenas um nome e dois atributos relacionados à implementação OAuth do Twitter, que aproveitaremos para essa abordagem intuitiva à autorização. Ao invés de armazenar a senha de um usuário, os usuários em um paradigma OAuth armazena tokens, que representam a permissão do usuário de atuar em seu nome. OAuth opera de maneira semelhante a um cartão de crédito, mas com dados de autorização como a moeda. Em vez de dar seu nome e senha a cada Web site, você dá aos sites permissão para acessar essas informações. (OAuth é semelhante ao OpenID — mas diferente; consulte Recursos para saber mais.)

Listagem 1. O início de um objeto User
import javax.persistence.Id;

public class User {
 @Id
 private String name;	
 private String token;
 private String tokenSecret;

 public User() {
  super();	
 }

 public User(String name, String token, String tokenSecret) {
  super();
  this.name = name;
  this.token = token;
  this.tokenSecret = tokenSecret;	
 }

 public String getName() {
  return name;
 }

 //...
}

Como é possível ver na listagem 1, o único código específico de persistência associado à classe User é a anotação @Id . @Id é JDO padrão, o que dá para saber do import. O armazenamento de dados GAE permite que os identificadores ou chaves sejam Strings ou Longs/longs. Na listagem 1, especifiquei o nome da conta do Twitter como a chave. Também criei um construtor que toma todas as três propriedades, o que facilitará a criação de novas instâncias. Observe que eu realmente não preciso definir getters e setters para esse o objeto ser utilizado no Objectify (embora precisarei deles para acessar ou configurar propriedades programaticamente!).

Quando o objeto User for persistido para o banco de dados subjacente, será um User kind. Essa entidade terá uma chave denominada name e duas outras propriedades: token etokenSecret, as quais são Strings. Bem fácil, hein?

Os poderes do usuário

Em seguida, adicionarei um pouquinho de comportamento à minha classe de domínio User . Farei um método de classe que possibilita aos objetos User se localizarem pelo nome.

Listagem 2. Localizando usuários pelo nome
 //inside User.java...
 private static Objectify getService() {
  return ObjectifyService.begin();
 }

 public static User findByName(String name){
  Objectify service = getService();
  return service.get(User.class, name);
 }

Algumas coisas estão acontecendo no User recém-cunhado na listagem 2. Para aproveitar o Objectify, preciso dispará-lo, por assim dizer. Assim, pegue uma instância do Objectify, que trata todas as operações semelhantes a CRUD. A classe Objectify pode ser considerada um tanto semelhante à classe SessionFactory do Hibernate.

Objectify tem uma API simples. Para localizar uma entidade individual pela sua chave, basta chamar o método get , que toma um tipo de classe e a chave. Assim, na listagem 2, faço uma chamada para get com a classe User subjacente e o nome desejado. Observe também que as exceções do Objectify estão desmarcadas, — o que significa que não preciso me preocupar sobre capturar vários tipos de exceções . Isso não significa que as exceções não ocorrem; apenas não precisam ser tratadas no tempo de compilação, por si. Por exemplo, o método get lançará um NotFoundException se o User kind não puder ser localizado. (Objectify também fornece um método find , que em vez disso retorna null.)

A seguir vem o comportamento da instância: desejo que minhas instâncias User suportem a capacidade de listar todos os retweets em ordem de influência, o que significa que preciso incluir um outro método. Mas primeiro vou modelar meu Retweet .

Quantos Retweets?

Retweet, como dá para imaginar, representa um retweet do Twitter. Esse objeto conterá vários atributos, incluindo um relacionamento de volta com o User devido.

Já mencionei que um identificador ou uma chave no armazenamento de dados GAE deve ser uma String ou uma Long/long. As chaves no armazenamento de dados GAE também são exclusivas, como seriam em um banco de dados tradicional. É por isso que a chave do objeto User é o nome de uma conta do Twitter, que é inerentemente exclusivo. A chave do objeto Retweet na Listagem 3 será uma combinação do tweet id e o usuário que fez o retweet. (Twitter não permite tweeting o mesmo texto duas vezes, por isso por enquanto essa chave faz sentido.)

Listagem 3. Definindo Retweet
import javax.persistence.Id;
import com.googlecode.objectify.Key;

public class Retweet {
 @Id
 private String id;
 private String userName;
 private Long tweetId;
 private Date date;
 private String tweet;
 private Long influence;
 private Key<User> owner;

 public Retweet() {
  super();
 }

 public Retweet(String userName, Long tweetId, Date date, String tweet,
   Long influence) {
  super();
  this.id = tweetId.toString() + userName;
  this.userName = userName;
  this.tweetId = tweetId;
  this.date = date;
  this.tweet = tweet;
  this.influence = influence;
 }

 public void setOwner(User owner) {
  this.owner = new Key<User>(User.class, owner.getName());
 }
 //...
}

Observe que a chave na listagem 3, id, é uma String; ela combina o tweetId e o userName. O médodo setOwner mostrado na listagem 3 fará mais sentido após eu explicar os relacionamentos.


Modelando relacionamentos

Retweets e Users neste aplicativo têm um relacionamento; ou seja, todo User contém uma coleção lógica de Retweets e todo Retweet contém um link direto de volta para seu User. Olhe de novo a listagem 3 e poderá observar algo incomum: um objeto Retweet tem um objeto Key de tipo User.

O uso de Chaves pelo Objectify em vez de referências do objeto reflete o armazenamento de dados não tradicional do GAE, que entre outras coisas não tem integridade referencial.

O relacionamento entre os dois objetos precisa realmente apenas de uma conexão rígida no Retweet .É por isso que uma instância do Retweet contém uma Key direta para uma instância do User . Consequentemente, uma instância do User não precisa realmente persistir RetweetKeys no seu lado — uma instância de User pode simplesmente consultar retweets para aqueles que vinculam de volta para si mesmos.

Entretanto, para tornar a interação entre os objetos mais intuitiva, na Listagem 4 incluí no User alguns métodos que aceitam Retweet. Estes métodos firmam o relacionamento entre os dois objetos: User agora estabelece diretamente sua propriedade de um Retweet.

Listagem 4. Incluindo Retweets em um Usuário
public void addRetweet(Retweet retweet){
 retweet.setOwner(this);
 Objectify service = getService();
 service.put(retweet);
}

public void addRetweets(List<Retweet> retweets){
 for(Retweet retweet: retweets){
  retweet.setOwner(this);
 }

 Objectify service = getService();
 service.put(retweets);
}

Na listagem 4, incluímos dois novos métodos no objeto de domínio User . Um trabalha com uma coleção de Retweets, enquanto o outro trabalha em apenas uma instância. Poderá ser observado que a referência service foi definida previamente na listagem 2 e seu método put está sobrecarregado para trabalhar com instâncias únicas e Listas. O relacionamento nesse caso também é tratado pelo objeto devido — a instância de User inclui a si própria no Retweet. Assim, Retweets são criados separadamente, mas quando forem incluídos em uma instância de um User, são formalmente conectados.


Mineração no Twitter

Minha próxima etapa é incluir método semelhante a localizador no User . Esse método permitirá listar todos os Retweets devidos de modo a influenciar — ou seja, de uma contagem devida inicial para contas que o retweeted. Controlarei da conta com mais seguidores para a conta com menos.

Listagem 5. Retweets por influência
public List<Retweet> listAllRetweetsByInfluence(){
 Objectify service = getService();
 return service.query(Retweet.class).filter("owner", this).order("-influence").list();
}

O código na listagem 5 reside no User . Ele retorna uma Lista de Retweets ordenados pela sua propriedade influence , que é um número inteiro. O "-" nesse caso indica que desejoRetweets em ordem decrescente, do mais alto para o mais baixo. Observe o código de consulta do Objectify: a instância service suporta filtragem por propriedade (neste caso owner) e até mesmo ordenação dos resultados. Observe também o padrão continuado de exceções não marcadas, o que mantém o código notavelmente conciso.

Consultando múltiplas propriedades

O armazenamento de dados GAE aproveita um índice para qualquer consulta emitida. Isso permite leituras rápidas, pois as propriedades únicas em uma entidade são indexadas automaticamente. Mas se você terminar consultando por múltiplas propriedades (como fiz na listagem 5, consulte por owner e, em seguida, por influence), é necessário fornecer um arquivo datastore-index.xml para GAE. Isso dá ao GAE aviso avançado de uma consulta recebida. A Listagem 6 é o índice customizado que torna possível consultar múltiplas propriedades:

Listagem 6. Definindo um índice customizado para o armazenamento de dados GAE
<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="true">
 <datastore-index kind="Retweet" ancestor="false">
  <property name="owner" direction="asc" />
  <property name="influence" direction="desc" />
 </datastore-index>
</datastore-indexes>

Persistência

Finalmente, preciso incluir algum recurso para persistir meus objetos de domínio. Pode ter sido observado que existe um fluxo de trabalho implícito para o relacionamento entre os objetos User eRetweet . Especificamente, preciso ter uma instância User criada (e salva no armazenamento de dados GAE) antes de poder incluir logicamente Retweets relacionados.

Na Listagem 7, incluo um método save no objeto User , mas observe que não preciso de um no Retweet .Retweets são salvos automaticamente ao serem incluídos em uma instância User— , o que é feito por meio dos métodos addRetweet eaddRetweets (observe as chamadas para service.put na listagem 4).

Listagem 7. Salvando usuários
public void save(){
 Objectify service = getService();
 service.put(this);
}

Pode ver como esse código é conciso? É a API do Objectify no trabalho.


Registrando classes de domínio

Estou quase pronto para compor meu aplicativo de mineração no Twitter, o que envolve um pouco de ligação com a API dos Servlets. Usarei servlets para tratar do registro no Twitter, extraindo dados de retweet e, finalmente, exibindo um relatório elegante. No momento vou deixar isso para a sua imaginação e focar em um único requisito de trabalho com o Objectify: registrar classes de domínio manualmente.

O Objectify não carrega classes de domínio automaticamente, — o que significa que ele não verifica entidades no caminho de classe. É necessário indicar antecipadamente ao Objectify quais classes são especiais, para que mais tarde seja possível acessá-las e usá-las por meio da API do Objectify. O objeto ObjectifyService permite registrar classes de domínio, o que naturalmente é necessário fazer antes de chamar seu comportamento semelhante a CRUD. Felizmente, como estou escrevendo um aplicativo da Web simples para ser implementado no GAE, posso usar a API do Servlet para registrar minhas duas classes em uma instância ServletContextListener .

ServletContextListeners tem dois métodos, um chamado quando um contexto é criado, o outro quando um contexto é destruído. Contextos são criados ao ser disparado pela primeira vez um aplicativo da Web, assim funcionará muito bem.

Listagem 8. Registrando objetos de domínio
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.googlecode.objectify.ObjectifyService;

public class ContextInitializer implements ServletContextListener {

 public void contextDestroyed(ServletContextEvent arg) {}

 public void contextInitialized(ServletContextEvent arg) {
  ObjectifyService.register(Retweet.class);
  ObjectifyService.register(User.class);
 }
}

A listagem 8 mostra uma implementação simples de um ServletContextListener, em que registro minhas duas classes de domínio do Objectify, User eRetweet. Conforme a API do Servlet, instâncias do ServletContextListener são registradas em um web.xml . Quando meu aplicativo inicia nos servidores da Google, o código na Listagem 8 será chamado. Todos os futuros servlets que usam meus objetos de domínio funcionarão muito bem e com nada mais a fazer.


Conclusão da Parte 1

Neste ponto, escrevemos um par de classes e definimos seus relacionamentos e recursos semelhantes a CRUD, todos usando Objectify-Appengine. Você pode ter observado algumas coisas sobre a API do Objectify quando trabalhamos no aplicativo de amostra — como o fato de que tira muito do detalhamento do código Java normal. Ele também aproveita algumas anotações JPA padrão, suavizando assim o caminho dos desenvolvedores acostumados a trabalhar com estruturas realçadas por JPA como Hibernate. No geral, a API do Objectify torna a modelagem de domínio para GAE mais fácil e mais intuitiva, o que é um impulso à produtividade do desenvolvedor.

Na segunda metade deste artigo, levaremos nosso aplicativo de domínio para o próximo nível, conectando-o junto ao OAuth, a API do Twitter (via Twitter4J) e Ajax-plus-JSON. Tudo isso será ligeiramente complicado pelo fato de que estamos implementando no Google App Engine, o que coloca algumas restrições na implementação. Mas, no lado positivo, terminaremos com um aplicativo da Web baseado em nuvem realmente escalável. Explorarei mais essas placas no próximo mês, quando começarmos a preparar o aplicativo de amostra para implementação no GAE.

Recursos

Aprender

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=600652
ArticleTitle=Java development 2.0: Mineração no Twitter com Objectify-Appengine, Parte 1
publish-date=12132010