Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

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.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

JEST: REST em OpenJPA

Unificando os estilos arquiteturais REST e JPA para aplicativos da Web multicamada

Pinaki Poddar, Senior Software Engineer, IBM  
Pinaki Poddar
Pinaki Poddar trabalha em tecnologia middleware com ênfase em persistência de objetos. Ele é um membro do Expert Group para a especificação Java Persistence API (JSR 317) e voluntário do projeto Apache OpenJPA. No passado, ele contribuiu para a construção de middleware de integração orientada a componentes para um banco de investimento global e uma plataforma de processamento de imagens médicas para o segmento de mercado de assistência médica. Para sua tese de doutorado, ele desenvolveu um sistema local de reconhecimento de voz automático, com base em rede neural.

Resumo:  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.

Data:  20/Jul/2011
Nível:  Intermediário Também disponível em :   Inglês
Atividade:  1054 visualizações
Comentários:  


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 (consulte Recursos).

JPA: mais que uma API

Com frequência, o JPA é considerado equivocadamente como simplesmente uma solução para object-relational mapping (ORM) ou um mero substituto do Java Database Connectivity (JDBC). A JPA, obviamente, é expressa como uma API na linguagem Java. No entanto, estilo arquitetônico model view controller (MVC) para aplicativos transacionais orientados a objetos está por trás da API.

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 (consulte Recursos). 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 (consulte Recursos). 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:

JEST x JAX-RS

Compare a primeira restrição com a Java API for RESTful Web Services (JAX-RS). Na JAX-RS, os POJOs persistentes devem ser anotados para ser representados. Como as classes persistentes de POJO fazem parte da unidade de compilação, os usuários são obrigados a recompilar os aplicativos. Além disso, essas anotações são (assim como na JPA) definidas estaticamente como Fetch.LAZY ou Fetch.EAGER e não são úteis para customizar o delineamento de um fechamento baseando-se em cada caso de uso.

  • 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:


Listagem 1. Tipo Java persistente simples

@javax.persistence.Entity
public class Actor {
  @javax.persistence.Id
  private long id;
  private String name;
  private java.util.Date dob;
}

A Listagem 2 mostra a representação XML correspondente de uma instância de Actor :


Listagem 2. Esquema XML baseado em domínio representando 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>

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:


Listagem 3. Entidade simples representada em um esquema XML baseado em modelo

<?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>

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 (consulte Recursos). 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 (consulte Recursos). (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

  1. No tempo t1, a solicitação do cliente R1 referente a um recurso chega ao servidor.
  2. 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.
  3. 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.
  4. O cliente renderiza A1 e permite que o usuário modifique a representação para A1'.
  5. O cliente pode enviar mais solicitações R2 e R3 para acessar mais recursos e recebe A2 e A3, respectivamente, como respostas.
  6. O cliente pode permitir que o usuário modifique A2 ou A3 também.
  7. 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'.
  8. 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

Solicitações idempotentes x solicitações transacionais

No jargão do HTTP, uma solicitação é idempotente ou não idempotente. No contexto do JEST, a categorização útil de uma solicitação de HTTP determina se a operação de JPA resultante requer uma transação do banco de dados ou não. Uma operação é idempotente quando produz o mesmo resultado para a mesma entrada quando é executada mais de uma vez. O verbo de HTTP GET é um exemplo. Espera-se que diversas solicitações do cliente referentes a um recurso como uma página HTML estática obtenham a mesma página. No contexto do JEST — no qual as solicitações de HTTP são convertidas efetivamente para operações da JPA — nenhuma solicitação, em termos estritos, é idempotente. Não há garantia de que duas solicitações sucessivas de PurchaseOrder 2345 produzam o mesmo resultado, porque entre as solicitações o status de PurchaseOrder 2345 pode mudar no banco de dados. O verbo de HTTP PUT também é uma operação idempotente. No contexto do JEST, dois PUTs sucessivos com cargas úteis idênticas gerarão uma exceção Duplicate Primary Key que faz com que a segunda solicitação falhe.

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 (consulte Recursos). 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.

    Por que o segmento de consulta para os argumentos?

    O JEST codifica argumentos de operação de JPA no segmento de consulta do URI de uma solicitação GET porque é possível usar um URI de JEST para especificar uma sequência de consultas de JPQL. Uma sequência de consultas de JPQL pode conter caracteres que não são permitidos no segmento do caminho de um URI.

    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 (consulte Recursos); 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

Veja a figura completa aqui.

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

Obter produtos e tecnologias

  • OpenJPA: faça o download do OpenJPA.

Discutir

  • Participe da comunidade do developerWorks. Entre em contato com outros usuários do developerWorks e explore os blogs, fóruns, grupos e wikis voltados para desenvolvedores.

Sobre o autor

Pinaki Poddar

Pinaki Poddar trabalha em tecnologia middleware com ênfase em persistência de objetos. Ele é um membro do Expert Group para a especificação Java Persistence API (JSR 317) e voluntário do projeto Apache OpenJPA. No passado, ele contribuiu para a construção de middleware de integração orientada a componentes para um banco de investimento global e uma plataforma de processamento de imagens médicas para o segmento de mercado de assistência médica. Para sua tese de doutorado, ele desenvolveu um sistema local de reconhecimento de voz automático, com base em rede neural.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

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.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=657641
ArticleTitle=JEST: REST em OpenJPA
publish-date=07202011
author1-email=ppoddar@us.ibm.com
author1-email-cc=

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).