Conteúdo


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

Comments

Conteúdos da série:

Esse conteúdo é a parte # de # na série: Java development 2.0

Fique ligado em conteúdos adicionais dessa série.

Esse conteúdo é parte da série:Java development 2.0

Fique ligado em conteúdos adicionais dessa série.

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.

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.

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.

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 para download


Temas relacionados

  • Java development 2.0: Esta série do developerWorks explora tecnologias que estão redefinindo a paisagem de desenvolvimento Java; os tópicos recentes incluem MongoDB (setembro de 2010), SimpleDB with SimpleJPA (agosto de 2010) e Google App Engine (agosto de 2009).
  • "Java development 2.0: NoSQL" (Andrew Glover, developerWorks, maio de 2010): Descubra por que armazenamentos de dados NoSQL como Bigtable e CouchDB estão mudando da margem para o centro.
  • "Java Object Persistence: State of the Union" (InfoQ panel, março de 2008): Entre na máquina do tempo da Internet para um lembrete de como parecia o futuro da persistência Java antes do NoSQL (apenas alguns anos atrás). Inclui uma discussão disse alto nível sobre a incompatibilidade de impedância relacional de objeto.
  • "OAuth-ing Twitter with Twitter4J" (Andrew Glover, The Disco Blog, setembro de 2010): O Twitter e a Twitter4J não permitem mais autorização básica, — o que torna uma boa ideia aprender sobre OAuth.
  • "OAuth-OpenID: You're Barking Up the Wrong Tree if you Think They're the Same Thing" (Michael Mahemoff, Software As She's Developed, novembro de 2007): Descubra o que o OAuth tem enão tem em comum com o OpenID.

Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

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