Desenvolvimento - Java
Design Patterns Fundamentais do J2EE
Aplicações empresariais são complexas, isto é um fato. Diariamente desenvolvedores, arquitetos, gerentes de projeto e usuários são forçados a lidar com estruturas de dados complexas, alterações em regras de negócio, mudanças de necessidades dos usuários e novas tecnologias...
por Equipe Linha de Códigowww.fundao.pro.br
Aplicações empresariais são complexas, isto é um fato. Diariamente
desenvolvedores, arquitetos, gerentes de projeto e usuários são forçados a lidar
com estruturas de dados complexas, alterações em regras de negócio, mudanças de
necessidades dos usuários e novas tecnologias. Naturalmente, os desenvolvedores
geralmente têm menos tempo e menos recursos do que precisariam (ou gostariam)
para enfrentar tudo isso.
Existem várias formas possívels de tratar tal
complexidade, mas todas podem ser agrupadas em dois princípios: utilizar
abordagens comprovadamente funcionais e antecipar necessidades futuras. Em ambos
os casos, existem técnicas comuns que transcende um sistema individual. Essas
técnicas são conhecidas como design patterns (para saber mais veja no fim do artigo).
Esses patterns
podem ser usados repetidas vezes, facilitando a incorporação da experiência de
desenvolvimentos anteriores em novos projetos. Patterns também fornecem uma
linguagem comum para a discussão de arquitetura de software em um nível mais
elevado (apenas dizer que um sistema implementa o pattern factory, por
exemplo, pode comunicar uma decisão de projeto muito mais rapidamente e mais
precisamente do que uma explicação complexa, assumindo que ambos os lados saibam
o que é o padrão factory.
Com a utilização cada vez maior da linguagem
Java e principalmente de toda a plataforma J2EE os design patterns têm se
tornado mais populares, uma vez que para construir uma aplicação de grande porte
utilizando J2EE é imprescindível a utilização de patterns adequados. Existem
muitos patterns para J2EE. Neste texto falaremos de apenas quatro deles, que têm
por objetivo aumentar a modularidade e a escalabilidade das aplicações J2EE.
Para saber mais sobre os design patterns do J2EE adquira o livro Core J2EE Patterns
Camadas da Aplicação
Aplicações empresariais modernas geralmente mantêm
no mínimo três camadas: um cliente, um servidor middleware e um banco de dados.
No mundo do J2EE, existem geralmente quatro camadas: um cliente, uma
aplicação Web construída ou com servlets Java ou com JavaServer pages (JSP), uma
camada de lógica de negócio construída em Enterprise JavaBeans (EJB) e uma
camada de persistência que acessa um banco de dados relacional.
Projetos
em multi-camadas resolvem vários problemas, mas também podem criar alguns
outros. Separar interface do usuário, lógica de negócio e dados da aplicação
torna o projeto como um todo mais fácil de ser compreendido e permite que
diferentes equipes trabalhem em paralelo em cima de diferentes componentes, mas
há um lado ruim: aumenta o overhead de comunicação entre as camadas. A
plataforma J2EE é basicamente um modelo de aplicação distribuído em
multi-camadas no qual a lógica da aplicação é dividida em componentes de acordo
com sua função. Isto permite que vários componentes da aplicação que compõem uma
aplicação J2EE sejam instalados em máquinas diferentes. Contudo, se não tomar o
devido cuidado com o J2EE ou qualquer outra arquitetura multi-camadas, você
acabará com um sistema que é muitas vezes mais lento do que um sistema de duas
camadas.
Projetos em multi-camadas possuem mais componentes para serem
monitorados e o sucesso de muitos projetos em multi-camadas se enforcam ao
tentar dividir as tarefas em componentes apropriados e ao organizar
apropriadamente os resultados. Os patterns a seguir ajudarão você a melhorar a
performance de seus sistemas em multi-camadas e torná-los mais fáceis de se
manter ao reduzir a complexidade. Como todos eles estão relacionados ao
particionamento da aplicação, esses são patterns essenciais para quase todas as
aplicações J2EE e altamente relevantes para todos os desenvolvedores. O pattern
Model-View-Controller (MVC) reforça um projeto modular e de fácil
manutenção e força a separação de camadas. O session façade organiza a
lógica de negócio para o cliente. O Data Access Object (DAO) fornece uma
interface flexível entre a lógica de negócio e as fontes de dados reais.
Finalmente, o Data Transfer Object (DTO), também conhecido como um
value object, facilita a comunicação entre as camadas.
Model-View-Controller
O design pattern MVC permite que você separe o
modelo de dados das várias formas que o dado pode ser acessado e manipulado. Um
sistema MVC é dividido em um modelo de dados, um conjunto de visões e um
conjunto de controladores. As visões fornecem a interface do usuário e, em uma
aplicação padrão, consistem de JSP. O controlador é geralmente implementado como
um ou mais servlets Java. O modelo consiste de ou EJB ou código que fornece
acesso direto ao banco de dados relacional.
MVC é útil principalmente
para aplicações grandes e distribuídas onde dados idênticos são visualizados e
manipulados de formas variadas. Como o MVC facilita a divisão de trabalho por
conjuntos de habilidades, este pattern é bastante adequado para empresas de
desenvolvimento que suportam desenvolvimento modular e concorrente com muitos
desenvolvedores. Promovendo a portabilidade de interfaces e do back-end, o
pattern MVC também torna fácil testar e manter suas aplicações. A chave para o
MVC é a separação de responsabilidades. As visões podem usar o modelo de dados
para exibir resultados, mas elas não são responsáveis por atualizar o banco de
dados. Os controladores são responsáveis por selecionar uma visão apropriada e
por fazer alterações no modelo de dados. O modelo, por sua vez, é responsável
por representar os dados base da aplicação. Algumas vezes o modelo de dados
também incluirá a lógica de negócio (as regras para manipular os dados de
negócio), e algumas vezes a lógica de negócio existirá na camada do controlador.
Na verdade existem quatro componentes em uma aplicação MVC, não três, já
que o cliente é uma parte integrante de toda a operação. O cliente envia uma
requisição para o servlet, que interage com o modelo de dados para efetuar a
ação requisitada pelo cliente. O controlador então passa informações para uma
visão, que as formata de acordo com as necessidades do cliente. Dependendo dos
tipos de clientes suportados, uma atividade pode ter muitas visões. Desacoplar
as visões do processo de requisição torna mais fácil criar novas visões para
novos formatos. Uma aplicação poderia apresentar uma interface HTML e depois
adicionar visões WML (para dispositivos wireless) e visões XML (para Web
services) futuramente.
A melhor forma de ver o MVC em ação é olhar para
algum código. Este exemplo simples inclui um servlet controlador, um modelo de
dados EJB e uma visão JSP. O cliente faz uma requisição para o servlet, pedindo
para ele criar uma nova conta de cliente. O servlet cria a nova entidade EJB
para o cliente e passa o objeto resultante para o arquivo JSP.
ClienteController.java
package exemplo;
import javax.servlet.http.*;
import javax.servlet.*;
import javax.naming.*;
import java.util.*;
import java.io.IOException;
public class ClienteController extends HttpServlet
{
protected void doPost(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException {
try
{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.evermind.server.rmi.RMIInitialContextFactory");
env.put(Context.SECURITY_PRINCIPAL, "admin");
env.put(Context.SECURITY_CREDENTIALS, "welcome");
env.put(Context.PROVIDER_URL,
"ormi://localhost:23892/current-workspace-app");
Context ctx = new InitialContext(env);
ClienteEJBHome clienteEJBHome =
(ClienteEJBHome)ctx.lookup("ClienteEJB");
ClienteEJB clienteEJB;
clienteEJB = clienteEJBHome.create();
clienteEJB.setName(req.getParameter("_NomeDoNovoCliente"));
req.setAttribute("cliente", clienteEJB);
req.getRequestDispatcher("/cliente.jsp").forward(req, res);
}
catch(Throwable ex)
{
req.setAttribute("Mensagem de Erro", "Erro do EJB: " +
ex.getMessage());
req.getRequestDispatcher("/error.jsp").forward(req, res);
}
}
}
ClienteEJB.java
package exemplo;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface ClienteEJB extends EJBObject
{
String getNome() throws RemoteException;
void setNome(String novoNome) throws RemoteException;
String getCidade() throws RemoteException;
void setCidade(String novaCidade) throws RemoteException;
String getEstado() throws RemoteException;
void setEstado(String novoEstado) throws RemoteException;
Boolean creditoAprovado() throws RemoteException;
void setCreditoAprovado(Boolean novaAprovacaoDeCredito) throws RemoteException;
}
cliente.jsp
<%@ page contentType="text/html;charset=windows-1252"%>
<HTML>
<H1>Saudações,
<% exemplo.ClienteEJB cliente =
(exemplo.ClienteEJB)request.getAttribute("cliente");
out.println(cliente.getNome()); %>
</H1>
</BODY>
</HTML>
O exemplo mostrado nos códigos
tira vantagem de muitas características do EJB e da API de Servlets do Java para
facilitar a transação entre componentes. O controlador invoca a visão usando um
despachador de requisições para encaminhar a requisição do cliente para um
arquivo JSP e ajusta os atributos da requisição para passar para frente
informações sobre o sucesso ou a falha de seu processamento. A visão então tem
acesso direto ao EJB, sem ter que carregar ele separadamente ou incluir a lógica
para povoá-lo.
Session Façade
Criar uma aplicação EJB controlável requer quebrar a
camada do EJB em duas partes. O modelo de dados é construído via entity beans e
geralmente corresponde à camada de banco de dados base. A lógica de negócio (que
em aplicações antigas poderia ter sido mantida em uma aplicação cliente ou em
stored procedures dentro do banco de dados) é mantida nos session beans. Em
muitas circunstâncias, clientes acessarão session beans que efetuarão ações
sobre entity beans e retornarão resultados apropriados para a aplicação cliente.
Geralmente faz sentido definir session beans por questões de reutilização; em
vez de construir um session bean para a transação "comprar um carro", você
poderia construir beans para carregar clientes, alterar estoque, ou requisitar
alguma personalização à fábrica, por exemplo.
É claro, é possível
adicionar lógica de negócio diretamente a um entity bean, mas isto deve ser
minimizado. Não existe uma boa razão para objetos que representem clientes
tenham algum conhecimento relativo a objetos que representam carros, mas se um
método buyCar() é integrado a um objeto cliente, então isto é exatamente o que
vai acontecer. O objeto cliente fatalmente se tornará carregado de métodos
especializados, tornando o reuso genérico mais difícil.
Tirar a lógica
de negócio dos entity beans e colocá-la na aplicação cliente também é
problemático, já que cada aplicação cliente (além de fornecer uma interface para
o usuário) deve incluir todas as regras de negócio para modificar a estrutura do
entity bean, e deve ter conhecimento de cada entity bean mesmo que tenha sido
invocado remotamente na transação. A separação das camadas cliente e de negócio
efetivamente desaparece, e qualquer alteração nas regras de negócio irá requerer
a atualização de todos os clientes.
Mesmo com a lógica de negócio
distribuída em vários session beans, ainda há o problema do overhead da rede.
Clientes EJB acessam o servidor EJB da rede, e chamadas na rede significam
atrasos. Acessar um objeto local é sempre muito mais rápido do que acessar um
objeto remoto, e cada entity bean ou session bean que um cliente usa resulta em
uma outra chamada remota.
A solução é empacotar transações de nível mais
alto em uma session façade. A session façade é um session bean que
organiza um conjunto de métodos de negócio relacionados. A session façade é
responsável por acessar recursos, atualizar dados e realizar a transação com uma
interação limitada com o cliente. O propósito da session façade é fornecer uma
interface simples e unificada para todos os clientes da aplicação. Os clientes
podem usar a session façade como um ponto de entrada único para os dados e para
a funcionalidade de uma aplicação.
Centralizando o maior número possível
de operações no servidor, a session façade pode tirar vantagem total do EJB 2.0,
incluindo interfaces locais de EJB"s. No EJB 2.0, quando um bean interage com um
outro bean no mesmo servidor, ele pode acessar aquele bean diretamente chamando
os métodos apropriados, em vez de realizar cada invocação de método através da
rede. Esta abordagem pode render ganhos de performance substanciais,
particularmente em casos onde o bean está interagindo com múltiplos session e
entity beans.
À primeira vista, é difícil de dizer o que diferencia uma
session façade de um session bean normal. Além disso, ambos são implementados
como session beans, e desta forma, da perspectiva do cliente, parecem a mesma
coisa. Uma session façade fornece uma camada de controle para a lógica de
negócio de uma aplicação. Uma session façade geralmente fornecerá suporte para
um ou mais casos de uso relacionados da aplicação, enquanto que um session bean
poderia suportar ou um caso de uso ou um componente de um caso de uso.
Diferentes aplicações clientes podem usar diferentes conjuntos de session
façades para interagir com o mesmo modelo de dados.
Isto faz da session
façade um local ideal para tratar de transações, já que grupos de atividades
podem ser facilmente declarados como parte de um único método de negócio. E ao
minimizar as interfaces apresentadas ao cliente, a camada do servidor fica mais
fácil de se manter, já que está sujeita a menos dependências externas.
Em aplicações menores, session façades podem efetivamente agir como um
substituto do front-end para os session beans e entity beans que compõem o
modelo de dados. Quando se desenvolve aplicações simples, expor todo o modelo de
EJBs diretamente para o cliente faz algum sentido; é melhor considerar o uso de
session façades se existe alguma chance da sua aplicação expandir ou se você
espera que o modelo de dados de back-end seja alterado.
Data Access Object
Pense nos recursos que uma aplicação pode precisar
acessar. Bancos relacionais são relativamente fáceis de se tratar: EJBs CMP
(Container-Managed Persistence) podem fazer a maior parte do trabalho para você,
mapeando transparentemente os campos de um EJB para tabelas em um banco de dados
base. Mas o que você deve fazer com relação a armazenamento em disco, arquivos
XML, sistemas proprietários e Web services? Ou para sistemas que utilizam um
banco relacional mas não usam EJB totalmente ou usam EJB"s BMP (Bean-Managed
Persistence)? Ou quando múltiplas aplicações precisam acessar um modelo de
dados? Ou quando os dados vêm de uma fonte hoje e de outra amanhã?
O
pattern DAO é uma solução simples para essas questões. Com esta solução,
desenvolvedores implementam um objeto que é unicamente responsável por receber
informação de um armazenamento persistente, onde quer que ele esteja. Isto
abstrai a visão do dado usada por uma aplicação do layout da tabela, esquema XML
ou arquivo em disco.
Uma equipe poderia produzir três objetos DAO por um
projeto particular: um para ler o banco de dados, um para ler dos arquivos XML
recebidos da Web e um para fornecer dados de teste para serem usados por
desenvolvedores trabalhando em outros aspectos do sistema. Se todos os três
objetos são descendentes de uma única classe abstrata Java que define os vários
métodos de acesso, os objetos podem ser substituídos na aplicação final
dependendo das necessidades atuais. Os objetos podem também ser usados em outros
projetos baseados no mesmo modelo.
Além de fornecer flexibilidade extra,
um DAO pode ser usado para aumentar a velocidade da camada de apresentação (ou
visão) em uma aplicação maior pulando uma camada EJB. Ler diretamente do banco
de dados é sempre mais rápido do que ir através da camada EJB, que tem que ler o
banco de qualquer forma. Fazer isto diretamente com a API JDBC (Java Database
Conectivity) é uma prática perigosa, já que ela liga o modelo de dados à camada
de apresentação de tal forma que qualquer alteração na implementação do modelo
de dados requer reescrever grandes partes da camada de apresentação, o que
geralmente não é muito prático. Ao se utilizar um DAO obtém-se o máximo de
velocidade com muito menos esforço de manutenção.
Data Transfer Object
Como mencionamos anteriormente, chamadas de métodos
remotos podem degradar severamente a performance de uma aplicação J2EE. Existem
duas formas de reduzir este overhead, ambos trabalham com a minimização do
número de conexões com o servidor remoto. Já mostramos a session façade, que
trabalha tentando manter o maior número possível de chamadas locais para o
servidor EJB.
A outra abordagem principal, o DTO, pode ser usado quando
dados apenas têm que ser transferidos para o cliente. DTOs trabalham coletando
conjuntos de informações relacionadas em um único objeto. Este objeto pode ser
transferido do EJB para o cliente com uma única chamada remota. Em vez de fazer
chamadas separadas para receber nome, endereço e formas de pagamento, o cliente
pode receber um objeto contendo tudo em uma única chamada. Já que cada chamada
na rede pode adicionar uma apreciável fração de segundo ao tempo que ela gasta
para executar uma atividade, reduzir este overhead é geralmente o mais efetivo
melhoramento de performance possível para uma aplicação J2EE.
Usando a
aplicação anterior, podemos adicionar o suporte a DTO adicionando um método
getCliente à definição do EJB, que retorna um objeto ClienteDTO. O ClienteDTO é
um JavaBean simples que inclui propriedades para cada campo do EJB pai ao qual
estamos interessados. O cliente pode chamar getCliente() e imediatamente receber
um pacote contendo todas as informações sobre o cliente.
Existem muitas
formas possíveis de implementar um DTO. Uma forma, como já vimos, é usar um
JavaBean para encapsular sua informação. Esta abordagem permite a checagem de
tipos em tempo de compilação e tem a vantagem adicional de ser essencialmente
auto-documentável.
Uma outra abordagem comum é usar uma implementação do
Framework de Coleções Java, geralmente um HashMap. Esta abordagem é mais
flexível do que escrever um objeto personalizado e não requer a inserção de uma
arquivo de classe de DTO no cliente. Usar um HashMap, contudo, requer definir um
conjunto de chaves antes do tempo e garante que o cliente e o EJB usam as mesmas
chaves. HashMaps também não permitem checagem de tipo em tempo de compilação, o
que pode resultar em erros estranhos e difíceis de diagnosticar futuramente.
E Agora?
Os quatro patterns cobertos aqui são apenas o começo. Além de
várias variações e especializações desses patterns básicos, você encontrará
muitos outros patterns que podem ser usados para resolver uma grande gama de
problemas. A medida que continuar a desenvolver suas aplicações J2EE, novos
patterns e variações se tornarão evidentes. Ficar de olho nas novas descobertas
é muito útil para projetos futuros.
Para saber mais: