segunda-feira, 1 de novembro de 2010

Familiarização com a API RequestFactory do GWT 2.1

Uma das novidades introduzidas pelo GWT 2.1 diz respeito a uma nova abordagem a serviços orientados a dados, responsáveis pelas operações CRUD da aplicação. Esta abordagem oferece formas de automatizar as tarefas relacionadas ao tratamento dos DTOs, responsáveis por trafegar os dados entre cliente e servidor.

Além disso, o GWT finalmente passa a oferecer uma solução para o binding entre os dados dos objetos e os widgets da UI. Outra vantagem de utilizar esta API é que ela oferece funcionalidade para cache de objetos; atualização das views quando perceber que um objeto na cache sofre alterações; menor payload na comunicação cliente-servidor, pois apenas as diferenças (deltas) são enviadas em cada requisição; e suporte a JSR-303 (Beans Validation).

Este post é a tradução que fiz para a documentação deste novo framework. Boa Leitura!

Tradução da Documentação Oficial publicada em http://code.google.com/intl/pt-BR/webtoolkit/doc/latest/DevGuideRequestFactory.html

RequestFactory é uma alternativa ao GWT-RPC para criação de serviços orientados a dados. RequestFactory e suas interfaces relacionadas (RequestContext e EntityProxy) facilitam a construção de aplicações orientadas a dados (CRUD), projetada para ser usada em conunto com uma camada ORM como JPA ou JDO no lado servidor.

Benefícios

RequestFactory facilita a implementação de uma camada de acesso a dados tanto no lado cliente como no lado servidor. Ela permite que o lado servidor seja estruturado numa forma centrada nos dados, e provê um nível maior de abstração do que o GWT-RPC (uma vez que este último é orientado a serviços, e não orientado a dados). No lado cliente, RequestFactory gerencia os objetos que tenham sido modificados, enviando apenas as diferenças para o servidor, o que resulta em payloads de rede muito leves. Além disso, RequestFactory provê uma sólida fundação para batching automático e cache de solicitações.

Como ela se relaciona com GWT-RPC?

RequestFactory usa um servlet próprio, RequestFactoryServlet, e implementa seu protocolo próprio para troca de dados entre cliente e servidor. Não foi desenhada para serviços de propósito geral como o GWT-RPC, mas sim para serviços orientados a dados, conforme veremos a seguir. RequestFactory NÃO utiliza GWT-RPC, e nem pretende substituí-lo. Foi projetada especificamente para implementar uma camada de persistência tanto no lado cliente como no lado servidor.

Codificando com RequestFactory

Vamos dar uma olhada nos componentes de uma aplicação que utiliza RequestFactory (Entity, EntityProxies, Interface RequestFactory e Implementações no Servidor), e em seguida, olharemos como são utilizados.

Entities

Uma entidade é uma classe do domínio da aplicação que pode ser persistida em um datastore, por exemplo um banco de dados relacional, ou no datastore do Google AppEngine. Em frameworks de persistência como JPA e JDO, entidades são anotadas com @Entity. Esta anotação não é exigida pela RequestFactory, mas quando falarmos de entidades neste guia, é sobre estas classes de domínio que estamos nos referindo. Segue abaixo parte da definição de uma entidade da aplicação de exemplo Expenses, distribuída junto com o GWT.

Entity Proxies

Um entity proxy é uma representação client-side de uma entidade, também conhecida como DTO (Data Transfer Object). Deve estender a interface EntityProxy, indicando que um objeto pode ser gerenciado por uma RequestFactory. A RequestFactory popula automaticamente as propriedades entre as entidades no lado servidor e o proxy correspondente no lado cliente, simplificando o uso do pattern DTO. Além disso, a interface EntityProxy possibilita que a RequestFactory avalie e envie apenas as mudanças (deltas) para o servidor. Abaixo, está o EntityProxy correspondente à classe de domínio Employee que acabamos de mostrar.

Além de estender a interface EntityProxy, é necessário também utilizar a anotação @ProxyFor para referenciar a entidade do lado cliente que está sendo representada. Não é necessário representar todas as propriedades e métodos da entidade server-side no EntityProxy, apenas os getters e setters das propriedades que precisem ser acessadas no lado cliente. Note que, apesar desde exemplo mostrar um método getId(), normalmente o código cliente utilizará o método EntityProxy.stableId(), uma vez que o EntityProxyId retornado pela chamada stableId() é usado através pelas classes relacionadas ao RequestFactory. Note também que o método getSupervisor() retorna uma associação a uma classe Proxy. As associações no código cliente também devem ser implementadas como subclasses de EntityProxy. RequestFactory automaticamente converte os proxies para as entidades correspondentes no lado servidor.

A interface RequestFactory

Similar ao GWT-RPC, é necessário definir uma interface entre o código cliente e o código servidor. A interface RequestFactory da aplicação consiste em métodos que retornam stubs de serviços, como no exemplo Expenses:

E o stub do serviço EmployeeRequest:

Os stubs de serviço devem estender RequestContext, e usar a anotação @Service para indicar a classe correspondente da implementação do serviço no servidor. Ao invés dos métodos do stub de serviço retornarem diretamente as entidades, eles retornam objetos que são subclasses de com.google.gwt.requestfactory.shared.Request. Isto permite que os métodos nesta interface sejam invocados de forma assíncrona, pela chamada Request.fire(), de forma similar à passagem dos objetos AsyncCallback nos serviços GWT-RPC.

Da mesma forma que as chamadas GWT-RPC passam um AsyncCallback que implementa os métodos onSuccess e onFailure, Request.fire recebe um Receiver como argumento, que deve implementar o método Request.onSuccess(). Receiver é uma classe abstrata que já oferece uma implementação padrão para onFailure(), que simplesmente dispara uma RuntimeException. Para alterar a implementação padrão, pode-se estender Receiver e sobrescrever o método onFailure(). Receive possui também um método onViolation(), que retorna as violações de regras de validação (JSR 303) no servidor (veja Validação de Entidades no final do artigo).

Os métodos retornam objetos Request que são parametrizados genericamente com o tipo de retorno do método do serviço. Este será é justamente o tipo do objeto esperado como argumento pela chamada onSuccess do Receiver. Métodos que não retornam valor devem retornar Request<Void>. Requests podem ser parametrizados com os seguintes tipos:

  • Tipos-Valor: BigDecimal, BigInteger, Boolean, Byte, Enum, Character, Date, Double, Float, Integer, Long, Short, String, Void
  • Entidades: quaisquer subclasses de EntityProxy
  • Coleções: List<T> ou Set<T>, onde T é um tipo-valor ou uma subclasse de EntityProxy.

Note que tipos primitivos não são suportados, devendo-se usar os wrappers correspondentes. Métodos que operam sobre a entidade - por exemplo persist() e remove() retornam objetos do tipo InstanceRequest ao invés de request, o que será explicado na próxima seção.

Implementações no lado servidor

No lado servidor, os métodos definidos numa interface de serviço de uma entidade são implementados na classe definida na anotação @Service. Nestes exemplos, as implementações foram feitas nas próprias classes das entidades. Ao contrário dos serviços GWT-RPC (cujas implementações devem estender a interface RemoteService), os serviços não deverão implementar a interface RequestContext, pois os argumentos no lado-cliente retornam tipos para os proxies das entidades, enquanto que as implementações server side retornam os proprios objetos de domínio das entidades.

No lado servidor, o serviço deve implementar cada método definido na interface RequestContext, mesmo que isto não signifique implementar formalmente a interface em Java. O nome do método e a lista de argumentos são os mesmos tanto no cliente e no servidor, segundo as seguintes regras de mapeamento:

  • Métodos que no lado cliente retornam Request<T>, retornam apenas T no lado servidor. Por exemplo, se um método retorna Request<String> no cliente, a implementação do lado servidor retorna String.
  • O lado servidor retorna as entidades de domínio onde no lado cliente são retornados EntityProxies. Se o lado cliente retorna Request<List<EmployeeProxy>>, a implementação no lado servidor retornará List<Employee>
  • Métodos que retornam um objeto Request na interface cliente devem ser implementados como métodos estáticos na implementação no servidor como no exemplo Employee.findAllEmployees().
  • Métodos que operam sobre uma instância específica de uma entidade - por exemplo persist() e remove(), devem retornar um InstanceRequest na interface cliente. Estes métodos não recebem diretamente o objeto como argumento, ao invés disso, o objeto é passado em chamadas InstanceRequest.using().

Quantro métodos especiais são obrigatórios para todas as entidades que sejam usadas pelo servlet RequestFactory:

  1. Um construtor sem argumentos - pode ser omitido caso a classe não tenha nenhum construtr explícito, o construtor implícito é usado.
  2. id_type getId()
  3. static entity_type findEntity(id_type id)
  4. Integer getVersion()

O método getId() deve retornar o ID da entidade persistente, o qual deve ser do tipo String ou Long. Normalmente é um valor gerado automaticamente pela infra de persistência (JDO, JPA, Objectify, etc).

O método "find By Id" de uma entidade tem uma convenção de nomes especial. No lado cliente, há um método find() definido já na interface RequestFactory, que os serviços estendem. No servidor, deve haver um método find, seguido do nome da entidade (por exemplo, findEmployee para a entidade Employee), recebendo como argumento o ID da entidade (um String ou Long).

O método getVersion() é usado pela RequestFactory para inferir se uma entidade sofreu alterações. A framework de persistência (JPA, JDO, etc) será responsável por atualizar a versão, toda vez que o objeto for persistido. A RequestFactory chama callVersion() para saber se ocorreram mudanças no objeto.

Esta informação é usada em dois lugares. Primeiro, RequestFactoryServlet envia um evento UPDATE para o cliente caso a entidade seja modificada como resultado da execução de uma operação no servidor, por exemplo, quando ao persistir a entidade resultar em uma nova versão desta entidade, no servidor. Segundo, o cliente mantém uma cache das últimas entidades carregadas. Todas as vezes que a versão de uma entidade mudar, são disparados eventos UPDATE no event bus de modo que os listeners possam atualizar a view.

Juntando todas as peças

Uma vez criadas as entidades e seus proxies, a RequestFactory com suas interfaces de serviços, deve-se criar esta última através de uma chamada GWT.create, e inicializá-la injetando nela o EventBus:

Usando o requestFactory

Configurado o RequestFactory, vejamos como ele funciona. O método RequestContext.create() é usado para criar uma novo proxy de entidade no cliente, o qual é persistido usando um método definido na interface de serviço da entidade:

Todo o código do lado cliente deve usar os proxies das entidades, e não as entidades propriamente ditas. Desta forma, os objetos de domínio não precisam ser compatíveis com o GWT, diferentemente do GWT-RPC, onde o mesmo tipo concreto é usado tanto no cliente como no servidor.

EmployeeProxy é uma interface, não uma classe, devendo seus objetos serem instanciados através de chamadas requestContext.create(EmployeeProxy.class). O benefício desta abordagem possibilitar que RequestFactory gerencie a criação e modificação dos objetos, de forma que ela possa enviar apenas as diferenças para o servidor.

O método persist() não possui argumentos, o que é consistente com a implementação do método na entidade Employee. Na interface EmployeeRequest, este método retorna um objeto InstanceRequest, que por sua vez recebe o objeto a ser persistido através da chamada using().

Vejamos agora o código para salvar o objeto Employee no servidor:

Disparamos um request usando o Receiver, passando para ele um callback que notifica a aplicação quando a solicitação for completada.

Em objetos objetos que não tenham sido criados pela chamada RequestContext.create(), por exemplo aqueles recebidos em consultas ao servidor, é necessário habilitar a edição, através de uma chamada RequestFactory.edit(). Todos os proxies para relacionamentos de um proxy que jpa se encontra no estado editável será também editável.

O método edit() retorna uma nova cópia do proxy, que é inicialmente um objeto imutável, e o proxy original pode ser descartado. Para enviar as alterações para o servidor, deve-se criar um novo request. Todas as edições ocorrem dentro de um contexto específico, e quando este contexto é disparado, todas as edições são enviadas, quaisquer invocações de métodos resultarão em alterações a serem enviadas para o servidor.

Relacionamentos entre Entidades

Alterações nas entidades relacionadas podem ser persistidas em uma única requisição. Por exemplo, este código extraído da aplicação de exemplo DynatableRF, cria uma Pessoa e um endereço em uma única operação:

RequestFactory envia automaticamente o grafo completo do objeto em uma única requisição. Neste caso, a implementação de Person.persist() no servidor é responsável por persistir também a associação Address, o que pode ou não ocorrer automaticamente por regras de cascading, dependendo de qual framework ORM é utilizada e de como o relacionamento for definido usando esta framework. Note que RequestFactory ainda não suporta componentes @Embedded, pois ele espera que toda entidade exista independentemente, com seu próprio ID.

Ao realizar consultas no servidor, RequestFactory não popula automaticamente os relacionamentos no grafo de um objeto. Para carregar os relacionamentos, use o método with() no request, especificando os nomes das proproedades das relações que devem ser carregadas

Validação das Entidades

RequestFactory suporta validação com JSR 303. Isto torna possível manter as regras de validação no lado servidor e notificar o cliente quando uma entidade não puder ser persistida devido a erros de validação. Para usar, assegure-se que uma implementação da JSR-303 esteja disponível no servidor, e anote as entidades com as anotações javax.validation, como @Size e @NotNull. Antes de invocar o método do serviço no servidor, RequestFactory chamará a framework de validação e enviar as ConstraintViolation do servidor para o cliente, o qual chamará o método onViolation() no objeto Receiver usado no request.

Conclusão

RequestFactory está no cerne das novas funcionalidades de Binding no GWT 2.1. Em artigos futuros, estas funcionalidades integradas em Cell Widgets, Editors, ao Event Bus, e Activities e Places.