Canguru - Persistência de Objetos em Java

Autor: Maíra de Assis Ramos

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/)

Índice

Apresentação do problema

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 existentes 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.

Objetivos

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:

Solução

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.

Utilizando o Canguru

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.

imagem1.png
Figura 1

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).

Arquitetura

O Arcabouço Canguru é dividido em três módulos (fig. 2):

imagem2.png
Figura 2

Para obter informações detalhadas sobre esse módulo, consulte a API do arcabouço Canguru.

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.

Descriptor

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:

imagem3.png
Figura 3

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.

Proxy Database

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:

Ferramentas, técnicas e padrões utilizados

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.

Restrições

Organização da equipe e metodologia de desenvolvimento

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.

Prazos e andamento

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.

Considerações finais

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:

Desafios e frustrações encontrados

Esse foi um projeto cujo resultado final me agradou muito, pois atingiu quase todos os objetivos propostos no início e ainda pode ser expandido. É gratificante peceber que um trabalho desse porte se encaminhou bem e tem um futuro promissor.

Entretanto, no decorrer do projeto, encontramos uma série de dificuldades e cheguei inclusive a pensar em desistir dele. O nosso cronograma inicial se atrasou muito, porque postergamos decisões e cortes no escopo do projeto e acabamos encontrado problemas não imaginados para resolver aquilo que havíamos proposto.

Os principais desafios encontrados foram:

Tempo: Como todos os integrantes do grupo estavam trabalhando, não tínhamos o tempo desejado para dedicar ao projeto e, para manter o cronograma, uma série de estudos e análises não puderam ser feitos da forma adequada. Considero esse um fator que colaborou para o surgimento de outras dificuldades encontradas. No final, esse tempo teve que ser gasto de qualquer forma, mas concentrado nos últimos meses do projeto e tivemos que distribuir nossas disponibilidades entre matérias em curso, trabalho e projeto de formatura.

Introspecção: Gastamos bastante tempo para chegar a versão final do algoritmo para realizar a introspecção. Fizemos vários protótipos definindo nossos próprios critérios para persistir ou não atributos antes de o Gustavo descobrir a interface de Java Beans, que é um padrão conhecido e adotado.

Recuperação de Referências: Na minha opnião, esse foi um dos maiores problemas, que inclusive não foi totalmente solucionado. Salvamos no banco a forma serializada dos objetos persistidos e, na recuperação, tivemos que encontrar uma forma de reapontar as referências, pois isso não é feito automaticamente quando um objeto é deserializado. A nossa solução descrita em Canguru resolve o problema para aquelas referências que recebem Id (1o nível), mas não resolvemos os outros casos. O grande problema é que teríamos que varrer o grafo completo de objetos, dando um id para cada, e isso seria tremendamente custoso.

Conversão OO-Banco: Tivemos que definir uma arquitetura que possibilitasse com facilidade a inclusão de novos tipos de atributos e que permitisse uma troca fácil de forma de persistência para outros bancos de dados ou mesmo arquivos texto, no formato XML, por exemplo.

Busca: Essa funcionalidade, em particular, levou a muita discussão, pois foi uma das que menos definimos no início do projeto. Tivemos que decidir como os filtros seriam fornecidos, como o resultado de uma busca seria devolvido pela aplicação e de que classes eram as reponsabilidades. Surgiram diversos pontos de vista e chegamos a solução atual. Em termos de arquitetura, considero essa a parte mais vulnerável do projeto, talvez tenhamos que rever isso um pouco antes de adicionar as novas funcionalidades planejadas.

Disciplinas do BCC e o projeto

Com algumas ressalvas, considero o curso do BCC muito bom. Percebi, não só nesse projeto, mas durante toda a minha vida profissional, como uma formação acadêmica forte faz diferença. Todas as matérias, inclusive as menos populares, tiveram um papel importante. Até aquelas em que os professores se ausentaram ao máximo são importantes, ao menos para perceber como não deve ser uma aula e dar mais valor aos bons professores.

Deixo aqui apenas duas críticas. Considero o conhecimento do paradigma OO fundamental e, portanto, acho que a disciplina MAC0441 Programação Orientada a Objetos deveria ser obrigatória. Além disso, acho que as disciplinas ligadas a Engenharia de Software e processos de desenvolvimento deveriam ter mais espaço dentro da grade curricular.

Vou citar as disciplinas que considerei mais importantes para a elaboração do projeto:

O trabalho em equipe

Encontramos alguns problemas para fazer reuniões com a equipe, principalmente por falta de tempo, consumido pelo trabalho, no meu caso até em finais de semana. No início, esse fator atrapalhou um pouco, pois havíam muitas decisões a serem tomadas e não achamos um meio eficiente de fazer a comunicação. Acabamos não conseguindo fazer uma divisão adequada das tarefas.

A partir do momento em que definimos melhor a arquiterua do projeto, a divisão de tarefas aconteceu de forma natural e conseguimos realizar as atividades de forma relativamente independente, sem que precisássemos estar no mesmo ambiente. Conseguimos nos comunicar por telefone e lista de e-mail de forma que todos acompanharam e ajudaram na solução de cada problema. Considero um sucesso a nossa organização e comunicação para esse desenvolvimento. Nossas discussões sobre que arquitetura, algoritmo, critério, etc adotar em cada solução foram uma das partes mais importantes e construitivas desse projeto, ao meu ver. Através da troca de informações pudemos aprender muito uns com os outros.

Destaco aqui a participação do Gustavo no grupo. Como já trabalhamos juntos e tínhamos as mesmas expectativas um em relação ao trabalho do outro, o relacionamento foi muito facilitado. Às vezes, em discussões técnicas, entra um pouco da vaidade indivídual ao defender o próprio ponto de vista, disvirtuando a finalidade dessa troca de informações, que é resolver o problema da melhor forma possível. Acredito que, graças ao nosso bom relacionamento, pudemos sempre focar as discussões nos objetivos a que se propunham e aprendemos o máximo possível com elas. Além disso, ele foi o maior responsável para que esse projeto desse certo, graças a sua pró-atividade e capacidade de resolver os problemas encontrados. Nos momentos em que estávamos desanimados, achando que o projeto não acontecer, ele ofercia idéias novas e mantinha o grupo motivado.

Em relação ao nosso orientador, o Prof. Francisco Reverbel, procuramos escolher um dos professores que tivessem mais experiência e conhecimento nessa área. Como havíamos cursado SOD com ele e sabíamos de sua participação em projetos como o JBoss, solicitamos que ele desempenhasse esse papel.

Conclusão

Considero esse trabalho uma repetição do que foi o curso inteiro para mim. No decorrer do curso, a presença e apoio dos amigos foi fundamental para uma formação e desempenho melhores. As horas de estudo em grupo e discussões sobre os EPs foram muito proveitosas, trazendo um crescimento que não seria obtido de outra forma. Da mesma forma, trabalhar em grupo com o Gustavo e com a Fernanda foi muito gratificante.

Sempre tive que dividir meu tempo entre as atividades da graduação e o trabalho, o que resultou, às vezes, em perda na qualidade dos meus estudos. Não consegui realizar todas as tarefas da maneira mais adequada e tive que correr muito atrás do prejuízo nas vésperas de entrega de projeto e de provas, para obter um resultado satisfatório. Nesse ponto, concordo com o Prof. Paulo Feofiloff, o curso do IME exige uma dedicação que não pode ser atingida quando se trabalha mais que 30 horas semanais. Durante o projeto, também tive que distribuir meu tempo da melhor maneira possível, utilizando noites e finais de semana disponíveis para que o projeto fosse feito.

Mas no final, gostei do resultado. Obviamente uma série de pontos poderiam ser feitos de forma melhor, tanto no decorrer do curso, quanto do projeto. Mas, definitivamente, aprendi muito com os dois e fico feliz por tê-los feito.

Referências

[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/