Contribuição dos melhores programadores

Olá Pessoal,

Acompanho o fórum há algum tempo e não é difícil perceber que nossa comunidade possui muitas pessoas priviligiadas em conhecimento nas mais diversas especialidades proporcionando muitos tópicos e discussões de altíssimo nível.

Como programador Java iniciante (estudo Java apenas há ± 4 anos), ainda encontro muitas dificuldades em pôr em prática os vários conceitos e padrões de OO existentes.

Sou da turma que normalmente entende melhor um conceito vendo um exemplo em código do que lendo tutorias e discussões em fórum e acredito que muitos são assim também.

Dessa forma pensei que talvez fosse uma boa idéia se pudéssemos contribuir juntos, em um pequeno projeto simples, e sem uso de frameworks de terceiros, que colocasse em prática diversos conceitos de OO e padrões de projeto como Entity, JPA, DAO, Factory, Singleton, BO, MVC, controle de Exceções e LOG, etc. afim de que pudesse servir de base para estudo e quem sabe até de ponta-pé inicial para novos projetos.

Serveria como um exemplo simples e didático para muitas perguntas de pessoas que também tem dificuldade em colocar em prática tantos padrões existentes.

Por isso, libertei meus medos e criei um projeto que utiliza os principais padrões apenas para demonstrar um CRUD de 3 entidades: País, Estado e Cidade.
E o hospedei no Google Code:
https://code.google.com/p/aplicacao-design/source/browse/#svn%2FAplicacao%2Fsrc

Minha intenção com isso é de que haja contribuição de boas idéias que pudessem melhorar esse fonte dos mais diversos especialistas que aqui se encontram e que costumam cooperar com a comunidade e servir como um projeto de estudo.

O que a comunidade acha?

Estou totalmente aberto a críticas e opiniões,

Grato desde já!

Bem, aqui vão as minhas sugestões:

[size=18]1.[/size]
O google code já cria as 4 pastas trunk, branches, tags e wiki por um bom motivo. Você deveria pesquisar o que elas significam. De forma resumida te digo que trunk é a versão de desenvolvimento aonde são feitos commits. Branches são para o desenvolvimento de versões específicas. Tags devem ser imutáveis e representam o código-fonte de uma determinada release. Por exemplo, todo mundo desenvolve ativamente na trunk para fazer a versão 3.0. Enquanto isso há algumas pessoas trabalhando para evoluir a versão 2.4 para a 2.5, sem acrescentar todas as coisas legais da 3.0, mas acrescentando umas coisinhas pequenas e corrigindo alguns bugs. Também existe uma branch para fazer backports de bugfixes na velha versão 1.6 e então lançar uma 1.7 para quem não puder migrar para uma 2.x ou 3.x, Cada versão lançada tem uma tag específica que é imutável, logo há uma tag para a 1.0, uma para a 1.1, uma para a 1.2, uma para a 2.0 beta, etc. Se o Fulano de Tal quiser fazer umas experiências de refatorar alguma coisa complicada, para ver se dá certo ou não, sem querer atrapalhar o que está no trunk, ele pode criar uma branch específica para isso também.

Portanto, coloque a pasta Aplicacao dentro do trunk já!

A pasta wiki é aonde o google salva o conteúdo da wiki editável pelo site. Além de poder usar a própria interface do google, você pode editar a wiki ao fazer checkouts e commits nesta pasta. Além disso, o seu wiki também acaba sendo versionado. No entanto se você zoar com esta pasta, o seu wiki vai bagunçar.

[size=18]2.[/size]
Algo me diz que a interface IDAO é meio inútil, ou então talvez devesse ser bem reduzida, pois o EntityManager já faz o que ela se propõe a fazer. Além disso, ela não precisa ter o generic que tem, pois você pode acrescentar o generic em cada método, ele não precisa ser um generic da classe. Isso te dá a vantagem que cada instância específica não fica presa a uma determinada classe de entidade. Além disso, isso simplifica bastante a instanciação do DAO.

A classe AbstractDAO não é abstrata, então deveria ter outro nome. Além disso, você não está usando os generics adequadamente nela conforme especificado pela IDAO. Outra sugestão nela, é que você use o JPA 2 e troque Query por TypedQuery.

Nas JPQLs, você não precisa usar “Object(o)”, basta usar “o”. O operador Object() é inútil e existe apenas porque é legado vindo do CMP do EJB 2.

[size=18]3.[/size]
Recomendo você se livrar da classe AbstractEntity. Digo isso por experiência própria, só traz problema e não traz benefício real nenhum. Deixe cada classe definir o seu próprio Id sem ser obrigada a herdar de uma superclasse mágica. Quanto ao método toString, você pode colocar em uma classe Stringfier só para isso. Caso queira abrir mão da regra de não usar frameworks externos, o Commons-lang3 contém uma classe bem legal para isso, ToStringBuilder, e ele também contém um HashCodeBuilder e um EqualsBuilder.

Mas, caso não queira usar mesmo o commons-lang, transforme a sua implementação de toString em um método estático a parte e todos os objetos que interessarem usar ela vão ter isso:

@Override public String toString() { return Stringfier.toString(this); }

Assim, suas entidades herdam apenas de Object e implementam apenas Serializable. Acredite, isso acaba simplificando as coisas.

[size=18]4.[/size]
Fábrica de DAO que lê do Properties? Na boa, acho que você não precisa disso. Um bom e velho new na implementação concreta já deve resolver. O motivo? A ideia de você ter fábrica de DAOs é obter independenência de SGBD. No entanto, uma das ideias do JPA já é te prover independência de SGBD. Assim, uma vez que você está usando JPA, provavelmente já não vai mais precisar de fábrica de DAO, e se precisar, vai ser para algo bem diferente do que o que você fez. Se depois você quiser rodar em um ambiente de testes, e não quiser ter um banco de dados para isso, pode-se usar o hsqldb como SGBD.

[size=18]5.[/size]
Nem preciso dizer que e.printStackTrace e throws Exception são más práticas de programação. Livre-se disso o quanto antes, pois isto tende a se infestar e rapidamente subverter o propósito para o qual as exceções existem.

[size=18]6.[/size]
O seu modelo é anêmico, e isto é ruim. A ideia da orientação a objetos é você ter o comportamento dos objetos dentro do próprio objeto. Por exemplo, você faz isso:

Isso também significa que você deveria também fazer isso (aqui a coisa é mais polêmica e muitos vão discordar):empresa.salvar();E não fazer isso:dao.salvar(empresa);
Como mudar para este modelo? Pegue todos os métodos do BO e coloque na própria entidade. Se for necessário que a entidade tenha que levantar uma instância do DAO ou do EntityManager para gerenciar a sua própria persistência, que o faça (isto daqui também é polêmico). Isso é seguro porque o seu EntityManager é ThreadLocal, e o seu DAO não tem qualquer estado que faça com que uma instância seja distinta de outra (o que é algo muito bom). Dependendo de como você fizer isso, vai ver que rapidinho você não vai mais nem precisar de nenhum DAO (bastará usar o EntityManager diretamente), ou então o DAO vai se reduzir a um QueryBuilder.

[size=18]7.[/size]
Porque que a classe Database tem o EntityTransaction em um ThreadLocal? Você deveria pode pegar ele do EntityManager tranquilamente sempre que precisasse sem ter que colocar ele em um ThreadLocal. O método EntityManager.getTransaction() é inteligente o bastante para fazer o que você quer sem precisar desse malabarismo. O único ThreadLocal que você precisa mesmo é para o EntityManager.

EDIT:
[size=18]8.[/size]
Tem certeza que você não quer abrir uma única exceção para a regra de “não usar frameworks externos” e colocar o JUnit para testes? Com isso você já poderia aprender a usar o JUnit que é disparado o framework de testes para java mais amplamente utilizado. Não existe framework para testes no JDK, e embora seja possível fazer-se testes unitários e outros testes automatizados sem precisar de um framework específico para isso, fica MUITO mais fácil usando-se o JUnit.

Olá victorwss,

Segue atualização:

  1. Feito! O svn do eclipse disse que respeitaria essa hierarquia de pastas e não observei que não havia sido feito na prática.

  2. Mantive o IDAO mas retirei o generic da classe. Não sabia dessa possibilidade de fazer generic somente via método. Será que acertei agora?
    Alterei o nome para DAOImpl e altualizei seus generics.
    Não conhecia o TypedQuery!! Todo fórum ou apostila que li perguntando sobre o porque a Query ainda não usar generics ninguém respondeu pra usar TypedQuery!! Impressionante!!
    Sobre o operador Object(o) eu sabia mais esqueci de remover porque tive que tirá-lo em um aplicativo que fiz no GAE pouco tempo atrás e que tinha dado problema justamente nisso. Aproveitei o embalo e converti de JPQL pra CriteriaBuilder.

  3. Esse item queria amadurecer melhor a idéia. Porque dizes que não traz vantagem alguma? Admito que nunca testei em produção mas pensei que dessa forma o programador não cometeria erros de criar ID’s diferentes nas entidades. Poupa código e evita que o programador venha a criar chaves compostas, pois tenho pavor de ver tabelas com chaves primárias compostas pois considero isso um anti-pattern.
    Criei a class Stringfier e implementei o método estático.

  4. Também me fiz a mesma pergunta. Tirei a leitura do Properties mas não sei o que fazer se o Factory é ou não desejável nesse caso.

  5. Sei disso e concordo plenamente. Mas não sei implementar um bom pattern pra isso. Você saberia?

  6. Entendo o que você quer dizer aqui… já li muitas discussões sobre isso sem ninguém chegar a nenhum consenso. A minha preferência por BO é o fato de deixar as entidades o mais clean possível (o mais próximo possível de um POJO), embora eu compreenda que isso seja uma quebra de OO que diz que todos os comportamentos do objeto devem estar em sua classe. Acho que esse ponto também deveria ser melhor discutido.

  7. Tem das duas formas na internet e pensei que a mesma seria a mais “completa”. Enfim se não há necessidade real então deve ser retirado.

  8. Concordo e seria de grande valia para aprendizagem. Diria que o Log4J também se encaixa nesse quesito. Infelizmente não domino nenhum deles. Quando me referi a não usar frameworks, eu tinha em mente não usar frameworks tais como: Spring, Struts, vRaptor, etc. pois penso que o uso prático desses frameworks devem vir após a aprendizagem dos conceitos de OO propostos anteriormente.

Todas as alterações sugeridas não polêmicas eu implementei e já atualizei o repositório.
https://code.google.com/p/aplicacao-design/source/browse/#svn%2Ftrunk%2FAplicacao%2Fsrc

Gostaria de enfatizar que, apesar de estar diretamente usufruindo do aprendizado, eu gostaria que essa simples aplicação fosse de domínio público e que todos pudessem colaborar na implementação do mesmo independentemente de mim. Talvez o repositório mais correto deveria ficar em nome do próprio GUJ ao invés do meu.

Enfim sigo aguardando por outros comentários,

Grato

Bem, vamos lá.

Já ficou bem melhor. Nada mais a dizer quanto aos pontos 1 e 7.

Quanto ao ponto 2, primeiramente na sua DAOFactory você tem o método getDao(Class<?> daoClass). Observe que agora o parâmetro não é mais utilizado (pois agora o IDAO não é mais genérico), portanto agora você pode eliminar este parâmetro (e isso vai simplificar os seus BOs). Com isso, todas as instâncias dos seus DAOImpl serão iguais, então se você quiser poderá colocá-la em cache, transformar em singleton, ou utilizar o construtor diretamente pois ele se torna um objeto extremamente leve.

Eu nem ia falar do CriteriaBuilder por enquanto. Que bom que você já foi direto nisso. :slight_smile:

Quanto as exceções (ponto número 5), vamos ao começo. Primeiramente na classe DAOImpl. Note que lá você captura RuntimeException apenas para relançar como Exception. E se ao invés de fazer isso, você simplesmente não a capturasse e deixasse propagar?
O grande segredo do tratamento de exceções, é saber quem pode tratar a exceção de forma eficiente (usando o catch), e quem não pode (deve relançar ela, ou lançar uma outra exceção tendo a primeira como causa).
Pense bem, uma PersistenceException vinda do JPA não tem como ser tratada no próprio método. Portanto deixe ela se propagar. Só isso já diminui o seu throws Exception para throws PersistenceException. No entanto, se você for subindo as camadas, vai ver que ninguém vai saber tratar essa exceção de forma adequada, o único tratamento possível seria no controller, aonde ele dá um catch (Exception e) ou um catch (Throwable t), gera um log e manda um erro para o usuário. O motivo de não ter aonde tratar o PersistenceException é porque ele de fato não é algo tratável, não há muito o que fazer exceto cuspir um erro na cara do usuário e talvez gerar alguns logs no caminho. Como não é razoável que alguém tente tratar a PersistenceException e ela é uma RuntimeException exatamente por causa disso, então você pode simplesmente apagar a cláusula throws e deixar ela se propagar sem fazer nenhum tratamento especial local, deixando para que o tratador de erro genérico do controller se vire com ela.

Quanto ao ponto 4, nos seus BOs, aplicadas as mudanças que sugeri sobre o ponto 2, você vai ter isso:

dao = new DAOFactoryImpl().getDao();Qual é o motivo de você não fazer simplesmente isso?dao = new DAOImpl();Ou então isso?dao = DAOImpl().getInstance();A menos que você tenha um motivo forte o suficiente, você vai ver que não precisa do DAOFactory.

No entanto, caso queira manter o DAOFactory do jeito que está, sabemos que o DEFAULT_FACTORY_NAME é "JPA". Asssim, isso:

if (DAOFactory.DEFAULT_FACTORY_NAME.equals(&quot;JPA&quot;)) dao = (IDAO) this.createNewInstance(&quot;br.com.empresa.model.dao.DAOImpl&quot;);É otimizado pela JVM nisso:

if (true) dao = (IDAO) this.createNewInstance(&quot;br.com.empresa.model.dao.DAOImpl&quot;);Que por sua vez é novamente otimizado para isso:

Vamos supor que ao invés do nome da classe, o createNewInstance recebesse uma referência da própria classe (o que já eliminaria o método getClass(String)). Como a classe DAOImpl está dentro do seu JAR, então é seguro referenciá-la diretamente. Você teria isso:

Que efetivamente é a mesma coisa que isso:

E assim o seu método se tornaria isso:

public IDAO getDao() { IDAO dao = null; try { dao = new DAOImpl(); } catch (Exception e) { e.printStackTrace(); } return dao; }
Não há como uma exceção ser lançada ali, então podemos eliminar o try e o catch. Logo, temos isso:

public IDAO getDao() { return new DAOImpl(); }
E então, em todos os lugares aonde você tem isso:

Você poderia substituir por isso:

O que elimina a sua DAOFactory. Por sinal, como DAOImpl é a única implementação de IDAO, e interfaces não tem muito sentido se não forem para permitir múltiplas implementações, você poderia eliminar a IDAO também e transformar a sua DAOImpl apenas em SimpleDAO.

No entanto, caso queira manter o DAOFactory (por exeplo, você pretende fazer com que aquele if no começo possa variar), então voltando ao ponto 5, que tipo de tratamento você pode fazer caso não seja possível criar-se o DAO? Resposta: Nenhum. Qual é a razão de não ser possível criar-se o DAO? Resposta: Existe uma classe essencial faltando na aplicação. Portanto, neste caso aí, pode criar uma classe ConfigurationError, ApplicationAssemblyError ou algo parecido (observe que é Error, e não Exception). Então encapsule qualquer exceção capturada nestes pontos como causa deste seu Error, dê um throw no seu Error e esqueça. Esse é um exemplo típico de onde um Error é mais apropriado do que uma Exception simplesmente porque ele indica que há um problema fatal e irrecuperável na sua aplicação: Um pedaço dela está faltando ou no mínimo o arquivo properties dela (que você já eliminou) está faltando ou está inválido. Repetindo isso é só para o caso de você querer manter o DAOFactory, pois caso queira eliminá-lo, nada disso vai precisar existir. E caso queira eliminá-lo, é uma coisa a menos para complicar a arquitetura e uma coisa a menos para debuggar.

Bem, este artigo descreve bem o que é (na minha polêmica opinião) essa ideia de se ter DAOFactory:
http://www.marcoscintra.org/fabula_porcos.asp

Quanto ao ponto 3, o que é que o AbstractEntity te dá de bom? Pense bem antes de responder a esta pergunta, qual é o grande motivo de você ter esta classe, o que ela trás de bom? Na minha opinião, ela apenas economiza um pouco de trabalho de digitação nas subclasses. No entanto, acredito que você já deve ter visto sobre os problemas que herança te dá: A herança é um tipo bem forte de acoplamento. Trocar um pouco de trabalho de digitação por um tipo forte de acoplamento normalmente não é boa ideia. Quanto a questão da chave primária, embora seja desejável e seja boa ideia que elas sempre sejam ids simples autoincrementados, há algumas situações aonde isso não é desejado. Um exemplo de uma dessas situações é quando você tem uma tabela de relacionamento N-N entre duas outras tabelas e depois você decide acrescentar mais um atributo pertinente ao relacionamento, o que te força a transformá-la em outra tabela, no entanto você quer que a PK continue sendo as duas FKs das tabelas relacionadas.

Quanto ao ponto 8, O JUnit é uma ferramenta bem facinha de usar e requer zero de configuração. O Log4j é mais chatinho, mas recomendo usar o log4j por trás do slf4j. Dê uma pesquisada neles, inclua os que você quer no projeto, e daí voltamos aqui ao tópico.

Quanto ao ponto 6, deixo para o próximo post. :slight_smile:

Olá,

Segue atualizações:

Devido ao fato de que o JPA 2.0 não ser suportado pelo Google App Engine, eu resolvi fazer duas implementações de DAO, sendo uma para JPA 1.0 e a outra para JPA 2.0.
Assim para projetos que não utilizem o GAE utiliza-se a implementação mais recente do JPA.

Dessa forma eu acho que legitimei a existência de um DAOFactory certo?
Se a resposta é positiva então há necessidade de criar uma configuração para isso no lugar da constante DEFAULT_FACTORY_NAME.

Quanto ao tratamento de exceções eu andei relendo sobre o assunto pois difere bastante do que estou acostumado em Delphi. Por enquanto apenas removi os tratamentos nos pontos que você indicou.

Com relação ao AbstractEntity, pergunto: O que você faria em um projeto com 200 entidades se, por exemplo, por força de uma nova legislação fiscal, você tivesse que criar um atributo novo que todas entidades devem possuir?
Explico: Um exemplo recente aconteceu em uma empresa que trabalhei pois o fisco para homologar sistema de PAF, queria que o sistema gerasse um relatório de todos os registros que houveram alterações diretas no banco de dados. E os homologadores pedem para abrir o BD e gerar uma modificação em um registro aleatório de qualquer tabela e esperam que ela saia no relatorio do sistema. Para tanto foi criado um campo hash em todas as tabelas para controlar as alterações que não são executadas pelo próprio sistema.
Neste caso/exemplo, com AbstractEntity meu problema não seria mais simples de resolver, bastando criar esse campo hash apenas no AbstractEntity, que reflete automaticamente para todas entidades?

Um outro caso, e se eu quisesse implementar uma estratégia diferente de geração de código para um banco de dados que possui uma arquitetura diferente da relacional (Ex. noSQL do GAE) não seria mais fácil através dessa implementação centralizada?

Quanto ao JUnit e Log4J vou dar uma estudada e tentar implementar no feriadão.

Abs.

[quote=dipold]Olá,

Segue atualizações:

Devido ao fato de que o JPA 2.0 não ser suportado pelo Google App Engine, eu resolvi fazer duas implementações de DAO, sendo uma para JPA 1.0 e a outra para JPA 2.0.
Assim para projetos que não utilizem o GAE utiliza-se a implementação mais recente do JPA.

Dessa forma eu acho que legitimei a existência de um DAOFactory certo?
Se a resposta é positiva então há necessidade de criar uma configuração para isso no lugar da constante DEFAULT_FACTORY_NAME.

Quanto ao tratamento de exceções eu andei relendo sobre o assunto pois difere bastante do que estou acostumado em Delphi. Por enquanto apenas removi os tratamentos nos pontos que você indicou.

Com relação ao AbstractEntity, pergunto: O que você faria em um projeto com 200 entidades se, por exemplo, por força de uma nova legislação fiscal, você tivesse que criar um atributo novo que todas entidades devem possuir?
Explico: Um exemplo recente aconteceu em uma empresa que trabalhei pois o fisco para homologar sistema de PAF, queria que o sistema gerasse um relatório de todos os registros que houveram alterações diretas no banco de dados. E os homologadores pedem para abrir o BD e gerar uma modificação em um registro aleatório de qualquer tabela e esperam que ela saia no relatorio do sistema. Para tanto foi criado um campo hash em todas as tabelas para controlar as alterações que não são executadas pelo próprio sistema.
Neste caso/exemplo, com AbstractEntity meu problema não seria mais simples de resolver, bastando criar esse campo hash apenas no AbstractEntity, que reflete automaticamente para todas entidades?

Um outro caso, e se eu quisesse implementar uma estratégia diferente de geração de código para um banco de dados que possui uma arquitetura diferente da relacional (Ex. noSQL do GAE) não seria mais fácil através dessa implementação centralizada?

Quanto ao JUnit e Log4J vou dar uma estudada e tentar implementar no feriadão.

Abs.[/quote]

Herança no JPA tem várias limitações. Do jeito que está você ainda não vai ter problemas, a menos que você comece a trazer listas de AbstractEntity, a tentar fazer relacionamentos polimórficos, ou trabalhar com métodos que têm parâmetros do tipo AbstractEntity. Se você tomar cuidado em fazer isso e não forçar nenhuma entidade a herdar de AbstractEntity, você está bem.

Quanto a este problema que você falou, me soa ser um caso excepcional e incomum. E quando você tem que aplicar a mudança em todas as tabelas exceto em uma? E quando você tem uma tabela que por ter alguma característica completamente diferente ela tem uma estrutura completamente diferente das outras? Nestes casos você pode ter problema com “herança maldita”, quando uma classe herda coisas que você não queria que fossem herdadas, mas você é obrigado por algum motivo a usar herança. E este problema em especial que você falou pode ser solucionado de outra forma: com classes @Embedded.

Quanto ao DAOFactoryImpl, já que deseja mantê-lo, sugiro alterar o métodp getInstance(): public static synchronized DAOFactoryImpl getInstance() { if (me == null) { me = new DAOFactoryImpl(); } return me; }E daí você pode eliminar o loadInstance().

Eu ainda não vejo como você poderia ter mais do que uma DAOFactory.

Tomei cuidado para que nenhum método dependesse de AbstractEntity por isso utilizei como parâmetro Serializable. Assim qualquer entidade fica livre para implementar seu próprio ID caso haja necessidade para tal.

Quanto a interface do Factory removi pois de fato tb não vejo como poderia surgir necessidade de outro Factory para esse caso.

Estudei JUnit e até achei simples de implentar, mas somente para métodos de retorno simples como String, int. Não sei como implementar isso quando o retorno da maioria dos métodos são objetos complexos.

Quanto ao sistema de LOG gostei do SLF4J ajuda muito na simplificação e o implementei nas classes BO facilmente. Só achei que esse tipo de controle muito sujeito a falhas, talvez o uso de POA fosse uma boa idéia o que achas?

Enfim… acho que o código já está começando a ficar próximo do que se pode chamar de Beta… eheh