No início do ano, tomamos familiaridade
com a regra do negócio que o sistema se propõe a resolver.
Estudamos como o sistema tinha sido desenvolvido até então,
e tomamos conhecimento de quais ferramentas tinham sido utilizadas
para isso.
Primeira Refatoração
Como o sistema apresentava uma arquitetura de classes bastante
desorganizada, o primeiro passo seria, sem dúvida, refatorá-lo.
Desse modo, seria muito mais fácil implementar as funcionalidades
restantes. Essa foi a etapa mais longa do processo.
Fizemos uma classe para ser persistida (PrevalentSystem)
que era independente do tipo de objeto a ser persistido, que, por
sua vez, tinha que implementar somente uma interface que desse acesso
a sua chave primária. Era possível criar “tabelas”,
persistir objetos, fazer buscas (através de uma linguagem
definida em objetos muito próxima de SQL), e até alterar
os objetos persistidos. Essa classe apresentava uma coleção
de comandos internos, que alteravam os objetos, de modo que pudemos
remover todos os comandos do sistema. Era possível fazer
atualizações de qualquer atributo de qualquer objeto
retornado por uma expressão de busca. Isso nos ajudou a diminuir
o número de classes pela metade. Essa classe também
era responsável por fazer a sua própria inicialização,
que tínhamos removido de JefrasServlet.
À medida que novas alterações eram feitas,
testes eram escritos para verificar se funcionavam corretamente.
Havia um outro problema: toda a lógica de negócios
estava nas páginas jsp. Criamos um pacote para colocar classes
que implementavam as regras de negócio. Após remover
as regras de negócio nas páginas jsp, já tínhamos
uma primeira versão do sistema funcionando.
Transição para J2EE
Depois de ser feita a primeira grande refatoração
do sistema, começamos a implementar os story cards que faltavam.
Durante isso, mais refatorações foram feitas ao sistema.
No decorrer do processo, começamos a ter problema com
o Prevayler. A primeira coisa que notamos era o problema
com relacionamento entre classes. Suponha que um professor P
é gravado no sistema através de um comando C1.
Como C1 contém uma referência a P,
quando C1 é gravado em um arquivo, é
gravada uma cópia de P. Depois disso, o
professor P vota no livro B. Para
isso, o sistema verifica se B não está
cadastrado no sistema. Suponha que B não
está cadastrado. Então, é executado um comando
C2, que registra o livro B no
sistema. Após isso, é necessário registrar
o voto do professor P no livro B.
Isso é feito através de um comando C3,
que persiste um voto. Esse comando tem uma referência para
o professor P e uma para o livro B.
Desse modo, o log do Prevayler ficaria dessa forma:
C1: persiste P
na tabela “professor”.
C2: persiste B na tabela
“book”.
C3: persiste o voto V, que
têm uma referência a B e uma a P,
na tabela “vote”.
Se desligarmos o sistema após essas
operações, no momento de colocarmos o sistema no ar
existirão duas cópias do professor P
e duas do livro B. Veja por quê: cada registro
de um comando seriado é uma “foto” daquele comando
e dos objetos para os quais referencia. Primeiramente, o comando
C1 é desseriado. É criada, então,
uma instância do comando C1 na memória,
que contém uma referência para um objeto professor
P’, que também foi carregada na memória
utilizando a versão seriada do professor P.
Esse comando é executado, o que adiciona o professor P’
na tabela “professor”. O mesmo é feito com C2.
É criada uma instância de C2 na memória,
baseada na “foto” tirada de C2 anteriormente,
que está registrada no log. Isso leva à criação
de um livro B’, com os mesmos atributos e
dados que tinha o livro B, antes de o sistema ser
desligado. Em seguida, é criada uma instância do comando
C3 na memória, apontando para uma instância
de um voto V’ que também é
criado, contendo os dados do voto V original. Esse,
por sua vez, tem uma referência ao professor P
e ao livro B, que também são instanciados
na memória, criando um professor P’’
e um livro B’’ na memória, com
os mesmos dados que havia no professor P e no livro
B.
Desse modo, existem duas cópias do livro B
e do professor P quando o sistema é carregado
na memória. Se você perguntar ao livro B’
se ele foi sugerido por alguém, o resultado será não,
já que é o livro B’’
que contém o registro do voto feito pelo professor. Apesar
de B’ e B’’
conterem o mesmo ISSN, o mesmo título e, assim por diante,
são instâncias diferentes na memória. É
B’ quem está registrado na tabela
“book”, e não B’’.
Resolver isso parecia fácil. Ao invés de gravar
uma referência a B e uma a P
no comando C3, poderíamos gravar as chaves
primárias desses objetos. Mas o que parecia uma solução
simples tornou-se muito difícil de ser implementado. Os comandos
da classe persistida poderiam gravar qualquer objeto, e como saber
se esse objeto referencia um objeto que já está persistido
no banco de dados, para evitar a criação de uma cópia
do mesmo no sistema? Tentamos muitas soluções, mas
vimos que estava ficando complicado e difícil, e nenhuma
solução parecia realmente boa.
Outro problema com o qual teríamos que lidar era a
mudança de classes. Em Java, se é feita a seriação
de um objeto em um arquivo, e a sua classe é alterada, não
será possível restaurar a instância que foi
gravada no arquivo. Isso significa que, se em algum momento tivéssemos
que alterar a classe Book, por exemplo, para gravar mais algum dado,
não seria possível obter os dados que estavam no banco
antes dessa alteração. O Prof. Dr. Fábio Kon
sugeriu que fizéssemos uma rotina que convertesse os objetos
seriados para a nova versão da classe sempre que fosse necessário,
mas isso parecia muito difícil. Não conseguimos encontrar
um modo de fazer isso, e o tempo estava começando a ficar
curto.
Além disso, havia momentos em que queríamos
que um conjunto de comandos fosse tratado como uma transação.
Por exemplo, não queríamos que o comando C2
acima seja efetuado se o comando C3 for cancelado.
Afinal, qual o sentido de existir um livro cadastrado que nunca
foi sugerido por ninguém? Mas, com o Prevayler,
como já foi dito, isso não é possível.
Um comando é uma transação. Teríamos
que agrupar nossos comandos em um único comando para fazer
transações, de acordo com a necessidade da lógica
de negócio que estivéssemos implementando.
Perdemos muito tempo tentando suprir as deficiências
do Prevayler, até que chegou um ponto em que se
tornava claro: não daria para continuar utilizando essa ferramenta.
Ela não se adequava à complexidade do sistema que
estávamos desenvolvendo. Fizemos a transição
do sistema para J2EE, que provê uma camada de persistência
transparente, com suporte a relacionamentos entre objetos e a transações.
Com isso, diminuímos o número de classes do sistema
consideravelmente.
Após fazermos a transição das entidades,
fizemos a transição das nossas classes eram responsáveis
pelas regras de negócio para session beans.
As principais funcionalidades que não tinham sido
implementadas foram adicionadas ao sistema e, atualmente, estão
em fase de teste. Existem 127 métodos de teste, todos executando,
mas esses testes ainda não cobrem todas as regras de negócio.
Validação de Dados
A forma como era feita a validação dos dados
digitados pelo usuário também foi substituída.
Como citamos anteriormente, existia uma exceção para
cada campo de cada entidade. Isso era ruim. Eliminamos essa exceção,
e implantamos um sistema de validação muito mais limpo,
baseado em um padrão apresentado no PLoP[3]
2002, denominado ValidationStrategy[3].
A nossa solução para validação será
detalhada na seção Arquitetura: Padrões Utilizados:
ValidationStrategy.
A camada Web
O próximo passo foi a camada web. A interface com o
usuário, feita em jsp, estava muito simples. Além
disso, faltavam telas cruciais para o sistema, como por exemplo,
uma tela para edição de listas de compras. Mas sabíamos
que páginas feitas puramente em jsp não eram a melhor
solução. Precisávamos de algo que provesse
um modo mais sistemático e modularizado de desenvolver páginas
web. Para isso, utilizamos o Struts[4],
uma ferramenta do projeto Jakarta que modulariza a interface gráfica
em M-V-C (Model - View - Controller).
Essa arquitetura de interfaces gráficas com o usuário
é bastante conhecida, e muito útil. A interface com
o usuário é dividida em três camadas:
- Model: esse é o modelo do sistema.
É a parte funcional do sistema que manipula os dados de acordo
com as regras de negócio. Como já foi dito, essa parte
do sistema foi feita em J2EE.
- View: essa é só a janela
do usuário. É somente o que ele vê. Essa parte
do sistema não possui lógica alguma. No Struts,
as Views são páginas escritas em jsp e o Form (uma
classe que armazena todos os dados digitados nos formulários
das páginas).
- Controller: o controller deve ser acoplado
à view, e é ele que pede para o modelo executar algum
processo sempre que o usuário clicar em um botão,
ou executar qualquer outra ação na interface gráfica.
É o controller que sabe a função de cada elemento
mostrado na view. Ele sabe para que serve cada botão, campo
de texto e outros elementos exibidos na view. No Struts,
o Controller é a Action.
Como o Struts utiliza essa arquitetura, é
muito mais fácil editar uma tela sem alterar a sua lógica.
A lógica da interface fica desacoplada da própria
interface com a qual o usuário tem contato. Para ganhar maior
flexibilidade, implementamos uma arquitetura reflexiva para trabalhar
com o Struts. A Classe JefrasActionRoot é
superclasse de todas as demais actions do sistema e invoca os métodos
de suas subclasses usando reflexão. Dessa forma, quando o
usuário executa alguma ação em uma página
como, por exemplo, pressionar um botão, um javascript coloca
na requisição, que está sendo enviada, o nome
do método a ser invocado para tratar a interação
com o usuário.
Como cada página está vinculada a uma Action
e a um Form, o próprio framework se encarrega de encontrar
a Action que será responsável por tratar a requisição.
Assim, a classe JefrasActionRoot obtém, a partir
da requisição, o nome do método a ser invocado
e realiza a invocação do método, usando o mecanismo
de reflexão. Essa superclasse também recebe as exceções
que forem lançadas pelo sistema, extrai suas mensagens e
as formata para exibição na tela.
Todas as páginas do sistema foram refeitas para utilizar
as tags do Struts e tirar o código Java que estava
impregnado. Os menus de navegação também foram
refeitos com o objetivo de melhorar a navegação dos
usuários. Antes, a tela inicial do sistema exibia todos os
links possíveis na barra de navegação e quando
o usuário clicava sobre um link que exigia autenticação,
a tela de login era exibida. Isso foi modificado. Hoje, a tela inicial
exibe somente os links comuns a todos os usuários e um campo
de login. Depois que um usuário se autentica, sua barra de
navegação é modificada para exibir os links
aos quais ele tem acesso.
Todas as mensagens e textos exibidos pela parte web da aplicação
utilizam um catálogo de mensagens (arquivo de propriedades,
que mapeia uma chave em um texto descritivo). O objetivo é
facilitar a internacionalização do sistema. Por exemplo,
para exibir os textos em inglês, basta copiar o arquivo de
propriedades para um arquivo com o mesmo nome seguido de _en
(isto é, JefrasWebResources_en.properties) e traduzir
os textos associados com as chaves para o inglês. O Struts
identifica o locale do usuário e seleciona o arquivo properties,
de acordo com a extensão do locale.
Conclusão
Foi dada continuidade ao projeto Jefras. Quase todos os cartões
restantes foram implementados (faltaram alguns que dependem do banco
que está sendo desenvolvido pela turma de laboratório
de banco de dados deste ano), e outras funcionalidades foram adicionadas
ao sistema.
Desse modo, podemos concluir que o sistema original foi totalmente
refatorado, e foram utilizadas outras tecnologias, que acreditamos
se adequarem melhor ao sistema. |