Principais padrões J2EE para a construção de aplicações não distribuídas – Parte IV
Não perca a quarta e última parte deste tutorial sobre padrões, estratégias de projeto e Struts.
Artigos Java Principais padrões J2EE para a construção de aplicações não distribuídas – Parte IV
Nesta seção discutiremos algumas estratégias para implementação ou utilização de alguns dos padrões apresentados e outras que resolvem problemas ou chamam a atenção para aspectos importantes ainda não abordados.
Uma das limitações das aplicações web e do navegador como interface cliente é a total falta de controle de uma aplicação sobre a navegação. Um usuário pode, por exemplo, submeter um formulário que resulta numa transação que irá debitar uma quantia de sua conta e, depois de receber a confirmação de que a operação ocorreu com sucesso, nada impede que ele clique no botão "Voltar" e re-submeta o mesmo formulário. Para controlar a ordem de acesso dos usuários às páginas e o acesso a determinados
recursos deve-se fazer uso de um mecanismo de token compartilhado.
· quando chega a solicitação de um usuário para a execução de algum processamento que requer controle de etapas um token é gerado, gravado na sessão do usuário e encaminhado para a próxima página do fluxo de execução,
· quando ocorrer uma nova submissão deste usuário, o token que chega dele deve ser o mesmo token que o servidor enviou na sua última resposta, confirmando que não é uma re-submissão ou, quando forem diferentes, sugerindo que isto pode ter acontecido,
· o processo de criação e envio de um novo token é repetido para todas as páginas intermediárias do processamento até chegar na página final, onde a transação deve ou não ser confirmada.
Este processo está ilustrado na Figura 3.1.
Figura 3.1: Ciclo de vida de um token.
Esta implementação permite que a todo momento tenha-se o controle quanto à ordem de interação do usuário com as páginas. Quando alguma comparação de token falhar significa que o usuário acessou a página diretamente, possivelmente através de um bookmark, ou que realizou duas vezes a mesma submissão.
A melhor maneira de gerenciar a geração e comparação do token é através de um controlador. A Struts, por exemplo, disponibiliza, embutido na sua implementação de front controller, mecanismos para controle de transações. Ao invés de criar uma classe utilitária separada para encapsular a lógica de geração e comparação de tokens, Struts provê suporte a isso adicionando esta funcionalidade às classes já existentes que fazem parte do seu mecanismo de controle.
Exposição desnecessária de estruturas de dados
Estruturas de dados da camada de apresentação, tal como objetos da classe HttpServletRequest, devem estar confinados apenas a esta. Compartilhar estes detalhes com a camada de negócios (camada modelo do MVC) ou qualquer outra, aumenta o acoplamento entre estas camadas e reduz drasticamente a reusabilidade dos serviços oferecidos por estas. Se a assinatura de um método em um serviço de negócio aceita um parâmetro do tipo HttpServletRequest então qualquer outro cliente deste serviço deve encapsular sua requisição num objeto do tipo HttpServletRequest. Além disso, a camada de negócios terá que compreender como interagir com estas estruturas de dados
específicas da camada de apresentação, aumentando a complexidade do código.
A solução é modificar os métodos de negócio para aceitarem parâmetros mais genéricos. Uma forma de se conseguir isto é decompondo cada um dos parâmetros contidos em HttpServletRequest para argumentos individuais como strings ou inteiros. No entanto, esta não é a melhor abordagem pois ainda mantém um forte acoplamento com a camada de apresentação. Se as informações necessárias para o método de negócio executar forem alteradas, então necessariamente as assinaturas de seus métodos também terão de ser modificadas. Uma alternativa mais flexível é copiar os dados relevantes da camada de apresentação em uma estrutura de dados mais genérica, como um value object. Neste caso os serviços continuam aceitando este objeto mesmo se os detalhes da sua implementação mudarem.
O exemplo abaixo mostra um objeto fortemente acoplado com o HttpServletRequest.
/** The following excerpt shows a domain object that is too tightly coupled with HttpServletRequest **/
public Customer ( HttpServletRequest request )
lastName = request.getParameter("lastname ");
Ao invés de expor o objeto HttpServletRequest ao objeto genérico Customer, decomponha os dois valores e crie uma classe assim:
public Customer (String first, String last )
Restringindo Acesso às Páginas JSP
Freqüentemente é necessário controlar o acesso a determinadas páginas JSP em uma aplicação. Quando existe um controlador, todo o acesso passa por este e o controle de permissões pode ser feito de forma centralizada. Uma opção para garantir maior proteção é tornar as páginas JSP totalmente inacessíveis, exceto para o controlador, movendo-as para baixo do diretório WEB-INF.
Por exemplo, considere uma aplicação chamada HelloWorld. Por padrão, nada impede que façamos:
http://localhost:8080/HelloWorld/secure_page.jsp
Para restringir acesso direto podemos simplesmente mover o arquivo JSP para um subdiretório de WEB-INF. O diretório WEB-INF é acessível apenas indiretamente através de redirecionamento, tal qual aqueles que chegam através de um controlador. Desta forma, se a página for acessada diretamente
http://localhost:8080/HelloWorld/WEB-INF/privateaccess/secure_page.jsp
o servidor responderá com uma mensagem de "recurso não disponível".
Em geral é desejável que as validações de entrada de dados sejam feitas tanto no cliente como no servidor. Apesar da validação no lado cliente ser menos sofisticada, esta possibilita verificar mais rapidamente, com menor tráfego na rede e sem ocupar recursos do servidor, se as informações submetidas estão com a formatação esperada. Não é recomendado que a validação esteja apenas no cliente pois esta depende de configurações locais que podem desabilitar a execução de \textit{scripts} pelo navegador.
Tipicamente validações no cliente envolvem embutir código, como JavaScript, nas visões JSP e não serão comentadas aqui em maiores detalhes. Validações no servidor podem ser categorizadas de duas formas: validação centrada em formulários e validação baseada em tipos abstratos.
Na validação centrada em formulários, para cada formulário é criado um método Java de validação. Todo código de validação para verificar, por exemplo, valores nulos ou se um campo é ou não numérico, é escrito nestes métodos que acabam tendo muito código em comum. Neste caso, como não existe um código central para tratar das validações, havendo campos em vários formulários que precisem ser validados da mesma maneira, cada um é manipulado separadamente e de forma redundante em vários lugares da aplicação. Esta estratégia é relativamente fácil de implementar e funciona, porém leva à duplicação de código e a uma conseqüente ineficiência de seu reaproveitamento.
Uma solução mais flexível, reutilizável e fácil de manter consiste em abstrair os tipos dos campos e restrições impostas sobre eles e separar a validação do modelo da lógica da aplicação. Um benefício desta estratégia é que o sistema torna-se mais genérico pois fatora as informações de tipo dos dados que formam o modelo (metadados) e suas restrições para fora da lógica da aplicação. Um exemplo desta implementação é ter um componente que encapsula lógica de validação tais como: decidir se uma string é vazia, se um número esta dentro de um intervalo, se um conjunto de caracteres está formatado de uma determinada maneira e assim por diante. Quando uma aplicação precisa validar diferentes aspectos do modelo em lugares diferentes, não é necessário cada um deles escrever seu próprio código de validação, ao invés disso o mecanismo central de validação é usado.
A framework struts possibilita a implementação dos dois tipos de validação. Uma extensão da struts chamada Validator possibilita uma configuração de validação baseada em arquivos XML. Em um arquivo está descrito uma série de validações comuns e as classes designadas para tratá-las e em outro mapeia-se os campos de formulários HTML com as validações que estes devem sofrer.
A construção de aplicações J2EE profissionais requer a correta compreensão e utilização dos recursos disponíveis na plataforma. Para projetar soluções coerentes, flexíveis, manuteníveis, com baixo grau de acoplamento e alta coesão e reaproveitamento de código, é necessário, antes de mais nada, estudar casos de sucesso no uso da tecnologia, casos onde todos os requisitos acima foram alcançados. Visando facilitar este processo, a SUN compilou alguns padrões de projeto que capturam soluções comprovadamente eficazes para problemas geralmente encontrados no projeto de sistemas com a plataforma J2EE. Conhecendo e empregando tais padrões, teremos mais chances de construir aplicações corretas, isto é, aplicações que serão construídas num tempo hábil, atenderão aos requisitos estipulados e serão gerenciáveis no decorrer do seu ciclo de vida.
Neste artigo, procuramos apresentar de forma sucinta alguns dos principais padrões e estratégias evidenciadas pela SUN. Esperamos que com este conhecimento básico o leitor possa ter tido uma idéia de fatores importantes envolvidos no planejamento de projetos com a tecnologia J2EE, evoluindo assim sua capacidade de projetar soluções com esta tecnologia.
[ALUR] Alur, D.; Crupi, J.; Malks, D. Core J2EE Patterns First Edition, Prentice Hall / Sun Microsystems Press, 2002.
[SINGH02] Singh, I.; Stearns, M. J., Enterprise Team. Designing Enterprise Applications with the J2EE Platform, Second Edition, Addison Wesley, 2002.
[J2EETIP] Rick, C.; Inscore, J.; Enterprise Partners. J2EE Technology in Practice: Building Business Applications with the Java 2 Platform, Enterprise Edition, Sun Microsystems Press, 2001.