O JEST une dois estilos arquitetônicos — Representational State Transfer
(REST) e Java™ Persistence API (JPA) — para permitir que clientes
remotos neutros em relação à linguagem transacionem com aplicativos
baseados em JPA seguindo princípios do REST. O JEST modela fechamentos
persistentes customizáveis de entidades gerenciadas como recursos de
REST e os representa no formato XML ou JavaScript Notation (JSON)
aprimorado para prestar contas em relação aos ciclos em um gráfico de
objeto. Este artigo explica conceitos de JEST. Em seguida, apresenta uma
implementação de demonstração: um Web client genérico que se comunica
com um servidor para consultar objetos persistentes e navegar em um
modelo de domínio persistente de forma agnóstica em relação ao domínio.
O estilo arquitetônico Representational State Transfer (REST) define
como um cliente e um servidor podem se comunicar para permitir que
sistemas multicamada escalem para uma quantidade praticamente ilimitada
de clientes. A Java Persistence API (JPA) define como um aplicativo do
lado do servidor deve interagir com os dados persistentes armazenados em
um banco de dados relacional. O REST e a JPA têm características
arquitetônicas em comum. Este artigo descreve e demonstra o JEST, uma
solução que une esses dois estilos em uma base arquitetônica robusta de
ponta a ponta para aplicativos da Web multicamada. O JEST faz parte do
projeto OpenJPA da Apache Software Foundation, que é uma implementação
da especificação Java Persistence 2.0.
Este artigo começa descrevendo os recursos mais importantes do REST e da
JPA para destacar as suas características comuns. A seguir, dá
detalhes dos desafios técnicos e conceituais da cooperação entre os dois
estilos e explica como o JEST supera esses desafios. A última seção
indica uma implementação concreta: um servlet Java que você pode
implementar em um servlet ou contêiner de aplicativo e acessar por meio
do cliente Dojo/Ajax pré-empacotado do JEST a partir de um navegador da
Web Vanilla.
Princípios do REST
Os princípios do REST foram adotados amplamente para a World Wide Web
e citados como um dos principais motivos de sua excelente
escalabilidade.
O REST se baseia em alguns conceitos principais que eu detalharei por
meio do cenário típico de interação mostrado na Figura 1:
Figura 1. Exemplo de interação no estilo REST.
- A interação entre o cliente e o servidor se baseia unicamente na transferência bidirecional de recursos.
Isso é diferente da interação presa à linguagem e baseada na API, na
qual o cliente chama funções do servidor por meio de proxies remotas
passando argumentos variáveis em uma linguagem de programação; - Um recurso é um conceito identificável e coerente. Identificável
significa que é possível identificar cada recurso de forma exclusiva
por meio de uma sintaxe bem-definida. A expressão mais comum dessa
identidade é um Uniform Resource Identifier (URI), como um endereço da
Web; - O cliente solicita um recurso — uma ideia abstrata como o endereço
de um bar localizado nas redondezas — proveniente de um servidor (por
exemplo: Google Maps). O servidor responde enviando uma representação
do recurso — por exemplo: um documento HTML com JavaScript integrado e
um hyperlink para o mapa da localização geográfica do bar; - A representação é coerente se contém informações suficientes para resolver todas as referências contidas na representação;
- Ao receber a representação, o cliente frequentemente a renderiza em um navegador. Os pontos importantes são:
- A ideia abstrata de um recurso é percebida como uma representação concreta;
- O servidor pode responder com representações diferentes do mesmo
recurso com base em vários critérios — por exemplo: o idioma natural do
usuário. A solicitação em si pode especificar uma forma aceitável
específica, como um tipo MIME solicitado por meio do cabeçalho Accept de uma solicitação de HTTP típica; - O cliente pode resolver todas as referências na representação ou
não. Por exemplo: um usuário pode decidir (ou não) seguir o hyperlink
para exibir o mapa da localização geográfica juntamente com o endereço
do bar, dependendo do imóvel disponível “na interface”;
- Além de renderizar, o cliente também pode permitir que o usuário
modifique a representação ou crie representações novas — por exemplo:
por meio de um formulário HTML. Depois que a interação com o usuário é
concluída, o cliente envia uma nova solicitação juntamente com a
representação modificada; - O servidor recebe a representação modificada e, com base no conteúdo, muda o recurso original ou cria recursos novos;
Uma interação entre um cliente e um servidor por meio da troca de representações de recursos se qualifica como um if — compatível com REST e é uma condição importante — o servidor está em repouso
depois de enviar a primeira resposta e antes de receber a próxima
solicitação. Ou seja, o servidor não gasta poder computacional nem
outros recursos para manter a memória da conversa com o cliente entre
solicitações sucessivas. (Da mesma forma, os aplicativos que armazenam
dados de sessão no servidor não são compatíveis com REST.)
Como o servidor não mantém o contexto dos clientes entre as
solicitações, a resposta do servidor pode escalar de forma favorável com
relação a um número praticamente ilimitado de clientes.
Transações ACID e REST
Um servidor stateless é mais realista na prática quando o cliente só
solicita recursos. Se o cliente envia representações modificadas em
diversas solicitações que o servidor tem que aplicar aos recursos atomicamente
— ou seja, com uma garantia transacional ACID (Atomicity, Consistency,
Isolation, and Durability) — a conformidade com o estilo REST é uma
proposta desafiadora. Isso acontece porque uma transação ACID, por
natureza, requer memória de curto e de longo prazo.
Uma transação ACID:
- Agrupa diversas ações do cliente durante um determinado período de tempo em uma unidade atômica (indivisível);
- Garante que o efeito combinado dessas ações seja consistente;
- Garante que o efeito combinado seja isolado de quaisquer ações simultâneas de outros clientes;
- Faz com que o efeito combinado dure para sempre;
A memória de curto prazo contém a série atual de ações (também conhecida como unidade de trabalho ou transação em andamento).
A memória de longo prazo, que muitas vezes é persistente e
compartilhada — como um banco de dados em um disco rígido — é
necessária para fazer com que o efeito combinado seja durável.
Portanto, as principais perguntas para o arquiteto de um aplicativo do tipo cliente/servidor com estilo REST são:
- Quais são os recursos identificáveis que o servidor oferece?
- Quais são as representações de recursos que o servidor gera?
- Como um servidor stateless suporta transações ACID?
As respostas dessas perguntas podem ser encontradas no contexto de um
aplicativo de servidor baseado em JPA voltado para Web clients ou
serviços da Web neutros em relação à linguagem sem violar os princípios
mais importantes do REST.
JPA: model view controller para persistência de objetos
Como estilo arquitetônico, a JPA segue a arquitetura clássica model view controller (MVC) para objetos Java persistentes:
- O modelo é um banco de dados relacional;
- A visualização é um Plain Old Java Object (POJO) com tipo forte sem nenhuma interface de jure nem superclasse (como era em especificações anteriores de Enterprise JavaBeans);
- O controlador é a própria API — ou seja, javax.persistence.EntityManager;
A Figura 2 mostra a arquitetura MVC da JPA para a persistência de objetos:
Figura 2. JPA como MVC para persistência de objetos.
Como a Figura 2
mostra, um aplicativo baseado em JPA só acessa o modelo (o banco de
dados relacional) por meio da visualização (POJOs). Todas as
modificações no banco de dados, como inserções de novos registros ou
atualizações dos já existentes, devem ser feitas somente por meio do
controlador— por EntityManager.persist() ou merge(), por exemplo.
Os aplicativos que mudam para a JPA para serviços de persistência
obtêm uma vantagem imediata ao passar para um provedor de JPA o chamado
“problema” de ORM — o problema complexo de converter entre o domínio do
banco de dados controlado pelo esquema relacional e a SQL — e o domínio
da linguagem Java orientada a objetos, controlado por tipos fortes e
referências unidirecionais.
Por que JPA >> ORM + JDBC
Entretanto, a proposta principal da JPA como componente de middleware
vai além da resolução do problema de ORM. É de grande importância para a
argumentação deste artigo um dos recursos mais potentes da JPA: o
suporte para transações separadas.
Um provedor de JPA garante transações ACID formadas por uma série de operações persistentes durante um período T sem manter uma conexão com o banco de dados por toda a duração do T.
Ao manter a transação em andamento na memória e diminuir o período de
tempo da conexão com o banco de dados, a JPA deixa o aplicativo mais
escalável com relação a transações simultâneas do usuário (Também pode
baixar os custos, já que o custo de um servidor de banco de dados
comercial é proporcional ao cn, onde c é o número de conexões simultâneas permitidas por uma instalação do banco de dados e n frequentemente é maior que 1).
No entanto, a ideia de transações separadas na JPA vai além disso. A
JPA suporta transações com o estilo acessar/separar/modificar/mesclar
para clientes remotos. O cliente pode solicitar objetos persistentes do
servidor, e o servidor os envia como um gráfico de objetos separados. O
cliente pode modificar o conteúdo ou a estrutura do gráfico do objeto
separado e enviar o gráfico do objeto modificado de volta ao servidor em
uma solicitação separada. O servidor pode executar o commit das
modificações no banco de dados com uma garantia de transação compatível
com ACID. O aplicativo de JPA não contém nenhuma referência aos objetos
separados entre a primeira solicitação de acesso e a solicitação de
mudança subsequente.
Essa semântica de transação separada é ainda mais significativa para
clientes típicos baseados na Web, nos quais o período de tempo da
transação do usuário pode ser bem mais longo que a transação do banco de
dados em si, ou quando a maioria das transações é somente leitura (ou
seja, o commit nunca é executado, e a chamada proporção compra/navegação
normalmente é 1:200 para muitos Web sites bastante conhecidos). Esse
processo de acessar/separar/modificar/mesclar pode permitir que um
aplicativo baseado em JPA se torne escalável e com custo reduzido. Um
aplicativo desse tipo pode suportar, digamos, 5.000 Web clients
simultâneos com apenas 10 conexões com o banco de dados e sem perder
transações nem reduzir o tempo de resposta.
Entretanto, o modelo de programação acessar/separar/modificar/mesclar
deve preencher alguns pré-requisitos para a interação cliente/servidor:
- O cliente deve ser escrito na linguagem Java para chamar a JPA por
meio de um bean de sessão remota ou de uma fachada semelhante; - O servidor envia o gráfico do objeto separado na forma de bytes
serializados de objetos Java com tipos fortes definidos no lado do
servidor. Para interpretar esses bytes, o cliente requer as definições
de classe dos tipos Java persistentes; - Na prática, a maioria dos fornecedores de JPA introduz a dependência
nas suas classes de implementação (como as proxies para os tipos de
coleção padrão de Java) nos POJOs definidos pelo usuário para
gerenciá-los com mais eficiência. Isso também torna o cliente dependente
das bibliotecas de tempo de execução do fornecedor;
O JEST, por sua vez, é direcionado aos clientes neutros em relação à
linguagem e faz suposições mínimas sobre o ambiente computacional do
cliente — o mínimo denominador comum é um navegador da Web Vanilla
simples. Sendo assim, o JEST deve gerar uma representação para gráficos
de objetos separados que possam ser consumidos por um cliente neutro em
relação à linguagem, unificando assim os princípios do REST e o modelo
de programação acessar/separar/modificar/mesclar da JPA.
Como o JEST unifica REST e JPA
Portanto, os principais desafios técnicos da unificação do REST e da JPA são:
- Como representar gráficos de objetos persistentes separados como recursos para clientes neutros em relação à linguagem;
- Como suportar transações ACID sem sacrificar a característica stateless de um servidor com REST;
Recursos JEST: fechamentos customizáveis e persistentes
No contexto do JEST, o recurso — o conceito central de uma
arquitetura com REST — é o fechamento persistente e customizável de uma
entidade identificável e gerenciada, como mostra a Figura 3:
Figura 3. Fechamento customizável e persistente.
O fechamento persistente de uma entidade raiz gerenciada x é definido como o conjunto de entidades gerenciadas C(x) ={e}, onde e pode ser acessado a partir de x
direta ou indiretamente, por meio de um relacionamento persistente.
Além disso, a entidade, por definição, sempre é acessível a si mesma.
A customização de um fechamento persistente é a capacidade de
configurar quais entidades são acessíveis a partir de uma entidade raiz
de forma dinâmica, no tempo de execução.
A JPA prescreve que os relacionamentos de objetos sejam decorados com
qualidades de persistência. Por exemplo: um relacionamento com diversos
valores entre um Department e seus Employees — ou seja, um campo privado na classe Department declarado como List<Employee> employees — pode ser decorado como um relacionamento de um para muitos por meio da anotação @OneToMany ou de um descritor de mapeamento que o acompanha. Nesse caso, todas as instâncias de Employee de um Department d se tornam acessíveis a partir ded. O fechamento também pode incluir caminhos indiretos de relação. Se cada Employee e tem uma referência a uma instância de Address a anotado com @OneToOne, a instância de Address a é acessível de forma temporária a partir da instância de Department por meio de Employee e e, portanto, o fechamento de d inclui uma instância de Address a também.
Entretanto, é importante ressaltar que o fechamento persistente só se aplica a entidades gerenciadas . Portanto, por exemplo, uma instância de Department d pode estar relacionado logicamente a um conjunto de 20 instâncias de Employee — mas esse relacionamento pode não ter sido carregado. Ou seja, as instâncias de Employee podem não ter sido levadas do banco de dados para o contexto de persistência que contém d. Nesse caso, o fechamento de d não incluirá as instâncias de Employee , e essas instâncias não serão carregadas a partir do banco de dados como um efeito colateral da avaliação do fechamento.
A especificação atual da JPA fornece alguns recursos elementares para
a customização de fechamentos. Qualquer propriedade persistente (campos
de relação e também campos básicos) pode ser decorada como FetchType.LAZY ou FetchType.EAGER.
Essas decorações entram em vigor quando uma instância de entidade raiz
está sendo carregada do banco de dados para um contexto persistente a
fim de determinar quais propriedades ou outras instâncias também devem
ser carregadas. Uma limitação grave da especificação JPA atual é o fato
de a customização ser definida estaticamente, no tempo de design. Não é
possível configurar o fechamento no tempo de execução.
A OpenJPA suportar a configuração dinâmica de fechamentos persistentes por meio da interface FetchPlan. A sintaxe rica e a semântica da FetchPlan permitem que o aplicativo configure o fechamento de qualquer instância raiz resultante de uma operação find()
ou de instâncias selecionadas por consulta. O JEST aproveita essas
mecânicas para acessar um conjunto relacionado de entidades e suas
propriedades. Um plano de busca é uma construção útil porque o cliente
pode controlar o conteúdo da representação solicitada para o mesmo
recurso lógico baseando-se em cada caso de uso. Por exemplo: o cliente
pode customizar uma representação que ele recebe, referente ao mesmo
bar, baseando-se no meio de renderização das informações — um telefone
celular ou um monitor de desktop de alta qualidade.
Representações coerentes de JEST: XML e JSON
O próximo aspecto do esquema de unificação do JEST é a representação de
recursos. A representação é voltada para os clientes neutros em relação à
linguagem e agnósticos em relação ao domínio. O JEST prescreve as
seguintes qualidades para representações de recursos:
- Não invasivo: para ser representado, o recurso não pode requerer anotação nem decoração de nenhum tipo (consulte a barra lateral JEST x JAX-RS );
- Neutro em relação à linguagem: a representação deve
ser consumível por parte de clientes baseados em qualquer linguagem de
programação. Como a interação é baseada em um URI e não em uma API,
clientes escritos em qualquer linguagem podem receber representações por
meio de solicitações de HTTP. A representação também segue um formato
bem conhecido, como XML ou JSON, para permitir que qualquer cliente
interprete o seu conteúdo. Nenhum cliente requer representação gerada
por JEST; - Agnóstico em relação ao domínio: embora o recurso
original seja um objeto Java persistente com tipos fortes, o cliente não
precisa das definições de classe persistentes para consumir a
representação gerada por JEST. No sentido do REST, a representação é autoexplicativa;
Representações baseadas em domínio x representações com domínio invariável
A representação neutra em relação à linguagem é uma sequência
ordenada de bytes ou caracteres com um formato implícito. XML e JSON são
exemplos comuns. Uma representação baseada em cadeia de caracteres
dessa natureza é voltada para uma ampla variedade de clientes porque não
supõe que o cliente tenha qualquer capacidade especial que não seja a
de analisar uma sequência de bytes ou caracteres de um conjunto de
caracteres. O JEST gera representações em XML e JSON, estendidas (no
caso do JSON) com alguns recursos essenciais.
O tema principal de uma representação JEST e o fato de ser baseada no
modelo e não no domínio. É comum encontrar representações XML de tipos
Java que são baseadas no modelo. Por exemplo: considere o tipo Java
persistente simples na Listagem 1:
@javax.persistence.Entity
public class Actor {
@javax.persistence.Id
private long id;
private String name;
private java.util.Date dob;
}
Listagem 1. Tipo Java persistente simples.
A Listagem 2 mostra a representação XML correspondente de uma instância de Actor :
<?xml version="1.0" encoding="UTF-8"?>
<Actor>
<id type="long">1234</id>
<name type="string">Robert De Niro</name>
<dob type="date">Aug 15, 1940</dob>
</Actor>
Listagem 2. Esquema XML baseado em domínio representando uma instância de Actor.
A representação XML da Listagem 2
é baseada em domínio porque o seu esquema explícito ou implícito é
controlado pela estrutura de tipo da classe de domínio persistente: Actor. Por outro lado, uma representação baseada em modelo da mesma instância é parecida com a Listagem 3:
<?xml version="1.0" encoding="UTF-8"?>
<instances>
<instance type="Actor" id="Actor-1234">
<id name="id" type="long">1234</id>
<basic name="name" type="string">Robert De Niro</name>
<basic name="dob" type="date">Aug 15, 1940</dob>
</instance>
</instances>
Listagem 3. Entidade simples representada em um esquema XML baseado em modelo.
Uma representação baseada em modelo tem um esquema com domínio
invariável: é suficientemente genérica para expressar qualquer tipo
persistente. Na representação baseada em modelo, o esquema de Actor
não varia, ao passo que, em uma representação baseada em domínio, ele
varia. A vantagem de uma representação baseada no modelo é o fato de
permitir que o destinatário interprete a representação de uma forma
genérica, independente da estrutura exata das informações recebidas.
O esquema baseado em modelo chamado jest-instance.xsd deriva diretamente da API Metamodel introduzida na JPA 2.0.
A API Metamodel é semelhante à API Java Reflection para tipos de
linguagem Java, que foi estendida para tipos persistentes para ganhar
mais expressividade. O JEST expressa as construções da Metamodel JPA
como (chamadas de metametadados) javax.persistence.metamodel.Entity ou SingularAttribute
etc. em um esquema XML. Todas as respostas do JEST para qualquer
entidade persistente seguem o mesmo esquema jest-instance.xsd.
Representação coerente por fechamento persistente
Um fechamento persistente é um conjunto fechado. Ou seja, para cada e1 em um fechamento C(x), isto é verdadeiro:
Se e1 se refere a uma entidade e2 e, em seguida, e2 devem estar no mesmo fechamento C(x)
Essa propriedade de fechamento garante que a representação
de um recurso de JEST seja coerente — um aspecto importante da
representação no estilo REST. O cliente recebe uma representação na qual
todas as referências são resolvidas dentro da mesma representação e não
precisa fazer outra solicitação ao servidor para resolver referências.
Porque o JEST estende o JSON
Para a representação em XML, o JEST define os seus esquemas XML para identidade e referências como tipos xsd:ID e xsd:IDREF
, respectivamente. Portanto, um analisador XML padrão pode resolver as
referências a elementos apropriados do Document Object Model (DOM).
Entretanto, uma representação coerente com o JSON enfrenta um
problema técnico. As bibliotecas de codificação padrão do JSON, bastante
usadas em ambientes de navegador da Web, não suportam referências
circulares.
(Isso é surpreendente, porque as referências circulares são uma
ocorrência comum em quase todos os gráficos de objeto persistente.) Para
resolver essa limitação dos codificadores de JSON já existentes e — o
que é mais importante — para suportar a premissa principal de que a
representação deve ser coerente para evitar conversas “prolixas” do
cliente com o servidor, o JEST fornece um codificador de JSON que
suporta referências circulares.
O codificador do JEST para JSON introduz duas propriedades adicionais, $id e $ref,
que transportam a identidade persistente das entidades gerenciadas para
resolver qualquer eventual ciclo dos gráficos de objetos. Os
analisadores de JSON padrão que estão disponíveis em Dojo ou Gson podem
interpretar essa representação melhorada simplesmente ignorando as
propriedades $id e $ref adicionais. Somente o
JEST pode reinterpretar a representação JSON melhorada para recriar um
gráfico de objeto com referências cíclicas.
Transações separadas: fundamentais para unificar o REST com transações JPA
O modelo de transação separada da JPA é a chave para superar os desafios
da prescrição do REST em relação à semântica do “servidor em repouso
entre solicitações” para aplicativos da Web transacionais. O JEST
integra esses dois estilos arquitetônicos realizando transações na
sequência mostrada na Figura 4:
Figura 4. Transação separada para integrar REST e JPA.
- No tempo t1, a solicitação do cliente R1 referente a um recurso chega ao servidor.
- O servidor usa a JPA para acessar o recurso a partir do banco de
dados como um conjunto de objetos Java. Esse acesso requer uma conexão
com o banco de dados. O provedor de JPA libera a conexão com o banco de
dados assim que o acesso é concluído. - O provedor normalmente mantém esses objetos em uma região da memória de curto prazo chamada de contexto persistente
na nomenclatura da JPA. Entretanto, nesse caso, o provedor separa
imediatamente os objetos do contexto de persistência, converte-os para
uma representação A1 que pode ser interpretada pelo cliente e envia A1 como resposta para a solicitação original R1. A separação imediata garante que o aplicativo do servidor não mantenha recursos depois do envio da resposta. - O cliente renderiza A1 e permite que o usuário modifique a representação para A1′.
- O cliente pode enviar mais solicitações R2 e R3 para acessar mais recursos e recebe A2 e A3, respectivamente, como respostas.
- O cliente pode permitir que o usuário modifique A2 ou A3 também.
- Assim que o usuário conclui as modificações de todas as respostas recebidas, o cliente envia uma solicitação R4 no tempo t2 com todas as representações modificadas A1′, A2′ e A3′.
- O servidor converte as representações modificadas que foram
recebidas para objetos Java e usa a JPA para mesclá-las em um contexto
de persistência. Nesse ponto, o provedor da JPA pode precisar de uma
conexão com o banco de dados para validar que as mudanças são
consistentes — por exemplo: para garantir que nenhum outro cliente
tenha executado commit de mudanças incompatíveis nos mesmos objetos
contidos em A1′, A2′ e A3′ no período entre t1 e t2.
No final, se as mudanças são consistentes e isoladas, o servidor
executa commit das mudanças no banco de dados por meio da API JPA.
Nesse esquema de unificação de REST e JPA, o aplicativo JPA não
mantém os objetos persistentes separados na memória de curto prazo
(contexto de persistência) quando as representações estão sendo
modificadas pelo cliente. Também não requer a retenção de uma conexão
com o banco de dados para assegurar a garantia transacional em um tempo
posterior t2. O servidor permanece stateless no que diz
respeito à conexão com o banco de dados e à memória de curto prazo. O
servidor precisa desses recursos de forma on demand por um período muito
mais curto que o tempo entre t1 e t2, que é a duração total da transação do ponto de vista do cliente.
Esse esquema de unificação resolve o problema crítico: como um
servidor stateless pode suportar transações de clientes sem manter
nenhum contexto do cliente e sem sacrificar a garantia de uma transação
ACID.
JEST em HTTP
O último conceito do JEST a ser entendido é a forma de especificar um
protocolo de comunicação e um esquema de URI. O REST nunca se refere
diretamente a um protocolo de comunicação específico entre o cliente e o
servidor. Entretanto, na prática, o REST tem um relacionamento próximo
com o HTTP (e influiu muito na evolução do HTTP). Por isso, o projeto
JEST escolheu o HTTP como o seu protocolo de comunicação. (Seguindo os
princípios do REST, os principais conceitos do JEST — segundo os quais
os recursos são fechamentos persistentes de entidades gerenciadas e
representações são neutras em relação à linguagem e agnósticas em
relação ao domínio — são independentes do HTTP).
Sintaxe de URI do JEST
O JEST interpreta URIs padrão para identificar recursos persistentes.
Os URIs padrão são definidos em quatro segmentos: esquema, autoridade,
caminho e consulta.
O JEST aplica uma interpretação especial aos segmentos de caminho e
consulta a fim de convertê-los para uma operação de persistência
apropriada.
Estes são dois URIs típicos de JEST que mostram as informações que devem ser codificadas em um URI para que o JEST opere.
Este URI obtém um Actor com chave primária m1:
http://openjpa.com:8080/jest/find/plan=basic?type=Actor&m1
Este URI obtém um único Actor de nome John
por meio de uma consulta com Java Persistence Query Language (JPQL):
http://openjpa.com:8080/jest/query/single?q=select p from Actor p where p.name=:n&n=John
Um URI de JEST deve codificar informações suficientes para fornecer os detalhes a seguir:
-
Operação de JPA: as operações persistentes no contexto do JEST são, em sua maioria, métodos na interface javax.persistence.EntityManager:
- find(): localiza uma entidade por seu identificador primário.
- persist(): cria a nova entidade no banco de dados.
- merge(): atualiza o contexto de persistência atual com o estado atual da entidade.
- remove(): exclui a entidade do banco de dados.
EntityManager também age como factory para criar instâncias executáveis de javax.persistence.Query a partir de uma sequência de consultas de JPQL.
Idealmente, a operação de persistência de destino deve ser implícita por meio de um verbo de HTTP. Os verbos de HTTP PUT, POST e DELETE são convertidos diretamente para a operação de JPA correspondente (merge(), persist() e remove(), respectivamente), como mostra a Figura 5:
Figura 5. Mapeamento dos verbos de HTTP em relação às operações de persistência de JPA.
HTTPGET, no entanto, deve ser multiplexado para uma operação de localização ou consulta. Além dessas operações, o GET também é usado para obter o metamodelo (/domain)
e as propriedades de configuração (/properties) da unidade de persistência. Para trabalhar com a multiplicidade de tipos de recurso disponíveis por meio de GET,
o JEST precisa que o nome de uma ação seja codificado no URI e exige
que a ação seja o primeiro segmento do caminho depois do contexto de
chamada.
- Qualificadores: uma operação de persistência pode
ser qualificada ou decorada. Por exemplo: pode-se restringir uma
consulta para que ela retorne somente um resultado ou somente os 20
primeiros resultados selecionados. Uma operação de localização ou
consulta pode ser decorada para aplicar um plano de busca a fim de
customizar o conjunto de entidades a ser buscadas no banco de dados.Os qualificadores são codificados em segmentos do caminho
subsequentes à ação. Uma ação pode ter qualificadores ou não. A ordem
dos qualificadores não é importante. O qualificador aparece como um par
chave/valor separado pelo caractere = . No caso de um
qualificador com valor booleano, pode-se omitir a parte do valor para
que fique mais conciso. São exemplos de qualificadores com valor
booleano: single (para indicar que uma consulta deve ser satisfeita por apenas uma entidade) e named (para indicar que o argumento de consulta especificado se refere ao nome de uma NamedQuery declarada e não a uma cadeia de caractere de JPQL). - Argumentos: toda operação precisa de argumentos. Os argumentos de uma operação find()
, por exemplo, são o tipo de entidade e o seu identificador primário.
Os argumentos de uma operação de consulta são a cadeia de caracteres de
JPQL ou o nome de uma consulta nomeada predefinida. As consultas
parametrizadas também tomam um número variável de argumentos como
parâmetros de ligação.Os argumentos de uma operação de JPA são codificados na parte de consulta do URI referente a uma solicitação GET de HTTP e na carga útil das solicitações PUT, POST e DELETE . Além disso, os argumentos codificados na parte de consulta de um URI de solicitação de GETdependem da ação. Por exemplo: uma operação find() toma dois argumentos: uma classe Java referente à entidade a ser localizada e o seu identificador persistente.
O segmento de consulta do URI separa cada argumento por meio do caractere “e comercial” (símbolo &) (&), e cada argumento aparece como um par chave/valor separado pelo caractere de igual (=). Diferentemente dos qualificadores, os argumentos são ordenados e, em sua maioria, são obrigatórios.
O aspecto crítico da conversão de um URI para uma operação executável
de JPA é a transferência de argumentos baseados em cadeia de caracteres
para os argumentos de tipos fortes que toda operação de JPA requer. Por
exemplo: o argumento type=Actor deve ser convertido para Actor.class. Uma carga útil de XML/JSON referente a uma solicitação POST deve ser convertida para um gráfico de objeto Java com tipo forte antes de ser mesclada no contexto de persistência.
O JEST em ação
Agora que você entende todos os fundamentos conceituais e mecânicos do
esquema de unificação do JEST, está pronto para dar uma olhada em uma
implementação concreta: JESTServlet. JESTServlet é um javax.sevlet.HttpServlet padrão para acessar recursos do JEST a partir de um cliente remoto. É possível implementar um JESTServlet em qualquer contêiner de servlet padrão, como Tomcat, WebSphere® ou Glassfish.
Implementando o JEST
O JESTServlet é empacotado com a instalação do OpenJPA. Há
instruções para download, construção e implementação no site da
documentação do OpenJPA ; portanto, não irei repetir essas etapas. Em vez disso, descreverei os dois modos de implementação suportados pelo JESTServlet: primário e auxiliar, como mostra a Figura 6:
Figure 6. Modo primário e modo auxiliar de implementação do JESTServlet.
No modo primário, o próprio JESTServlet instancia uma unidade de persistência, EntityManagerFactory.
A unidade de persistência nomeada é descrita por um descritor de
persistência padrão, o META-INF/persistence.xml, empacotado no archive
Web.
No modo auxiliar, o JESTServlet não tem a sua própria unidade de persistência, mas descobre a unidade de persistência de um componente irmão: um artefato implementado, como outro servlet no mesmo módulo de implementação. No modo de implementação auxiliar, o JESTServlet
precisa saber o nome da unidade de persistência usada pelo componente
irmão. Atualmente, o componente irmão precisa ativar o pooling nativo de
EntityManagerFactory do OpenJPA para que o JESTServlet
possa obter um identificador referente à mesma unidade a partir do
pool. Aplicativos e contêineres de servlet diferentes podem recorrer a
uma mecânica patenteada para fazer o pool das unidades de persistência
(ou de suas proxies) que o contêiner frequentemente injeta nos
componentes operacionais por meio da injeção de dependência. Na época em
que este texto foi escrito, o JESTServlet não tinha uma
mecânica generalizada para localizar uma unidade de persistência em um
pool a partir dos pools gerenciados por contêiner, mas o projeto está
estudando o acréscimo desse recurso.
A amostra pré-empacotada demonstra o modo auxiliar, no qual um
servlet simples separado instancia uma unidade de persistência e é
implementado juntamente com o JESTServlet no mesmo archive Web. A amostra mostra como o JESTServlet,
sem nenhum conhecimento prévio da unidade de persistência do irmão,
pode navegar no domínio de persistência ou representar as instâncias
persistentes por meio dos seus recursos genéricos e agnósticos em
relação ao domínio.
Um cliente de Dojo para o JEST
De forma proposital, o JEST não aborda o problema da renderização de uma
representação de recurso. Por exemplo: ele não fornece representação em
HTML. Entretanto, o JESTServlet, entrega uma página HTML
única com JavaScript integrado. Essa página é efetivamente um cliente
Ajax para os recursos do JEST. O cliente usa a biblioteca de JavaScript
do Dojo para enviar uma solicitação assíncrona de recursos ao JESTServlet usando a sintaxe de URI do JEST.
Ele renderiza a resposta (em XML ou JSON) da forma que é recebida ou na
forma de widgets visuais Dojo — confirmando a minha afirmação anterior
de que a saída do codificador especializado de JSON que é responsável
pelos gráficos cíclicos pode ser consumida pelos analisadores padrão de
JSON. A Figura 7 mostra a página:
Figura 7. Interface com o usuário baseada em Dojo para o JEST.
Essa página da Web única e interativa deixa todos os recursos de JEST
disponíveis acessíveis para o usuário: o modelo de domínio persistente,
as entidades persistentes e a configuração da unidade de persistência.
Fornece formulários HTML para que o usuário desenvolva uma solicitação.
Esses formulários mostram como a sintaxe de URI do JEST é formada passo a
passo por qualificadores e argumentos especificados pelo usuário.
O JESTServlet executa cada solicitação do cliente em um
contexto que liga uma solicitação/resposta do servlet de HTTP a um
contexto de persistência. O tempo de vida do contexto é igual ao tempo
de vida do ciclo solicitação/resposta — satisfazendo assim a
característica stateless do REST entre as solicitações.
Conclusão
O JEST aproveita os conceitos funcionais ricos da arquitetura da JPA:
- Uma linguagem de consulta potente;
- Fechamentos customizáveis;
- Modelo de domínio genérico;
- Transações separadas para fornecer uma representação rica em
conteúdo e configurável dinamicamente a clientes remotos e neutros em
relação à linguagem, em conformidade total com os princípios
arquitetônicos do REST;
A abordagem do JEST é não invasiva em relação aos recursos
persistentes, ao contrário de outras abordagens com intenções
semelhantes, como o JAX-RS.
Como o JEST é totalmente baseado em metadados, ele é um recurso
genérico que se aplica a qualquer modelo de domínio persistente sem o
conhecimento prévio de suas especificidades.
O JEST pode ser estendido para representar o estado interno de tempo
de execução do OpenJPA — por exemplo: as estatísticas de execução das
consultas ou a taxa de acertos do cache da segunda camada ou das
entidades em cache. O acesso a informações internas desse tipo pode ser
útil para a construção de um console de monitoramento baseado no
navegador da Web.
A segurança de dados com baixa granularidade é uma questão em aberto
no JEST. O fornecimento de acesso a dados persistentes na forma de
recursos por parte de um cliente remoto gera problemas de segurança e
privacidade de dados. O projeto está estudando uma forma de autenticar
ou controlar o conteúdo de uma resposta do JEST em um nível de dados com
baixa granularidade com base nas credenciais do cliente solicitante.
Considerando que o ambiente de execução do JEST tem conhecimento de um
contexto persistente e da árvore de expressão de uma consulta
executável, é factível basear os nós da árvore da expressão de consulta
em um conjunto de regras de acesso com base na função do cliente (O JESTServlet
atual impede qualquer tipo de script envolvendo vários navegadores ao
reescrever a URL básica do cliente de JavaScript em implementação).
Recursos
Aprender
-
JEST: explore o Web site do JEST, que inclui páginas sobre:
- Representação do JEST: veja como o JEST representa um gráfico de objeto Java persistente em XML ou JSON;
- Uso do JEST: leia sobre os modos primário e auxiliar de implementação do JESTServlet;
- Documentação do OpenJPA: consulte a documentação oficial do OpenJPA e do JEST;
- Representational State Transfer: leia o artigo da Wikipédia sobre o REST;
- Architectural Styles and the Design of Network-based Software Architectures (Roy Thomas Fielding, 2000): a tese de doutorado de Fielding deu origem aos conceitos e à terminologia do REST;
- “Consultas dinâmicas e typesafe em JPA 2.0” (Pinaki Poddar, developerWorks, setembro de 2009): leia sobre a API Metamodel do JPA 2.0;
- JSR 317: Java Persistence 2.0: dê uma olhada na especificação de JPA atual;
- “A brief introduction to REST” (Stefan Tilkov, InfoQ, dezembro de 2007): veja uma introdução aos conceitos de REST;
- Identificador Uniforme de Recursos (URI): sintaxe genérica: esta solicitação de comentários (RFC) define os segmentos padrão dos URIs;
- Dojo e Gson: saiba mais sobre essas duas bibliotecas de JavaScript;
- JSON: leia sobre a falta de suporte do JSON para referências de objetos;
- Fechamento transitivo: explore o conceito por trás dos fechamentos de persistência;
- Grupos de buscas: leia sobre a interface FetchPlan do OpenJPA;
-
Navegue na
livraria de tecnologia para ver livros sobre este e outros tópicos técnicos; - Zona tecnologia Java do developerWorks: Encontre centenas de artigos sobre quase todos os aspectos da programação Java;
Obter produtos e tecnologias
- OpenJPA: faça o download do OpenJPA;