sábado, 18 de dezembro de 2010

Hands-on: Dependency Management com Maven - Parte 1

"E disse: Maven, a vida é pra valer..."

Neste post, listarei algumas vantagens de se usar o maven em projetos Java - todas de situações que já vivenciei na pele, e que o Maven resolve com agilidade e elegância. No final, segue uma série de screenshots mostrando desde a instalação do Maven no Eclipse, à criação de um projeto e à configuração de algumas dependências.

Happy Maven!

A parte 2 está disponível aqui.

Por que usar Maven?

  • Organização da Aplicação: layout-padrão amplamente aceito entre desenvolvedores
    • classes principais em src/main/java
    • classes de teste em src/test/java
    • arquivos de configuração e recursos em src/main/resources e src/test/resources
    • arquivos de aplicações web (html, jsps, css, etc) em src/main/webapp
  • Baixar e configurar automaticamente as dependências do projeto
    • bibliotecas (jars)
    • os javadocs das dependências, úteis para recursos como auto-complete das IDEs
    • os códigos-fonte das dependências, úteis para debug
  • Configuração de Builds da Aplicação
    • Possui tarefas para compilar (mvn:compile), testar (mvn:test) e empacotar (mvn:package) a aplicação.
    • Build pode ser customizado (mvn:build)
  • Versionamento de código
    • Não coloca jars no controle de versão: Menor tamanho-util do projeto no SCM
    • O pom.xml armazena o histório de dependências
      • Se num determinado branch é usado GWT 1.7, e em outro branch é feito o upgrade para o GWT 2.0
      • Como o pom.xml é versionado, ao migrar entre os branches, o Maven saberá exatamente qual versão da dependência utilizar
  • Trabalho em equipes / Configurações de SO e IDE
    • Versionamento de configurações de IDE são muito frágeis
      • Atreladas ao Sistema Operacional (por exemplo, localização de bibliotecas no filesystem)
      • Atreladas à versão da IDE (pode dar conflito quando são usadas versões diferentes da mesma IDE)
      • Atreladas à IDE (impede que o desenvolvedor utilize sua IDE favorita)
    • Como o Maven pode ser utilizado independente de SO, IDE ou versão da IDE, ele resolve também estes problemas.

ATENÇÃO

Quando mal-configurado, faz "backup da internet inteira no seu micro" - culpa de quem o configurou!

Hands On

Instalar Plugin Maven no Eclipse

Help > Install new software

Digitar o update site do Maven e clicar em Add...

Fornecer um nome para o Repositório Maven (neste caso, M2E)

Selecionar o plugin para a instalação

Em seguida, basta aceitar a Licença de Uso do Maven, aguardar o download e instalação do plugin M2E e reiniciar o Eclipse.


Criar projeto Maven

Após reiniciar o Eclipse, vamos criar um projeto Maven

Ao tentar criar o projeto, o plugin M2E alerta que o Eclipse precisa estar rodando sobre uma JDK. Por padrão, o Eclipse roda sobre a JRE default do sistema, que é simplesmente uma Máquina Virtual Java. O Maven, porém, depende de bibliotecas do Kit de Desenvolvimento Java (JDK) para funcionar. 

Caso ainda não tenha feito, instale uma JDK em sua máquina (por exemplo OpenJDK ou a Oracle JDK), e siga os passos abaixo para o plugin M2E funcionar.

Configurando JDK para o Maven

JRE nas Preferências do Eclipse

Window > Preferences

Java > Installed JREs (note que o Eclipse está usando a jre6 por default). Selecione a jre6 e clique em Editar.

Altere a propriedade JRE home para o diretório onde sua SDK foi instalada, e a propriedade JRE name para jdk6 (ou outro de sua preferência).

Pronto, agora o Eclipse está configurado para usar a Máquina virtual da JDK ao invés da JRE padrão.

Parâmetro -vm no eclipse.ini

Resta apenas alterar a Máquina Virtual sobre a qual o Eclipse é inicializado, o que é feito no arquivo eclipse.ini. localizado no diretório do Eclipse.

Adicione, no início do arquivo, o parâmetro -vm, apontando para a localização do executável javaw.exe que existe no diretório bin da instalação da JDK.

Criar projeto Maven (continuação)

Ao tentar criar o projeto Maven novamente, a mensagem de erro do Maven rodar sobre JDK não mais deve aparecer, e a sequência é ilustrada pelas telas abaixo.

Na tela seguinte, marque a checkbox "Create a simple project", pois neste primeiro momento, não queremos trabalhar com nenhum arquétipo do Maven.

Ao prosseguir, é necessário informar os dados do projeto. Em Group Id, é uma boa prática colocar o nome do pacote do projeto, em Artifact Id, o nome do Projeto. Por enquanto, deixe as demais opções como estão e clique em Finalizar.


Resultado:

Note que o projeto criado pelo Maven foi configurado, por padrão, para usar um ambiente de execução Java 5. Isto pode ser consertado com alguns passos simples:

Clique com o botão direito sobre o nome da JRE do projeto, e selecione a opção Configure Build Path

Selecione a JRE e clique no botão Edit

Marque a opção "Workspace default JRE" ao invés de "Execution Environment / J2SE5"

Layout final do Projeto e o arquivo POM (Project Object Model)

Agora, com o projeto corretamente configurado, abra o arquivo pom.xml. Trata-se de um arquivo xml onde são feitas as configurações do projeto. O Eclipse possui editores visuais que facilitam a edição deste arquivo, evitando que tenha que escrever xml "à mão". O arquivo xml pode ser editado diretamente, na aba pom.xml, mas para este tutorial, usaremos os recursos visuais que o Eclipse oferece.



Adicionando algumas dependências no Projeto

Como em próximos artigos pretendo abordar o desenvolvimento de projetos GWT usando Maven, simularei a adição das bibliotecas gwt-user e gwt-servlet no projeto. O mesmo conceito é válido para adicionar a maioria das bibliotecas existentes, como Spring, Hibernate, Guice, Struts, etc... Na aba Dependency, clique em Add.

Digite o nome da biblioteca que deseja localizar. Note que o Eclipse exibe o resultado da pesquisa exibindo o groupId (com.google,gwt) e o artifactId (gwt-user) do artefato localizado. Note também que, por padrão, o Eclipse realiza pesquisa de dependências no repositório central do maven (http://repo1.maven.org/).


É possível configurar o Eclipse para pesquisar dependências em outros repositórios - por exemplo https://repository.jboss.org/nexus/content/groups/public/. Para tal, é necessário configurar um arquivo MAVEN_HOME/settings.xml (isso será abordado em um outro artigo).

Importamos também a biblioteca GWT-Servlet:

Depois de adicionar as dependências e salvar o arquivo pom.xml, o plugin do Maven se encarrega de baixar as dependências e configurar o Build Path da aplicação.

Pronto! O projeto está configurado, e as bibliotecas do GWT disponíveis para desenvolvimento.
Note, ao lado das bibliotecas em Maven Dependencies, que o plugin do Maven as salvou no diretório HOME do usuário, em um subdiretório .m2. Este será o repositório de bibliotecas que o Maven utilizará para gerenciar as dependências dos projetos.

Por agora é só, espero que este tutorial seja esclarecedor e motive a utilizar essa ferramente poderosa que é o Maven!

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.

domingo, 31 de outubro de 2010

Desenvolvimento GWT usando MVP com Activities e Places

Esta semana, foi anunciado o esperadíssimo lançamento do GWT 2.1. Neste release, o GWT finalmente incluiu um framework MVP baseado nas best practices recomendadas no Google I/O 2009.

Este post é a tradução que fiz para a documentação deste novo framework. Particularmente achei a documentação pouco didática e relaxada com os novos usuários.

Para os usuários já acostumados com o estilo GWT de desenvolver (baseado em MVP/EventBus para promover baixo acoplamento e facilitar o teste unitário, e PlaceManagers atuando como controllers que gerenciam o estado da aplicação e a URL do browser), a solução proposta é interessante e elegante, padroniza um estilo de programação que antes ficaria a cargo do desenvolvedor arquitetar e implementar.

Para os novos usuários, a documentação é deveras extensa, e ficaria bem melhor documentada se fosse recheada com imagens e diagramas ao invés de apenas descrições e código. Deixo como promessa aos colegas entusiastas do GWT que, nas próximas semanas, envidarei esforços para didatizar e criar diagramas que facilitem este aprendizado.

Boa Leitura!


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


O GWT 2.1 traz inclusa uma framework para desenvolvimento MVP. Este artigo demonstra as Activities e Places no GWT 2.1, relacionadas aos aspectos View e Presenter do MVP. Num artigo futuro, olharemos mais a fundo as novas funcionalidades para o Model (RequestFactory, EntityProxy e Data Binding). As funcionalidades relacionadas à Model podem ser usadas independentemente das Activities e Places, ou podem ser usadas em conjunto, usando um ProxyPlace e classes relacionadas.

Se o MVP é um conceito novo para você, dê uma olhada nestes artigos:

Definições

Uma Activity no GWT 2.1 é análoga ao presenter, na terminologia MVP. Ela não contém widgets ou código da UI. Atividades são iniciadas e encerradas por um ActivityManager associado a um Widget. Um nova e poderosa feature no GWT 2.1 é que uma atividade pode automaticamente exibir um alerta de confirmação, quando a atividade está prestes a ser encerrada - por exemplo quando o usuário navega para um novo Place. Além disso, o ActivityManager alerta o usuário quando a janela está prestes a ser fechada.

Um Place no GWT 2.1 é um objeto Java representando um estado particular da UI. Um Place pode ser converdido em um token de histórico de navegação (veja o objeto GWT History), e vice-versa, ou seja, uma URL com token de navegação pode ser traduzida em um Place. Para tal, define-se um PlaceTokenizer para cada Place, e o PlaceHistoryHandler automaticamente atualiza o navegador com a URL correspondente a cada Place da aplicação.

O código referenciado neste artigo pode ser baixado nesta aplicação de exemplo, que consiste em um simples "Hello World!" com uma view adicional para ilustrar a nevegação.

Vamos dar uma olhada em cada uma das partes de uma aplicação GWT 2.1 usando Places e Activities.

Views

Um conceito chave do desenvolvimento MVP é que uma View é definida por uma interface. Isto permite que múltiplas implementações da View, baseadas em características do cliente (por exemplo, se ele é mobile ou desktop), e torna mais leve o teste unitário, pois avita o uso do GWTTestCase, cuja execução é mais demorada.

O GWT não obriga que as interfaces View da aplicação extendam ou implementem alguma classe ou interface. O GWT 2.1 introduz uma interface IsWidget, implementada pela maioria dos Widgets GWT. Quando uma view provê um widget, é recomendável que ela estenda IsWidget, como no exemplo abaixo:

A implementação correspondente extende Composite, evitando que sejam expostas dependências a Widgets específicos da implementação.

Abaixo, uma view um pouco mais complicada, que declara uma interface para o Presenter (Activity) correspondente:

A interface Presenter e o método Presenter permite uma comunicação Bidirecional entre View e Presenter, que simplifica as interações envolvendo widgets repedidos, além de permitir implementações a usarem UiBinder com métodos @UiHandler que delegam chamadas diretamente para o Presenter, como no exemplo abaixo.

Segue o template uiBinder desta implementação:

Devido ao fato que a criação de widgets envolve operações DOM, views têm um custo alto de criação. Uma boa prática, portanto, é torná-las reusáveis, e uma forma relativamente fácil de fazer isto é através de uma View Factory, a qual pode fazer parte de uma ClientFactory.

ClientFactory

Apesar de não ser estritamente necessário, é muito útil fazer uso de fábricas, ou frameworks de Injeção de Dependências com o Google GIN, para obtenção de referência aos objetos necessários à aplicação - por exemplo o Event Bus. Este exemplo faz uso de uma ClientFactory para prover implementações para um EventBus, um PlaceController e para as Views.

Usando ClientFactories em conjunto com o mecanismo de deferred binding do GWT, é possível obter diferentes implementações de objetos, baseadas em propriedades como o user.agent. Por exemplo, a aplicação pode usar uma MobileClientFactoryImpl ou uma DesktopClientFactory.

Para tal, a ClientFactory deve ser instanciada usando GWT.create:

As classes de implementação devem ser especificadas no descritor gwt:

A tag pode ser usada para especificar diferentes implementações de acordo com o user.agent, com o locale, ou por propriedades próprias da aplicação. Segue um exemplo de implementação de uma ClientFactory:

Activities

Classes Activity devem implementar a interface com.google.gwt.app.place.Activity. Por conveniência, pode-se também extender a classe AbstractActivity, que provê implementações default vazias para cada um dos métodos requeridos. segue o código de Helloactivity, que simplesmente diz olá para um usuário:

A primeira coisa a se notar é que HelloActivity tem uma referencia HelloView. Uma forma de codificar MVP é definir a interface View no próprio Presenter. Apesar de ser um estilo perfeitamente legítimo, não há nenhuma razão fundamental para uma Atividade e sua interface View correspondente serem altamente acoplamas uma com a outra. Note que a atividade implementa a interface Presenter que foi declarada na View. Isto permite que a View possa chamar métodos da Atividade, o que fcilita o uso dos métodos @UiHandler do UiBinder.

O construtor de HelloActivity recebe dois argumentos: HelloPlace e ClientFactory. Nenhum é estritamente obrigatório para uma atividade. HelloPlace simplesmente facilita para HelloActivity a ontenção de propriedades d estado representado pelo HelloPlace (neste caso, o nome do usuário que estamos cumprimentando). A passagem de HelloPlace no construtor implica que uma nova HelloActivity deverá ser criada para cada HelloPlace.

No GWt 2.1, Activities são desenhadas para serem descartadas, enquanto as views, cujo custo de criação é mais caro devido às chamadas DOM envolvidas, devem ser reusáveis. Em concordância com esta idéia, ClientFactory é usada pela HelloActivity para obter as referências para HelloView, bem como ao eventBus e ao PlaceController, papel que pode também ser desempenhdo usando Injeção de Dependências com o Gin.

O método start é invocado pelo ActivityManager e coloca as coisas em ação. Ele atualiza a view e então a insere dentro do widget de conteúdo [da página de host], através da chamada

Ao implementar mayStop(), HelloView provê um alerta a ser mostrado para o usuário quando a atividade estiver prestes a ser encerrada devido ao fechamento da janela ou à navegação para outro Place. Caso retorne null, nenhum alerta será exibido.

Finalmente, o método goTo invoca o PlaceController para navegar para um novo Place. PlaceController, por sua vez, notifica o ActivityManager para encerrar a Activity atual, e encontrar / iniciar a Activity associada ao novo Place, além de atualizar a URL no PlaceHistoryHandler.

Places

Além de ser acessíveis via URL, uma Activity deve corresponder a um determinado Place, que deve estender com.google.gwt.app.place.Place, e ter um PlaceTockenizer que saiba como serializar seu estado para um token URL. Por padrão, a URML consiste no nome não-qualificado (sem prefixo referente ao nome pacote) do Place, seguido por dois pontos (:) e o token retornado pelo PlaceTokenizer.

É conveniente - apesar de não obrigatório - declarar o PlaceTokenizer como uma classe interna dentro do próprio Place. Porém, não é necessário ter um PlaceTokenizer para cada Place. Muitos Places da aplicação podem não precisar salvar estado na URL, então eles podem simplesmente estender BasicPlace, que declara um PlaceTokenizer que simplesmente retorna null.

PlaceHistoryMapper

É uma interface que serve para mapear todos os Places disponíveis na aplicação. Deve-se criar uma interface que estende PlaceHistoryMapper, e usa a anotação @WithTokenizers para listar cada uma das classes Tokenizer, como no exemplo abaixo:

Em tempo de compilação, o GWT gera uma classe baseada nesta interface, que é o elo entre os PlaceTokenizers da aplicação e os PlaceHistoryHandler do GWT que sincronizam a URL do navegador com o estado do Place.

Para se ter maior controle sobre o PlaceHistoryMapper, é possível usar uma anotação @Prefix para alterar a primeira parte da URL associada a um Place. Para um controle ainda maior, pode-se implementar um PlaceHistoryMapperWithFactory na aplicação para prover uma TokenizerFactory, que por sua vez provê os PlaceTokenizers.

ActivityMapper

Finalmente, a aplicação deve possuir um ActivityMapper para mapear Places em Activities. Segue um exemplo:

Uma implementação mais elegante do que esta cadeia de if/else's é realizar as configurações correspondentes em módulos do GIn.

Juntando as peças

O EntryPoint da aplicação ficará parecido com o exemplo abaixo:

Como isso tudo funciona

O ActivityMapper gerencia as Activities rodando no contexto de um determinado container widget [da página de host]. Ele escuta por PlaceChangeRequestEvents e notifica a atividade atual quando um novo Place é solicitado. Caso a Activity vigente permita a mudança do Place (quando Activity.mayStop() retorna null), ou quanto o usuário permite que o Place mude (ao clicar OK na caixa de diálogo), o ActivityManager descarta a Activity atual e inicia uma nova. Para determinar a nova Activity, o ActivityManager usa o ActivityMapper para obter a Activity associada ao novo Place solicitado.

Justamente com o ActivityManager, duas outras classes trabalham para gerenciar os Places da aplicação. PlaceController inicia a navegação para um novo Place, sendo responsável por alertar ao usuário sobre as transições entre Places. PlaceHistoryHandler provê um mapeamento bidirecional entre Places e a URL. Toda vez que a aplicação navega para um novo Place, a URL será atualizada com um novo token, representando o Place, de forma que ele pode ser salvo na lista de favoritos do navegador, bem como a URL seja adicionada ao histórico de navegação - permitindo que os botões voltar e avançar sejam corretamente interpretados pela aplicação. Inversamente, quando uma determinada URL é solicitada pelo navegador - seja ela digitada, clicada na lista de favoritos, ou respondendo a ao clique nos botões avançar ou voltar do navegador, o PlaceHistoryHandler assegura que a aplicação carregue o Place correspondente.

Como navegar

Para navegar para um novo Place na aplicação, basta chamar o método goTo() no PlaceController, fato que é ilustrado na chamada HelloActivity.goTo(). Através de um PlaceRequestEvent, PlaceController alerta avisa à Activity atual que ele deve estar parando, e uma vez que isto seja autorizado, dispara um PlaceChangedEvent, com o novo Place. O PlaceHistoryHandler escuta por PlaceChangeEvents e atualiza o token de histórico da URL da forma apropriada. O ActivityManager também escuta por PlaceChangeEvents, usando o ActivityMapper da aplicação para iniciar a Activity associada ao novo Place.

Ao invés de usar PlaceController.goTo(), pode=se também criar um hiperlink contendo o token para o novo Place. Quando o usuário navegar para a nova URL (seja via hiperlink, botões voltar/avançar, lista de favoritos, etc), PlaceHistoryHandler captura ValueChangeEvent do objeto History e chama o PlaceHistoryMapper para transformar o token no Place correspondente, e então invocar laceController.goTo() com o novo Place.

E caso aplicações que tenham múltiplos painéis na mesma janela, cujos estados devem ser todos salvos na mesma URL? GWT 2.1 não tenta prover uma implementação genérica de um Composite Plac; Porém, sua aplicação pode criar um CompositePlace, uma CompositeActivity, e um CompositePlaceTokenizer, delegando a eles esta responsabilidade. Neste caso, apenas os objetos composite deverão ser necessários para registro no ActivityMapper e PlaceHistoryMapper da aplicação.

quarta-feira, 4 de agosto de 2010

Fontes dos Artigos sobre GWT publicados na MundoJ 40 e 41


Pra galera que leu e gostou dos artigos, segue repositório onde coloquei o código da aplicação de exemplo.

http://code.google.com/p/gwt-mundoj/

Cada commit corresponde a um conceito que foi desenvolvido nos artigos, então é possível acompanhar o artigo baixando sequencialmente cada revisão do repositório, à medida que avançar na leitura da revista.

Espero que possa ser útil a quem desejar conhecer os conceitos-core do GWT, e torne o aprendizado mais suave e divertido.

quarta-feira, 28 de julho de 2010

Quase 6 meses sem tempo para postar

Well, well, quase 6 meses sem postar nada... De fevereiro pra cá muitas coisas aconteceram:

  • Estudo incansável de GWT em fevereiro/março
  • Servidor Dedicado Expresso na Locaweb foi descontinuado - gastei o mês de março inteiro configurando um novo servidor cloud - o tempo foi maior pois aproveitei a oportunidade para gravar os vídeos da configuração do servidor linux, usando o VMWare. Valeu à pena, agora tenho tudo documentado.
  • Entre abril e junho, brincadeira de gente grande, minha primeira aplicação com GWT, integrando as atividades de Planejadores, Mecânicos de Aeronaves, Analistas de Controle de Qualidade e Técnicos de Controle de Manutenção da Webjet. Os desafios foram muitos, mas o resultado final foi simplesmente EXCELENTE!

Estou cada vez mais encantado com o poder do GWT e as possibilidades que ele abre. A Aplicação server-side ficou beeeem mais simples, consegui deslocar parte do processamento da aplicação para o lado do navegador, as UIs ficaram mais ricas, responsivas e interativas.

Publiquei dois artigos na MundoJ:

  • MJ40: Hello GWT! Uma Exploração dos Recursos da Caixa de Ferramentas da Google
  • MJ41: Uma Arquitetura de Aplicação GWT usando o padrão MVP
cujos fontes estou disponibilizando no Google Code: gwt-mundoj.

Também foi um tempo de mudança de cultura: comecei a aprender com mais seriedade os testes unitários, a me aculturar com TDD, aprendi um novo paradigma de programação com GWT bastante diferente do que estava acostumado.

Na parte de gerenciamento de projetos, senti na pele o quanto é NÃO trivial gerenciar uma equipe, ainda que pequena. São tantas as preocupações - desde as culturas de controle de versionamento, à integração contínua - que ainda tenho que aprender, e ao mesmo tempo ensinar aos colaboradores.

Na parte de motivação, notei a mentira no "mito das cenouras" - pessoas não são motivadas por cenoura na frente, muito menos atrás! Vide Drive 1/2 e Drive 2/2.

Mas aprendi também, ao gerenciar colaboradores, que liberdade é um valor que deve ser dado desde o primeiro momento, porém que deve ser continuamente posto à prova - tem que haver feedback contínuo! Isso de forma natural e transparente, para não deixar que a liberdade vire bagunça e falta de comprometimento. Uma vez perdida a confiança, é difícil retomar o ambiente de trabalho para o estado ideal que ele deve sempre possuir.

Não posso deixar de citar também o curso de SCRUM na Caelum. A Cecília, instrutora do curso, é muito gente boa, e me deixou com a maior vontade de mandar um currículo para trabalhar na Caelum tb!

O curso foi bom, no sentido que reforçou os conceitos que havia aprendido nos cursos da Fratech que assisti em 2008. Alguns conceitos do SCRUM eu estava aplicando de forma equivocada, e agora estou tentando aplicá-lo por completo, não mais aos pedaços. Não posso deixar de passar o bizu do link EPF/SCRUM, um material bem organizado e bem escrito para quem quer se familiarizar ou ter um roteiro de trabalho para esta metodologia ágil.

É isso aí! Para este ano ainda resta bastante trabalho, mas na medida do possível, tentarei não deixar este blog tão de lado. É sempre bom, em determinados momentos da vida, olhar para as coisas que fizemos no passado, e perceber o quanto amadurecemos, ter noção de quão grande o caminho que já trilhamos para alcançar nossos sonhos e objetivos.

Hoje foi um desses momentos, onde vejo que já passamos da metade do ano, mas tudo o que já foi feito em 2010 já rendeu muitos aprendizados, experiências, e conquistas!

segunda-feira, 15 de fevereiro de 2010

Classicismo Tecnológico

Em Arte, o Classicismo refere-se, geralmente, à valorização da Antiguidade Clássica como padrão por excelência (...), que os classicistas pretendem imitar.

Até 2 semanas atrás, estava estudando / exercitando GWT 2.0 em um módulo de uma aplicação web que desenvolvo.

Naquele momento, o cliente fez uma solicitação que demandaria processamento local, algo como o usuário selecionar arquivos em sua máquina e os enviar para um servidor FTP. Porém esta funcionalidade deveria ser disponibilizada e integrada com as telas / workflows da aplicação (não bastava ensinar ao usuário como usar um cliente FTP).

Apesar de nunca ter desenvolvido applets para sistemas em produção, escolhi esse caminho. "Forçado" a desbravar o mundo das applets, fiquei surpreso com o grau de similaridade entre as API do GWT e do Swing. Foi possível usar uma arquitetura semelhante, adotar os mesmos padrões, etc...

Gostei de trabalhar com o Swing também pelo fato de as IDEs (Netbeans ou Eclipse VE) possuirem suporte visual para construção das UIs.

Caso a view seja pouco acoplada com o resto da aplicação (por exemplo adotando o padrão MVP), é possível desenhar toda a UI através desses editores visuais, sem que isso afete a lógica da aplicação.

Desta forma, foi possível construir toda a aplicação, desacoplada da View; enquanto, na base da experimentação, tentativa/erro, fazia e refazia a UI, até que ela chegasse ao formato desejado.

Algumas conclusões no final do trabalho - No Swing:

  • A curva de aprendizado pareceu menor (API madura e boa disponibilidade de documentação);
  • O suporte das IDEs (tanto Netbeans quanto Eclipse/VE) é bom;
  • Pode-se usar recursos locais da máquina do usuário - coisa difícil de se obter em uma aplicação web;
  • O tempo de download do applet (tanto pela página quanto pelo JWS) é similar ao tempo de download das bibliotecas-base do GXT;
  • Swing parece estável, enquanto no GXT já experimentei alguns bugs;
  • Menos problemas com licenças (o GXT é GPL);
  • O produto final ficou muito bom - visualmente e funcionalmente;
  • A barreira de instalação da JVM parece ser da mesma ordem de grandeza quanto a barreira de instalar o Flash ou de garantir que a aplicação se comportará de forma idêntica entre todos os navegadores disponíveis.

Depois de listar tantas qualidades, comecei a procurar defeitos, daí nasceu a pergunta que originou esse post no RioJUG.

Em nenhum momento estou dizendo que uma abordagem é boa e a outra ruim; apenas não consegui entender o porquê de uma tecnologia tão madura como Swing / JWS / Applets ser pouco utilizada nas aplicações atuais.

Também fiquei surpreso ao perceber que novas tecnologias, como o GWT/GXT se assemelham às antigas, e que as decisões de design para programar numa (web) ou noutra (swing) são muito parecidas.

Estaríamos vivenciando um momento de classicismo tecnológico, buscando inspiração em tecnologias do passado para ajudar a resolver os problemas do futuro?

sexta-feira, 8 de janeiro de 2010

Aplicação de Exemplo usando Hibernate/JPA e DAOs Genéricos

Desafio DF-JUG

Este post foi escrito com o objetivo de responder ao desafio proposto pelo colega Roberto Messa no DFJUG em 07/01/2010. A solução proposta pelo Messa era baseada em JDBC puro, usando DAOs que encapsulavam comandos SQL. Seria interessante se aproveitássemos o desafio para fazer um estudo comparativo entre várias tecnologias, mostrando como cada uma delas soluciona o problema.

Persistence.xml - Configurar a conexão que será usada pela Unidade de Persistência

É o único XML da aplicação, onde configuramos o JPA.

Vantagens

  • caso a aplicação não utilize recursos específicos do BD, para trocar de SGBD basta
    • reconfigurar o dialeto,
    • o driver
    • e a URL de conexão.
  • Não é necessário recompilar a aplicação.
  • O Hibernate gera as tabelas automaticamente... (não preciso das SQL para DDL)

Interface GenericDao.java

Classe JpaDao.java

Fornece a implementação básica do Dao, que pode ser reutilizada para todos os demais. Acabou ficando um pouco extensa, pois incluí nela lógica transacional, estilo BMT (Bean Managed Transactions - soluções declarativas são mais robustas e adequadas, mas para fins didáticos, manterei BMT através do DAO).

Acrescentei no construtor algumas linhas, para o DAO saber qual o tipo genérico com o qual foi parametrizado.

Começa a brincadeira

ProdutoDao

Vazio... (apenas extende e parametriza JpaDao).

Produto.java

Um POJO.... (maior parte gerada usando a IDE - getters, setters e construtores).

Main.java

Respondendo ao desafio:

Saída

Rebatendo desafio, parte 1: Controle de Transações

Caso vc esteja realizando operações que salvam varios registros, e ocorre uma exceção no meio da operação, qual a estratégia? O que foi salvo, continua no BD e o que nao foi salvo se perde? Desfaz todas as alterações? Mesmo usando BMT, que é feio, o exemplo abaixo ilustra essa situação:

Saída

Ou seja, a aplicação chegou a salvar A2 (o ID foi gerado), mas após a exceção em A3, a aplicação fez rollback na transação de desfez todas as operações.

Rebatendo desafio, parte 2: Mais entidades

Vamos "pagar", ou "recuperar o investimento" do código extra de JpaDao na primeira iteração:

ClienteDao.java

Cód. Adicional em Main.java:

Cliente.java é só mais um POJO...

Rebatendo desafio parte 3: Relacionamentos e Cascateamento de Persistência

De uma tacada só, vou salvar um Pedido e seus Itens (OneToMany). Também há métodos adicionais, para calcular total do Pedido.

PedidoDao.java

Pedido.java

ItemPedido.java

Main.java

Em pedidoDao.save, salvo, em uma única linha, 3 objetos: o pedido, e seus dois itens.

Saída

SQL

Eu particularmente só gosto de SQL quando preciso descer no nivel do BD para analisar os dados salvos... Vejamos tudo o que o JPA/Hibernate geraram, sem que eu tenha escrito nenhum SQL em toda a aplicação...