Desenvolvimento em Java 2.0: Armazenamento em nuvem com o SimpleDB da Amazon – Parte 2
Na primeira parte deste artigo, mostramos como otimizar a própria API
da Amazon para modelar um aplicativo de corrida no estilo CRUD.
Deixando de lado a exclusividade óbvia para a maioria dos
desenvolvedores de Java da abordagem somente de cadeia de caractere da
Amazon para tipos de dados, você já deve ter se flagrado olhando para a
API do Amazon com um certo ceticismo.
Afinal de contas, as APIs para
otimização de um banco de dados relacional são, no momento, bastante
padronizadas e bem planejadas e, talvez o mais importante, elas são
familiares.
Nos bastidores, muitas estruturas relacionais hoje implementam o
Java Persistence API. Isso torna a modelagem de objetos de domínio para
praticamente qualquer tipo de aplicativo Java fácil e familiar por toda
a gama de RDBMSs. É natural a resistência a aprender uma nova abordagem
para modelagem de domínio, quando você já domina uma que funciona e a
boa notícia é que com o SimpleDB, isso não é preciso.
Nesta segunda metade da minha introdução ao SimpleDB, mostrarei como
refatorar o aplicativo de corrida da parte 1 para ficar em conformidade
com a especificação da JPA. Depois iremos transportar o aplicativo para
SimpleJPA e descobrir algumas das formas como essa plataforma inovadora
de software livre pode tornar a adaptação para modelagem de domínio
NoSQL, e o armazenamento baseado em nuvem, um pouco mais fácil.
Por que o SimpleDB?
O SimpleDB da Amazon é um armazenamento de dados baseado em nuvem
bastante simples, mas substancialmente escalável e confiável. Devido à
sua base não relacional/NoSQL, o SimpleDB é flexível e incrivelmente
rápido.
Como parte da família de serviços da Web da Amazon, o SimpleDB
usa HTTP como mecanismo de comunicação subjacente, de modo que é capaz
de suportar ligações de linguagem, da linguagem Java a Ruby, C# e Perl.
O SimpleDB também tem um preço acessível: no licenciamento do SimpleDB,
paga-se somente pelos recursos consumidos, diferente do método mais
tradicional de comprar uma licença de antemão para uso e espaço
previstos.
Como parte da classe de armazenamento de dados NoSQL, ou não
relacional, que está surgindo, o SimpleDB está relacionado ao Bigtable
ou CouchDB do Google, também descritos nesta série.
Hibernate e JPA: Uma breve história
Muitos desenvolvedores de Java hoje aproveitam o Hibernate (e o
Spring) para persistência de dados. Além de ser um precursor do sucesso
do software livre, o Hibernate mudou o campo de ORM para melhor. Antes
do Hibernate, os desenvolvedores de Java tinham de lidar com a confusão
dos beans de entidade EJB; antes disso, nós basicamente preparávamos
nossos próprios ORMs ou comprávamos um de um fornecedor como a IBM. O
Hibernate eliminou toda essa complexidade e custo em nome de uma
plataforma de modelagem baseada em POJO que muitos de nós aceitamos
hoje.
Uma Java Persistence API foi criada em resposta à popularidade da
inovação do Hibernate de aproveitar POJOs para modelagem de dados.
Hoje, o EJB 3.0 implementa JPA, assim como o Google App Engine. O
próprio Hibernate é uma implementação de JPA, presumindo que você use o
Hibernate EntityManager.
Levando-se em conta como foi bom para os desenvolvedores de Java
modelar aplicativos centrados em dados usando POJOs, faz sentido que um
armazenamento de dados como o SimpleDB nos ofereça uma opção
semelhante.Afinal de contas, ele é praticamente um banco de dados, não
é?
Modelagem de dados com objetos
Para usar o SimpleJPA, precisamos trabalhar um pouco em nossos objetos Racer e Runner
, trazendo-os de volta para acelerar com a especificação de JPA.
Felizmente, os conceitos básicos de JPA são bem simples: você decora
POJOs normais com anotações e uma implementação EntityManager toma conta do resto não há necessidade de XML.
Duas das principais anotações usadas pela JPA são @Entity e @Id,
que especificam um POJO como persistente e delineiam sua chave de
identidade, respectivamente. Com o objetivo de converter nosso
aplicativo de corrida em JPA, também precisaremos de duas anotações
usadas para gerenciar relacionamentos: @OneToMany e @ManyToOne.
Na primeira metade deste artigo, mostrei como prosseguir com runners
e races. Nunca usei nenhum objeto para representar essas entidades, no
entanto acabei de usar a API bruta da Amazon para persistir as
propriedades das duas. Se quisesse modelar um relacionamento simples
entre um race e seus runners, faria como mostrado na listagem 1:
Listagem 1. Um objeto Race simples
public class Race {<br /> private String name;<br /> private String location;<br /> private double distance;<br /> private List<Runner> runners;<br /><br /> //setters and getters left out...<br />}Na listagem 1, especifiquei um objeto Race com quatro
propriedades, sendo que a última é uma Collection de runners.
Em seguida, posso criar um objeto Runner simples (mostrado na listagem
2) que contém o nome de cada runner (por enquanto, simplificarei o máximo possível) e SSN junto com a instância Race em que ele está correndo.
Listagem 2. Um Runner simples relacionado a um Race
public class Runner {<br /> private String name;<br /> private String ssn;<br /> private Race race;<br /><br /> //setters and getters left out...<br />}Como você pode ver nas listagens 1 e 2, modelei de forma lógica um relacionamento de muitos-para-um entre
runners e um race. Em uma situação real, provavelmente seria mais
apropriado fazer a ligação muitos-para-muitos (os runners normalmente
não participam de mais de um race?), mas vou simplificar as coisas.
Também deixarei de lado por enquanto os construtores, setters e
getters. Irei mostrá-los adiante.
Anotações em JPA
Deixar esses dois objetos prontos para SimpleJPA não é um desafio
tão terrível. Primeiro, tenho que indicar minha intenção de torná-los
persistíveis, incluindo a anotação @Entity em cada um. Também preciso definir os relacionamentos de forma adequada usando @OneToMany no objeto Race e @ManyToOne no Runner .
A anotação @Entity está anexada no nível de classe e
as anotações de relacionamento estão anexadas no nível getter. Tudo isso está demonstrado nas listagens 3 e 4:
Listagem 3. Um Race JPA detalhado
@Entity<br />public class Race {<br /> private String name;<br /> private String location;<br /> private double distance;<br /> private List<Runner> runners;<br /><br /> @OneToMany(mappedBy = "race")<br /> public List<Runner> getRunners() {<br /> return runners;<br /> }<br /><br /> //other setters and getters left out...<br />}Na listagem 3, decorei o método getRunners com umaanotação @OneToMany. Também especifiquei que o relacionamento pode ser encontrado usando a propriedade race na entidade Runner.
Na listagem 4, anotarei o método getRace de forma semelhanteno objeto Runner.
Listagem 4. Um Runner JPA detalhado
@Entity<br />public class Runner {<br /> private String name;<br /> private String ssn;<br /> private Race race;<br /><br /> @ManyToOne<br /> public Race getRace() {<br /> return race;<br /> }<br /><br /> //other setters and getters left out...<br />}A maioria dos armazenamentos de dados (relacionais ou não) precisa
de uma forma de descrever exclusividade entre os dados. Assim, se eu
quiser tornar esses dois objetos persistentes em um armazenamento de
dados, tenho de pelo menos adicionar IDs a eles. Na listagem 5,
adicionei uma propriedade id do tipo BigInteger ao objeto de domínio Race. Farei o mesmo em relação ao objeto Runner.
Listagem 5. Adição de um ID ao Race
@Entity<br />public class Race {<br /> private String name;<br /> private String location;<br /> private double distance;<br /> private List<Runner> runners;<br /> private BigInteger id;<br /><br /> @Id<br /> public BigInteger getId() {<br /> return id;<br /> }<br /><br /> @OneToMany(mappedBy = "race")<br /> public List<Runner> getRunners() {<br /> return runners;<br /> }<br /><br /> //other setters and getters left out...<br />}A anotação @Id na listagem 5 não fornece nenhuma informação sobre como o ID é gerenciado. O programa
irá presumir que estou fazendo isso de forma manual, em vez de usar uma
EntityManager, por exemplo.
Como inserir SimpleJPA
Até agora, não fiz nada específico com o SimpleDB. Os objetos Race e Runner
são anotados genericamente com anotações de JPA e podem persistir em
qualquer armazenamento de dados que seja suportado por uma
implementação de JPA. As opções incluem Oracle, DB2, MySQL e (como você
já deve ter adivinhado) SimpleDB.
A SimpleJPA é uma implementação de software livre de JPA
para o SimpleDB da Amazon. Embora ela não suporte toda a especificação
de JPA (por exemplo, você não pode participar de consultas JPA), ela
suporta um subconjunto grande o suficiente para que valha a pena
explorá-lo.
Uma grande vantagem de usar SimpleJPA é que ela tenta lidar continuamente com as questões lexicográficas que discuti na primeira metade deste artigo.
A SimpleJPA faz a conversão da cadeia de caractere e qualquer
preenchimento subsequente para objetos que dependem de tipos numéricos.
Para a maior parte, isso significa que você não precisa mudar seu
modelo de domínio para refletirtipos de String. (Há uma exceção a essa regra, que explicarei daqui a pouco).
Como a SimpleJPA é uma implementação de JPA, você pode usar com ela
objetos de domínio em conformidade com JPA facilmente. A SimpleJPA só
exige que você use IDs de String, mostrando que sua propriedade id deve ser java.lang.String. Para tornar as coisas mais fáceis, a SimpleJPA fornece a classe base IdedTimestampedBase, que gerencia a propriedade de ID do objeto do domínio, assim como os atributos de data created e updated. (Por baixo dos panos, a SimpleDB gera um exclusivo Id.)
Transporte do aplicativo para SimpleJPA
Para fazer com que as classes Race e Runner
estejam em conformidade com a SimpleJPA, eu poderia estender a classe
base conveniente da SimpleJPA ou alterar cada propriedade do id de BigInteger para String. Optei pela primeira opção, como mostrado na listagem 6:
Listagem 6. Mudança da Race para usar a classe base IdedTimestampedBase da SimpleJPA
@Entity<br />public class Race extends IdedTimestampedBase{<br /> private String name;<br /> private String location;<br /> private double distance;<br /> private List<Runner> runners;<br /><br /> @OneToMany(mappedBy = "race")<br /> public List<Runner> getRunners() {<br /> return runners;<br /> }<br /><br /> //other setters and getters left out...<br />}Não vou mostrar o mesmo código para Runner, mas fique à vontade para passar rapidamente por ele você mesmo: apenas estenda IdedTimestampedBase e remova a propriedade id de Runner.
A atualização dos IDs para Race e Runner é
a primeira etapa para fazer com que o aplicativo de corrida fique em
conformidade com a SimpleJPA. Em seguida, é preciso trocar tipos de
dados primitivos (como double, inte float) para objetos como Integer e BigDecimal.
Começarei com a propriedade distance de Race. Achei o BigDecimal mais confiável que o Double (no release atual de SimpleJPA), então mudei a propriedade distance de Race para BigDecimal, como mostrado na listagem 7:
Listagem 7. Mudança da distância para BigDecimal
@Entity<br />public class Race extends IdedTimestampedBase{<br /> private String name;<br /> private String location;<br /> private BigDecimal distance;<br /> private List<Runner> runners;<br /><br /> @OneToMany(mappedBy = "race")<br /> public List<Runner> getRunners() {<br /> return runners;<br /> }<br /><br /> //other setters and getters left out...<br />}Agora Runner e Race estão prontos para serem persistidos através de uma implementação de SimpleJPA.
Uso da SimpleJPA com o SimpleDB
Manipular seus objetos de domínio em relação ao SimpleDB com
SimpleJPA não é diferente de ir contra um banco de dados relacional
normal com uma implementação de JPA. Se você já fez qualquer
desenvolvimento de aplicativo com JPA, então nada irá surpreendê-lo.
A
única coisa que pode ser nova é configurar EntityManagerFactoryImpl
da SimpleJPA, que necessitará das suas credenciais dos Serviços da Web
da Amazon e o nome do prefixo para seu domínio SimpleDB. (Outra opção
seria fornecer um arquivo de propriedades contendo suas credenciais no
caminho de classe.)
Usar o nome do prefixo que você especifica ao criar uma instância EntityManagerFactoryImpl
de SimpleJPA resultará em domínios SimpleDB que começam com seu prefixo
seguido de um travessão e o nome do seu objeto de domínio. Assim, se eu
especificar “b50” como meu prefixo, ao criar um item Race em SimpleDB, o domínio será “b50-Race”.
Assim que tiver criado uma instância EntityManagerFactoryImpl de SimpleDB, todo o restante é dirigido pela interface. Você precisará de uma instância EntityManager, que você pode obter de EntityManagerFactoryImpl, como mostrado na listagem 8:
Listagem 8. Obtenção de uma EntityManager
Map<String,String> props = new HashMap<String,String>();<br />props.put("accessKey","...");<br />props.put("secretKey","..");<br /><br />EntityManagerFactoryImpl factory = <br /> new EntityManagerFactoryImpl("b50", props);<br /><br />EntityManager em = factory.createEntityManager();Manipulação de objetos de domínio
Assim que tiver um identificador para uma EntityManager, é possível manipular objetos de domínio conforme desejar. Como exemplo, posso criar uma instância Race como esta:
Listagem 9. Criação de um Race
Race race = new Race();<br />race.setName("Charlottesville Marathon");<br />race.setLocation("Charlottesville, VA");<br />race.setDistance(new BigDecimal(26.2));<br />em.persist(race);Na listagem 9, a SimpleJPA lida com todas as solicitações HTTP para criar Race
na nuvem. Usar SimpleJPA significa que eu posso recuperar o Race usando
uma consulta JPA, como mostrado na listagem 10. (Lembre-se de que você
não pode fazer junções com essas consultas, mas pode procurar com
números.)
Listagem 10. Como encontrar um race por distância
Query query = em.createQuery("select o from Race o where o.distance = :dist");<br />query.setParameter("dist", new BigDecimal(26.2));<br /><br />List<Race> races = query.getResultList();<br />for(Race race : races){<br /> System.out.println(race);<br />}De números a cadeias de caractere
A mágica de transformar número em cadeia de caractere da SimpleJPA é
muito bacana; por exemplo, se você habilitar impressão de consulta em
SimpleJPA, é possível ver quais consultas ela emite para SimpleDB. A
consulta enviada é mostrada na listagem 11. Observe como distance está codificada.
Listagem 11. A SimpleJPA lida com números de uma maneira ótima!
amazonQuery: Domain=b50-Race, query=select * from `b50-Race`<br /> where `distance` = '0922337203685477583419999999999999928946'Preenchimento automático e codificação tornam as coisas bem mais fáceis, você não acha?
Relacionamentos em SimpleJPA
Muito embora o SimpleDB não permita junções de domínio em consultas, ainda assim é possível ter itens
relacionados nos domínios. Como mostrei na parte 1,
é possível simplesmente armazenar a chave de um objeto relacionado em
outro e depois recuperar esse objeto quando precisar dele. É isso o que
a SimpleJPA faz também. Por exemplo, mostrei anteriormente como
vincular Runners a uma Race usando anotações de JPA. Assim, posso criar uma instância de um Runner, adicionar a instância race existente a ela, e depois persistir a instância Runner , como mostrado na listagem 12:
Listagem 12. Relacionamentos com SimpleJPA
Runner runner = new Runner();<br />runner.setName("Mark Smith");<br />runner.setSsn("555-55-5555");<br />runner.setRace(race);<br />race.addRunner(runner);<br /><br />em.persist(runner);<br />em.persist(race); //update the race now that it has a runnerObserve também na listagem 12 que eu tive que atualizar a instância Race para refletir o fato de ter adicionado uma instância Runner a ela (observe também que adicionei um método addRunner a Race que simplesmente adiciona um Runner à Collection interna de Runners).
Mais uma vez, se eu procurar um race por sua distância, também posso obter uma listagem de seus
runners, como a listagem 13:
Listagem 13. Mais diversão nos relacionamentos!
Query query = em.createQuery("select o from Race o where o.distance = :dist");<br />query.setParameter("dist", new BigDecimal(26.2));<br /><br />List<Race> races = query.getResultList();<br /><br />for(Race races : race){<br /> System.out.println(race);<br /> List<Runner> runners = race.getRunners();<br /> for(Runner rnr : runners){<br /> System.out.println(rnr);<br /> }<br />}Usar uma instância EntityManager permite que eu exclua
entidades através do método remove, mostrado na listagem 14:
Listagem 14. Remoção de uma instância de classe
Query query = em.createQuery("select o from Race o where o.distance = :dist");<br />query.setParameter("dist", new BigDecimal(26.2));<br /><br />List<Race> races = query.getResultList();<br /><br />for(Race races : race){<br /> em.remove(race);<br />}Embora eu tenha removido uma instância Race da listagem 14, os Runners relacionados não são removidos. (Eu posso, é claro, lidar com isso usando uma anotação EntityListeners de JPA, o que significa que posso olhar um evento de remoção e usá-lo para remover instâncias Runner .)
Conclusão
Esse rápido tour de SimpleDB mostrou como manipular objetos em
armazenamento de dados não relacionais, usando a API dos serviços da
Web da Amazon e a SimpleJPA. A SimpleJPA implementa um subconjunto da
API de Persistência Java para tornar a persistência de objeto no
SimpleDB mais fácil.
Uma das conveniências de usar SimpleJPA, como você
viu, é que ela converte automaticamente tipos primitivos nos objetos de
cadeia de caractere que o SimpleDB reconhece. A SimpleJPA também lida
com as regras de não junção do SimpleDB para você automaticamente,
tornando mais fácil modelar relacionamentos. As interfaces abrangentes
de listener da SimpleJPA também tornam possível implementar regras
lógicas de integridade de dados, que você provavelmente espera de um
mundo relacional.
O resultado da SimpleJPA é que ela pode ajudá-lo a acessar
escalabilidade significativa e acessível financeiramente de forma
rápida. Com a SimpleJPA, é possível aproveitar o conhecimento que você
já tem de anos de trabalho com estruturas como Hibernate em um ambiente
de armazenamento não relacional, baseado em nuvem.
Recursos
Aprender
- Desenvolvimento em Java 2.0:
Esta série do developerWorks explora tecnologias e ferramentas que
estão redefinindo a paisagem do desenvolvimento em Java, incluindo Gaelyk (Dezembro de 2009), Google App Engine (Agosto de 2009) e CouchDB (novembro de 2009). - “NoSQL Patterns”
(Ricky Ho, Técnicas de Programação Pragmática, novembro de 2009): Uma
visão geral e listagem de bancos de dados NoSQL, seguidas de uma visão
mais profunda da arquitetura comum de armazenamento de dados NoSQL. - “Computação em nuvem com o Amazon Web Services, Parte 5: Processamento de conjunto de dados na nuvem com o SimpleDB”
(Prabhakar Chaganti, developerWorks, fevereiro de 2009): Aprenda
conceitos básicos do SimpleDB da Amazon e explore algumas das funções
fornecidas pela boto, uma biblioteca Python de software livre para
interatividade com SDB. - “Computação em nuvem com o Amazon Web Services, Parte 1: Introdução”
(Prabhakar Chaganti, IBM developerWorks, julho de 2008): Explore como
os serviços oferecem uma alternativa convincente para arquitetar e
construir aplicativos escaláveis e confiáveis. - Google App Engine for Java: Part 3: Persistence and relationships”
(Richard Hightower, developerWorks, agosto de 2009): Rick Hightower
explica as desvantagens da estrutura atual de persistência baseada em
Java do Google App Engine e discute algumas soluções alternativas. -
Navegue pela
livraria de tecnologia para ver livros sobre esses e outros tópicos técnicos. - Zona de tecnologia Java do developerWorks: Encontre centenas de artigos sobre cada aspecto da programação Java.
Obter produtos e tecnologias
-
SimpleJPA: Uma
implementação de JPA (Java Persistence API – API de Persistência Java)
para o SimpleDB da Amazon. Em outras palavras, uma estrutura de ORM
(object-relational mapping – mapeamento objeto relacional) para banco
de dados em nuvem da Amazon.
Discutir
- Participe da comunidade do My developerWorks.
Entre em contato com outros usuários do developerWorks enquanto explora
os blogs, fóruns, grupos e wikis orientados ao desenvolvedor.
artigo publicado originalmente no DeveloperWorks Brasil, por Andrew Glover
Andrew Glover é desenvolvedor, autor, palestrante e empresário com uma
paixão por desenvolvimento orientado a comportamento, Integração
Contínua e desenvolvimento de software Agile. É possível entrar em
contato com ele em seu blog







