Equipe:
Felipe Gustavo de Almeida
Fernanda Simões de Almeida
Maíra de Assis Ramos
Orientador: Prof. Francisco Reverbel (http://www.ime.usp.br/~reverbel/)
Uma das principais dificuldades encontradas pelos desenvolvedores de aplicações orientadas a objeto é a persistência dos dados. Em muitos casos opta-se por utilizar bancos relacionais e, com isso, surge o problema de mapear os objetos para o banco.
As soluções geralmente adotadas envolvem descrever uma relação entre as propriedades do objeto e os campos de uma ou mais tabelas no banco relacional. Essa relação pode ser descrita através de queries SQL, em que a aplicação é responsável por persistir e recuperar os objetos direto no banco, ou através de ferramentas mais sofisticadas como o Prevayler, Hibernate, ou mesmo Enterprise Java Beans.
Quando o desenvolvedor opta por fazer a persistência "manualmente" usando queries, perde-se muito tempo desenvolvendo código para recuperar e salvar os dados em banco e a aplicação fica muito vulnerável a pequenos erros de programação típicos de trabalhos repetitivos e copy-and-paste.
A maioria das ferramentas existente para resolver esse problema requer que o usuário descreva o mapeamento OO-Relacional, por exemplo em XML, implemente interfaces e obedeça a uma série de restrições. Para projetos grandes, algumas delas são muito boas porém, para aplicações menores acabam introduzindo muita complexidade.
O objetivo desse projeto é construir um arcabouço de simples utilização que facilite o trabalho do desenvolvedor, tornando o processo de persistir dados o mais transparente possível e sem onerar excessivamente os recursos da máquina.
O público alvo do Canguru é o desenvolvedor de aplicações que não necessita ou não queira toda a complexidade de um sistema como, por exemplo, o Hibernate.
Para tal, o Canguru deve apresentar as seguintes características:
O Canguru é um arcabouço em Java para persistência e recuperação de dados, que se baseia em uma forma transparente de fazer a conversão entre Objetos de aplicações Java e registros salvos no banco de dados, oferecendo uma interface simples derivada de java.util.Set com métodos adicionais para salvar, recuperar e fazer busca por objetos.
Para utilizar o Canguru, o desenvolvedor tem apenas dois contatos com o banco de dados: a criação da base (somente da base) e do arquivo de configuração do Canguru como abaixo.
database.prp:
#Configurações para conexão com o banco de dados driver=org.postgresql.Driver url=jdbc:postgresql://127.0.0.1:5432/databasename user=usuario password=senha
Nesse arquivo são informados os dados para a conexão com o banco de dados:
Com a conexão para o banco configurada, a aplicação cliente deve instanciar um objeto da classe Canguru, informando qual o tipo (classe ou interface) dos objetos que serão armazenados. Pode-se utilizar diversos cangurus para armazenar objetos de tipos diferentes. A partir disso, as funcionalidades do arcabouço são acessadas através de três interfaces (fig. 1) disponibilizadas na classe Canguru:
Dessa forma, a aplicação consegue inserir elementos em uma Collection (Canguru), solicitar que sejam salvos em banco quando necessário e recuperá-los total ou parcialmente, através de filtros.
Para detalhar a utilização do Canguru, vamos usar o exemplo abaixo:
CanguruExample.java:
1: import java.sql.SQLException; 2: import java.util.Iterator; 3: import java.util.Set; 4: import canguru.Canguru; 5: import canguru.MixurucaEnhanced; 6: import canguru.descriptor.exception.AttributeDefinitionNotFoundException; 7: import canguru.descriptor.exception.InvalidAttributeException; 8: import canguru.descriptor.exception.InvalidNameException; 9: import canguru.exception.BeanManipulationException; 10: import canguru.exception.CanguruInitializationException; 11: import canguru.exception.FilterException; 13: /** 14: * Pequeno exemplo de uso do Canguru. 15: */ 16: public class CanguruExample { 18: CanguruExample() { 19: } 21: public void run() { 23: try { 24: // inicializa o canguru, os parâmetros são: 25: // um String que servirá como identificador para essa coleção 26: // um Class que indica qual a classe dos objetos que o canguru deverá aceitar 27: Canguru canguru = new Canguru("exemplo", MixurucaEnhanced.class); 29: // cria alguns objetos para serem adicionados (da mesma classe que foi passada 30: // para o construtor do Canguru) 31: MixurucaEnhanced mixuruca1; 32: MixurucaEnhanced mixuruca2; 33: MixurucaEnhanced mixuruca3; 35: //atribui valores a algumas das propriedades dos objetos criados 36: mixuruca1 = new MixurucaEnhanced(); 37: mixuruca1.setStringTest("teste"); 39: mixuruca2 = new MixurucaEnhanced(); 40: mixuruca2.setStringTest("teste"); 41: mixuruca2.setAInteger(new Integer(42)); 43: mixuruca3 = new MixurucaEnhanced(); 44: mixuruca3.setAInteger(new Integer(42)); 46: // adiciona os objetos ao Canguru 47: canguru.add(mixuruca1); 48: canguru.add(mixuruca2); 49: canguru.add(mixuruca3); 50: canguru.add(new MixurucaEnhanced()); 52: try { 53: //salva os dados 54: canguru.save(); 55: } 56: catch (ClassNotFoundException e1) { 57: e1.printStackTrace(); 58: } 59: catch (SQLException e1) { 60: e1.printStackTrace(); 61: } 62: catch (java.io.IOException e1) { 63: e1.printStackTrace(); 64: } 65: catch (canguru.exception.IOException e1) { 66: e1.printStackTrace(); 67: } 68: } 69: catch (CanguruInitializationException e) { 70: //a inicialização do canguru falhou 71: e.printStackTrace(); 72: } 74: //recuperando os dados 75: try { 76: //inicializa um novo canguru utilizando o mesmo string como identificador e o mesmo class, 77: // assim poderemos recuperar os dados salvos pela outra instância do Canguru. 78: Canguru canguru = new Canguru("exemplo", MixurucaEnhanced.class); 80: try { 81: //recupera todos os objetos objetos salvos em banco 82: canguru.restore(); 84: //percorre os elementos 85: Iterator it = canguru.iterator(); 86: while (it.hasNext()) { 87: System.out.println(it.next()); 88: } 90: try { 91: // busca elementos especificos 92: canguru.addFilter("stringTest", "teste"); 93: Set filtered = canguru.getFilteredSubset(); 94: //nesse ponto o filtered possui referencia para os dois objetos que possuem 95: //a propriedade stringTest == teste 97: //restringindo mais a busca 98: canguru.addFilter("AInteger", new Integer(42)); 99: filtered = canguru.getFilteredSubset(); 100: //agora o filtered soh possui referencia para um objeto que possui stringTest == test 101: //e AInteger == 42 103: //limpa os filtros 104: canguru.resetFilter(); 105: //adiciona outro filtro 106: canguru.addFilter("AInteger", new Integer(42)); 107: filtered = canguru.getFilteredSubset(); 108: //agora o filtered possui referencia para dois objetos com AInteger == 42 110: } 111: catch (AttributeDefinitionNotFoundException e2) { 112: e2.printStackTrace(); 113: } 114: catch (InvalidNameException e2) { 115: e2.printStackTrace(); 116: } 117: catch (InvalidAttributeException e2) { 118: e2.printStackTrace(); 119: } 120: catch (FilterException e2) { 121: e2.printStackTrace(); 122: } 124: } 125: catch (java.io.IOException e1) { 126: e1.printStackTrace(); 127: } 128: catch (ClassNotFoundException e1) { 129: e1.printStackTrace(); 130: } 131: catch (SQLException e1) { 132: e1.printStackTrace(); 133: } 134: catch (BeanManipulationException e1) { 135: e1.printStackTrace(); 136: } 137: } 138: catch (CanguruInitializationException e) { 139: e.printStackTrace(); 140: } 141: } 143: public static void main(String[] args) { 144: new CanguruExample().run(); 145: } 146: }
Na linha 27 inicializamos uma instância do Canguru passando como parâmetros um string
exemplo
e uma classe MixurucaEnhanced.class
. O string tem a função de identificador para a coleção, a classe
indica qual o tipo dos objetos que essa instância deverá aceitar. Cada instância pode trabalhar apenas com um tipo de objeto.
Entre as linhas 29 e 44 criamos algumas instâncias de objetos da classe
MixurucaEnhanced
e atribuímos valores a algumas de suas propriedades. A seguir adicionamos esses objetos ao Canguru
(linha 47).
Para salvar todos os objetos em banco usamos canguru.save()
na linha 54 e ao executar esse
método o Canguru irá criar automaticamente as tabelas necessárias.
A próxima etapa é recuperar os dados, para fazê-lo vamos utilizar uma nova instância do Canguru, que deve ser inicializada (linha 78) com os mesmos parâmetros da instância que usamos para salvar. Repare que não estamos usando a instância anterior apenas para ilustrar. É perfeitamente possível carregar os dados do banco na mesma instância que foi usada para salvá-los.
Após inicializado o Canguru, para recuperar os dados, basta chamar o método canguru.restore()
(linha 82), para percorrer os objetos recuperados usamos os métodos disponíveis em java.util.Set, conforme ilustrado na linha 85. Adicionalmente dispomos de um sistema de filtros que permite buscar objetos pelos valores de suas
propriedades (linhas 92 a 107).
O Arcabouço Canguru é dividido em três módulos (fig. 2):
Para obter informações detalhadas sobre esse módulo, consulte a API do arcabouço Canguru.
O Canguru possui duas classes mais importantes: Canguru
e CanguruSaver
, a primeira é a interface
que o desenvolvedor usa, a segunda é a classe que trata os objetos para salvá-los e recuperá-los.
Quando o método save()
é chamado o Canguru passa a
Collection
com os objetos para o CanguruSaver
, que por sua vez os percorre como um grafo, utilizando o java.beans.Intrsopector
para fazer uma
espécie de numeração topológica, atribuindo ids e detectando ciclos.
Para atribuir ids utilizamos o PMap
que é um Map
java que usa "==
" no
lugar de .equals()
para fazer as comparações. Algum tempo depois de termos feito essa implementação encontramos o artigo
Long-Term Persistence for JavaBeans de MILNE,
Philip e WALRATH, Kathy que sugere uma implementação semelhante.
Uma vez percorridos e devidamente identificados (por ids) os objetos são analisados e os valores e ids de
suas propriedades são salvos dentro de um Descriptor
juntamente com
sua forma serializada.
Para recuperar os objetos o CanguruSaver
os deserializa do banco, guardando cada uma de suas propriedades e o
próprio objeto em um PMap
(na realidade é usado um ObjectTable
, que facilita o uso do PMap
), assim o Canguru pode identificar propriedades que referenciem
uma mesma instância, e "corrigir os ponteiro".
Os filtros são pares (atributo, valor) enviados para o Descriptor
que os usa como critério de seleção na
busca no banco.
É importante ressaltar que o tratamento de referências só é feito no objeto que é passado diretamente como parâmetro para
o método add(Object o)
do Canguru, isso irá causar problemas ao salvar grafos mais complexos de objetos.
O Módulo Descriptor é responsável por receber as solicitações de persistência, recuperação e busca do Canguru e repassá-las para o Proxy Database de forma adequada. A classe Descriptor serve como fachada para todo o pacote e é a única classe que pode ser acessada pelos outros módulos.
Para o entendimento desse módulo são necessárias três definições:
Quando um Canguru é criado, ele instancía o descriptor
que utilizará, e o configura para realizar suas operações de persistência, recuperação e busca.
São definidos:
A partir do momento em que o Descriptor está configurado, são oferecidos três conjuntos de operações ao Canguru. O Canguru pode adicionar os elementos a serem persistidos e solicitar que sejam salvos no banco de dados. Pode solicitar que o Descriptor carregue os elementos a partir do banco do dados para depois carregá-los para a aplicação. Ou ainda, pode inserir filtros de busca, fazer a pesquisa e recuperar os elementos retornados. Todas essas operações são, por sua vez, delegadas ao Proxy Database associado ao Descriptor.
Na camada de proxy para o banco de dados são realizadas todas as operações de banco delegadas pelo descriptor. Para realizar essas operações, o proxy utiliza as configurações definidas no descriptor associado. Além da conexão com o banco de dados, as seguintes operações são realizadas:
Como decidimos trabalhar separadamente, cada um em sua casa, precisamos de ferramentas tanto para integração do código em si, como para integração da equipe, permitindo discussões sobre as soluções encontradas, definição de próximos passos e divisão de tarefas. Para o controle de versões de código utilizamos o CVS, que permite manter o código sincronizando e íntegro, mesmo com alterações concorrentes de vários programadores. Para solucionar os problemas de comunicação, criamos uma lista de discussão, um WiKi e utilizamos massivamente o telefone.
Para programar, utilizamos o Eclipse, que oferece uma série de recursos interessantes, entre eles padronização de código, geração automática do esqueleto para Javadoc e os métodos mais comuns de refatoração de código. Utilizamos também o JUnit integrado ao Eclipse para realizar os testes de Unidade da aplicação.
O arcabouço Canguru foi desenvolvido utilizando o J2SE 1.4.2 e foi testado para o SGBD Postgres 7.3
No desenvolvimento utilizamos, ainda, alguns padrões de projeto. Para instanciar a subclasse adequada do Tipo de Atributo utilizamos o padrão Factory e procuramos utilizar Façades entre os subsistemas da aplicação.
AND
, não sendo possível fazer buscas por
objetos que possuam a propriedade xxxx="valor1" ou xxxx="valor2".A equipe é composta por Felipe Gustavo de Almeida, Fernanda Simões de Almeida e Maíra de Assis Ramos.
Os estudos iniciais sobre o projeto, como introspecção, ferramentas relacionadas ao assunto, ferramentas que poderiam auxiliar o projeto, foram feitos individualmente e, posteriormente, discutidos com toda a equipe.
Já toda a fase de modelagem foi marcada por reuniões da equipe toda para discutir e definir as diretrizes a serem tomadas. Assim que conseguimos uma modelagem mais precisa do projeto, pudemos fazer uma divisão inicial das tarefas entre os componentes do grupo, sendo cada integrante responsável por determinadas atividades que poderiam ser realizadas separadamente. Entretanto, toda solução encontrada foi discutida na busca de aprimoramentos ou novas idéias, assim como a própria divisão de tarefas e objetivos foram rediscutidos e adaptados ao logo do desenvolvimento.
Para que a equipe pudesse trabalhar separadamente, foi essencial o uso de um método de controle de versões (CVS), assim como a criação de uma lista de discussão, que serviu como canal de comunicação entre os membros da equipe para avisar como estava o andamento do projeto e discutir sobre dúvidas que surgiram durante a implementação. Outro elemento de grande importância na nossa comunicação foi o telefone, permitindo que grande parte das decisões fosse feita "on-line", visualizando o código e discutindo.
A divisão inicial do trabalho previa o Gustavo trabalhando no módulo Canguru, a Maíra desenvolvendo a camada de persitência no banco de dados, que incluía tanto o Descriptor, quanto a camada de Proxy para o banco de dados e a Fernanda desenvolvendo a aplicação exemplo para o Canguru.
No andamento do projeto, encontramos dificuldades em desenvolver a aplicação cliente em paralelo com o desenvolvimento do Canguru em decorrência de alterações na interface do Canguru e do volume de desenvolvimento em outros módulos. Decidimos priorizar o desenvolvimento do arcabouço com testes automatizados utilizando o JUnit. A estrutura inicial para os testes, bem como toda a estrutura de comunicação (Wiki + Lista) e controle de fontes (CVS) foi desenvolvida pelo Gustavo.
Com a arquitetura mais madura, surgiu a definição dos três módulos existentes. O módulo Canguru, que inclui implementação da interface Set, introspecção, serialização e recuperação de referências, ficou a cargo do Gustavo. Já o módulo Proxy Database, que inclui a parte de comunicação com o banco de dados, a criação de tabelas e geração de comandos SQL para persistir, recuperar e buscar os objetos ficaram por conta da Maíra, com grande participação do Gustavo. Por sua vez, o módulo Descriptor, responsável pela conversão Banco-OO e pela definição das buscas, foi desenvolvido pela Maíra e pela Fernanda.
Na fase de pré-entrega do projeto, todos participaram executando e melhorando os testes de forma que todo o arcabouço fosse examinado. Assim que alguma falha era percebida, ela era corrigida independentemente que quem havia desenvolvido o código primeiramente. Aproveitamos para utilizar algumas técnicas de refatoração oferecidas pelo Eclipse para prover melhorias no código. O CVS e, principalmente, a comunicação por e-mails e telefone foram cruciais para a sincronização das alterações.
O cronograma original de desenvolvimento era:
Julho: Estudo mais detalhado e modelagem do sistema.
Agosto: Introspecção, grafo de objetos, geração do banco, gravação de objetos.
Setembro: Busca, recuperação, proxy/memento e API.
Outubro: Ajustes, testes e aplicação exemplo.
Novembro: Apresentação e monografia.
Apesar dos esforços esse planejamento não foi respeitado. No início tínhamos feito uma modelagem vaga demais, sobravam muitas decisões para a etapa de implementação. Também gastamos muito tempo com protótipos do sistema de instrospecção.
Em setembro, utilizando o conhecimento que havíamos adquirido com a fase inicial do desenvolvimento, remodelamos o sistema de uma forma mais precisa e modularizada. Os benefícios foram imediatos: a divisão de tarefa, que antes era algo difícil, se tornou um processo natural, e as dúvidas, antes muito comuns durante o desenvolvimento, passaram a ser mais raras de de fácil resolução.
O desenvolvimento passou a ter um ritmo muito bom, porém já não tínhamos muito tempo e itens como a utilização de proxies e a aplicação exemplo, que faziam parte da proposta inicial, tiveram que ser deixados de lado.
O resultado do projeto, o Canguru, agradou a toda a equipe, pois é uma ferramenta útil e de simples uso, que não exige configurações complicadas ou implementação de interfaces por parte do usuário, como era nosso objetivo inicial.
A intenção do grupo é continuar o seu desenvolvimento, aprimorando aquilo que já possuímos e incorporar novos recursos ao Canguru, tendo sempre em mente sua principal característica, que é a simplicidade. Alguns pontos que seriam interessantes no Canguru e que merecem ser estudados são:
Muito embora durante o curso eu tenha feito um estágio reconhecido pelo IME, eu optei por não escrever uma monografia baseada neste estágio e sim fazer um projeto para que eu pudesse ter a oportunidade de enfrentar novos desafios e concluir o curso com uma bagagem de conhecimento maior.
De fato, no projeto, tive a oportunidade de trabalhar com instropecção dos objetos, que era algo novo para mim, com a ferramenta JUnit para a geração dos testes, que não era utilizada na empresa na qual havia trabalhado, por exemplo.
No entanto, a proposta do projeto, de fazer um arcabouço voltado para desenvolvedores de modo a facilitar a tarefa de persistir os dados, foi o que me atraiu a fazer este projeto, até porque eu mesma já tinha vivenciado essa dificuldade desenvolvendo aplicativos orientados a objeto e esse foi o maior desafio do projeto: fazer algo simples, de aplicação prática e que tornasse a persistência de objetos o mais transparente possível ao usuário.
No desenvolvimento deste projeto, foram importantes as disciplinas:
O fato de alguns dos membros da equipe já terem trabalhado juntos fez com que o entrosamento entre os mesmos fosse facilitado, proporcionando uma maior agilidade no desenvolvimento do projeto.
Por outro lado, tivemos de enfrentar o problema de conseguir conciliar os horários entre os membros da equipe, uma vez que todos estavam trabalhando e cada qual em um horário diferente. Neste ponto, o uso de comunicação eletrônica (primeiro e-mails individuais, depois uma lista de discussão da equipe, e icq), assim como telefonemas e controle de versão (CVS) foram imprescindíveis para a organização e divisão das tarefas entre os membros da equipe.
Fazer este projeto foi válido uma vez que ele proporcionou que eu tivesse contato com novos conceitos e pudesse aumentar os conhecimentos que adquiri na faculdade e nas empresas nas quais trabalhei.
Mesmo não tendo feito o trabalho de formatura sobre o estágio que realizei, gostaria de ressaltar neste momento que considero importante que os alunos entrem no mercado de trabalho enquanto ainda fazem o curso. Afinal, o trabalho permite que se adquira agilidade, responsabilidade, aprendendo a lidar com prazos, além de, ao colocar o aluno em contato com pessoas com diferentes formações e graus de experiência, proporciona uma troca de conhecimentos extremamente interessante.
[1] API Canguru (javadoc), api/index.html
[2] API Java 1.4.2 (javadoc), http://java.sun.com/j2se/1.4.2/docs/api/
[3] Documentação do PostgreSQL 7.3, http://www.postgres.org/docs/7.3/static/index.html
[4] Java Beans, http://java.sun.com/products/javabeans/
[5] Especificação do Java Beans, http://java.sun.com/products/javabeans/docs/spec.html
[6] MILNE, Philip e WALRATH, Kathy. Long-Term Persistence for JavaBeans
[7] JOHNSON, Mark. Make JavaBeans mobile and interoperable with XML
[8] Enterprise Java Beans, http://java.sun.com/products/ejb/
[9] Prevayler, http://www.prevayler.org/
[10] Hibernate, http://hibernate.sourceforge.net/
[11] JBoss, http://www.jboss.org/
[12] JMangler, http://javalab.cs.uni-bonn.de/research/jmangler/
[13] Javassist, http://www.csg.is.titech.ac.jp/~chiba/javassist/
[14] OGNL, http://ognl.org/
[15] JXPath, http://jakarta.apache.org/commons/jxpath/index.html
[16] JUnit, http://www.junit.org/
[17] CVS, http://www.cvshome.org/
Dezembro de 2003