Caelum | Ensino e Inovação - Cursos de Java, Scrum, Ruby on Rails


Design Patterns: um mau sinal?

Por Paulo Silveira em 17/12/06

Uma semana atrás estava em Moçambique, próximo a África do Sul, ministrando três treinamentos da Caelum. Conheci muita gente e muitos desenvolvedores, incluindo dois escoceses: Cameron Smith, formado em ciências políticas (!) e mestre em engenharia de software pela Univerdade de Glasgow, e Colin Fairless, bacharel e mestre em Matemática Aplicada pela Imperial College de Londres. Posso dizer que aproveitei muito do enorme conhecimento desses dois.

Durante uma conversa com o Cameron, ele comentou bastante sobre a maneira de ensinar um novo desenvolvedor java. Em especial a dificuldade do HelloWorld e se os programadores são ainda iniciantes. Ele particularmente usa o beanshell no nício, para o estudante ter uma resposta rápida. Depois o assunto foi sobre como ensinar design patterns. Cameron criticou bastante jogar diagramas UML para uma pessoa e mostrar alguns casos de uso, como é feito em tantos livros. Tanto ele quanto eu achamos que isso deva ocorrer de uma maneira natural: esperar o aluno refletir sobre um caso particular. Um exemplo seria a necessidade de fazer algum cache ou controle de instância, para você deixar o construtor dessa classe privado e fornecer um método estático para fábrica-lo. Então sim você teria a permissão de explicar que milhares de pessoas passam por esse mesmo problema e que essa solução rápida foi batizada de factory. Neste início nada de UML, apenas código deve ser usado.

O amado e odiado Paul Graham diz, em a revanche dos nerds, que quando você encontra um problema e esse começa a se tornar comum, você tem três opções: (a) usar uma linguagem poderosa que lhe permita resolver aquele problema (b) escrever um interpretador para uma linguagem que resolva aquele problema (c) virar um compilador humano. Ele diz que os design patterns caem na categoria (c): é você quem precisa resolver um problema muito comum. Você é o responsável por perceber os casos de uso de determinado pattern e escrever pela milésima vez a mesma solução!

Ele ainda cita Peter Novig (um dos famosos autores do AIMA e diretor de pesquisas do Google), em sua apresentação sobre como 16 dos 23 design patterns do GoF são evitados/invisíveis usando linguagens dinâmicas.

Ambos criticam muito o uso de Design Patterns como um template: substitua aqui, ali e ali o nome de sua classe, implemente aquela interface e você já tem tudo quase pronto! Eles procuram separar isso de uma idéia que chamam de Design Strategies, fazendo uma analogia de templates versus provérbios: não é uma receita de bolo a ser aplicada, e sim mais um guia de como resolver um problema. A receita de bolo é o mau sinal! O smell.

Pensando nisso separei alguns design patterns que com o passar do tempo estão sumindo, devido a uma abordagem diferente de programação, ou até mesmo virando antipatterns.

Singleton – Este é bem fácil e já bloguei sobre os problemas do Singleton. É raríssimo querermos que uma classe possua uma única instância por modelagem. Normalmente usamos o singleton apenas para fazer lookup a uma instância: quase que uma variável global. Pode ser totalmente substituído com o uso de injeção de dependências.

Service Locator – Mesmo problema que o singleton: uma maneira de pegarmos a referência a algum objeto. O uso de injeção de dependências (como @EJB, @Resource e @PersistenceContext no Java EE 5) eliminam a necessidade de aplicar esse pattern.

ValueObject – No Java EE é comum criarmos classes que servem apenas para trafegar os valores das nossas entidades verdadeiras. Fazemos isso porque provavelmente nossas entidades são muito pesadas, remotas, ou fortemente acopladas a alguma API específica. Para passar essa informação para nossa camada View antes produzimos ValueObjects (ou Data Transfer Objects, para quem prefere). O Hibernate, EJB3 ou mesmo com os ActionForms do Struts eliminam a necessidade de você criar uma classe nova muita parecida com a sua entidade real, já que nessas tecnologias podemos usar POJOs.

DAO – Alguns dizem que não deveríamos ter um DAO: nossa própria entidade faria o acesso aos dados. É a idéia do ActiveRecord e de tantos outros frameworks (e para quem se lembra das ferramentas java de ORM do milênio passado, como Torque e OJB). Caso você tenha uma classe Produto, você teria um método estático Produto.list(), por exemplo. Isto evitaria o smell de criar uma hierarquia de classes paralelas a suas entidades. Pessoalmente hoje em dia eu não gosto da idéia, mas é uma forte vertente.

Obviamente não estou pregando a total extinção de todos esses patterns (muito menos o DAO!), apenas lembrando que devemos medir muito bem a necessidade de criar mais classes e mais abstrações, em vez de atacar o problema com mais simplicidade, como o Michael Nascimento também já blogou (duas vezes).

Uma pequena dica cultural: durante um fim de semana em Moçambique fui assistir um filme de Bollywood: Don, junto com o Colin Fairless, Imran Issufo e Edrisse Mussá, esses dois últimos cursaram o meu treinamento e fizeram a delicadeza de me convidar. Excelente filme de ação indiano, fiquei impressionado com a produção, e você também ficará!

  • Share/Bookmark

Como impressionar seus amigos com Java

Por Thadeu Russo em 05/12/06

Olá, já estou a algum tempo na Caelum mas só agora estreando no blog e, para compensar a demora, resolvi fazer um pouco diferente. O título deve estar chamando a atenção e antes de colocar o assunto de verdade (está bem, sei que estou valorizando um pouco), vou apenas lembrar da pessoa que me mostrou o assunto e da que me sugeriu colocar no blog. Ricardo e Paulo. Pronto, direto ao assunto.

Certamente muitos desenvolvedores Java já passaram pelo problema de ter de mostrar alguns recursos interessantes da plataforma. Certamente o HelloWorld não é um grande atrativo. Talvez demonstrar como é fácil fazer relacionamentos com tabelas associativas e chaves compostas através do Hibernate ajude bastante, mas ainda falta certo apelo visual.

Existe uma API feita em Java de livre uso, chamada Prefuse que utiliza-se do Java 2D para tornar simples tarefas complicadas visualmente, como por exemplo, renderizar grafos de maneira visualmente agradável e ainda por cima animada, permitindo inclusive uma interação do usuário através de drag-and-drop.

Uma aplicação mais prática e um tanto quanto útil, é a representação do relacionamento de tabelas ou mesmo de objetos, quando você precisa navegar entre uma grande quantidade de informações e quer uma maneira mais rápida em vez de pular de página em página. A idéia deste post é apenas fazer a introdução ao prefuse, sem entrar em muitos detalhes da API.

A prova de que a prefuse é algo extremamente interessante e que seus amigos ficarão impressionados está aqui. Esse link vai iniciar uma aplicação java web start com uma pequena demonstração que escrevi, mostrando visualmente o relacionamento das fronteiras dos estados do Brasil.

O código que foi necessário para gerar este exemplo segue abaixo:

/**
 @author Thadeu Russo
 
 */
public class Visualizacao {
  
  /** Display onde será desenhada a visualização */
  private Display display;
  
  /** Lista de actions relacionadas ao layout */
  private ActionList layout;
  
  /** Lista de actions relacionada as cores */
  private ActionList color;
    
  /** renderizador dos labels */
  private LabelRenderer labelRenderer;
  
  /**
   * Carrega as informações que estao no 
   * arquivo sourceFile para um novo grafo
   @param sourceFile
   @return graph
   @throws DataIOException no caso de 
   * problemas com a leitura dos dados
   * */
  private Graph loadGraph(String sourceFilethrows DataIOException{          
    return new GraphMLReader().readGraph(sourceFile);
  }
  
  
  /**
   * Este método retorna o display para a visualization informada 
   @return display
   @throws DataIOException 
   * */
  private Display getDisplay() throws DataIOException {
    Visualization visualization = this.buildVisualization(
                      this.loadGraph("data/brasil.xml")
                      );
    /* troquei a visualização? */
    if (this.display == null || !this.display.getVisualization().equals(visualization)) {
      this.display = new Display(visualization);
      this.display.setSize(720500);
      this.display.addControlListener(new DragControl());
      this.display.addControlListener(new PanControl());
      this.display.addControlListener(new ZoomControl());      
      this.display.addControlListener(new WheelZoomControl());
      this.display.pan(200350);
      this.display.setForeground(Color.GRAY);
      this.display.setBackground(Color.WHITE);
    }
    return this.display;
  }
  
  /**
   * Recupera as actions relacionadas ao layout
   @return ActionList relacionados ao layout
   * */
  private ActionList getLayout() {
    if (this.layout == null) {
      this.layout = new ActionList(Activity.INFINITY);
      this.layout.add(new ForceDirectedLayout("graph"));
      this.layout.add(new RepaintAction());
    }
    return this.layout;
  }
   
  /**
   * Recupera a configuração de cores e actions relacionadas a esta
   @return action list
   * */
  private ActionList getColor(){
    if (this.color == null) {
      DataColorAction fill = new DataColorAction("graph.nodes""region",
          Constants.NOMINAL, VisualItem.FILLCOLOR, this
              .getColorPalette());      
      ColorAction text = new ColorAction("graph.nodes",
          VisualItem.TEXTCOLOR, ColorLib.gray(0));

      ColorAction edges = new ColorAction("graph.edges",
          VisualItem.STROKECOLOR, ColorLib.gray(200));

      this.color = new ActionList();
      color.add(fill);
      color.add(text);
      color.add(edges);
    }
    return this.color;
  }
  
  /**
   * Monta a visualização a ser mostrada no display
   @param graph O grafo a ser renderizado
   @return visualization
   * */
  private Visualization buildVisualization(Graph graph){
    Visualization visualization = new Visualization();
    visualization.add("graph", graph);    
    visualization.setRendererFactory(
        new DefaultRendererFactory(this.getLabelRenderer())
        );
    visualization.putAction("color"this.getColor());
    visualization.putAction("layout"this.getLayout());
    visualization.run("color");
    visualization.run("layout");    
    
    return visualization;
  }
  
  /**
   * Recupera o renderizador de rótulos (labels)
   @return label renderer
   * */
  private LabelRenderer getLabelRenderer() {
    if (this.labelRenderer == null) {
      this.labelRenderer = new LabelRenderer("state");
      this.labelRenderer.setRoundedCorner(88);
    }
    return this.labelRenderer;
  }  

  /**
   * Recupera a paleta de cores a ser usada na renderização
   @return paleta de cores
   * */
  private int [] getColorPalette(){
    return new int[] { ColorLib.rgb(255128128),
        ColorLib.rgb(25512864), ColorLib.rgb(176176100),
        ColorLib.rgb(10025564), ColorLib.rgb(128255255) }
  }
  
  public static void main(String[] argsthrows DataIOException {
    Visualizacao visualizacao = new Visualizacao();
    
    JFrame frame = new JFrame("Grafo do Mapa do Brasil por Regiões");    
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);    
    frame.add(visualizacao.getDisplay());
    frame.pack();
    frame.setVisible(true);
    
  }
}

Aqui, a informação para gerar o grafo apresentado é lida de um XML. Poderia ser muito bem carregada a partir de um banco de dados (interessante ler o metadata do banco para gerar relacionamento entre tabelas, não?). O código acima serve de boa base para você fazer sua própria aplicação. Creio que o prefuse seja uma opção bastante interessante em vez de usar tecnologias de visualização como Flash, onde você ainda teria o trabalho de fazer toda a integração, em vez de diretamente implementar listeners java.

E vocês, que APIs consideram interessantes para demonstrar o poder e em especial a simplicidade do Java?

  • Share/Bookmark

Entidades Managed, Transient e Detached no Hibernate e JPA

Por Paulo Silveira em 23/11/06

Esse blog tem recebido uma média alta de comentários (agradecemos!), em alguns posts temos mais de dez pessoas diferentes dando sugestões. O patinho feio é meu longo post sobre as exceptions que mais acontecem quando desenvolvemos com Hibernate e JPA, talvez por alguns casos não serem tão comuns assim, e porque JPA ainda está recente.

Apesar de terem reclamado que isto está parecendo um livro de Hibernate, vou escrever aqui a introdução mais curta da história da JPA, baseado na palestra que dei sobre esse assunto no ConexãoJava e com a ajuda do Fábio Kung e do Nico Steppat para deixá-la minimal.

Um objeto é dito transiente quando não tem representação no banco de dados e nem o EntityManager o conhece. Exemplo:

Cliente c = new Cliente();

Aqui, qualquer mudança no objeto referido por c não gerará nenhum tipo de insert ou update no banco de dados.

O oposto é quando o objeto existe no banco de dados e o EntityManager em questão possui uma referência para ele, essa entidade está managed, gerenciada pelo EntityManager. Considere em uma referência a um EntityManager no seguinte exemplo:

Cliente c = new Cliente()// transiente
em.persist(c)// gerenciado

Ou ainda:

Cliente c = em.find(Cliente.class, 1)// gerenciado

Quando uma entidade está managed, qualquer mudança em seu estado (como uma chamada de setter) resultará em uma atualização no banco de dados no momento do commit.

O último caso é quando a entidade representa algo que possivelmente está no banco de dados, mas o EntityManager o desconhece: a entidade está fora do contexto, detached. Exemplo:

Cliente c = new Cliente();
c.setId(1);

Uma entidade também está detached quando o EntityManager de onde tiramos esse Cliente (por exemplo, quando fizemos um find ou vindo de uma Query) já não está mais aberta. Qualquer mudança nessa referência obviamente não surtirá efeito no banco de dados. Para que essa mudança faça efeito, isto é, para reattach o entidade, antes precisamos amarrá-la ao contexto de persistência. Repare que no EntityManager já pode existir uma entidade Cliente com esse mesmo id, imagine então o que aconteceria se tivéssemos um método que se chamasse reattach ou update?

Por isso o método é o merge. Ele junta a possível entidade com mesmo id que se encontra no EntityManager com a passada como argumento, e devolve a que está managed. O método merge não faz reattach. Então:

Cliente c = new Cliente();
c.setId(1);
em.merge(c);
c.setNome("Cliente com nome alterado");

Não surtirá efeito! Aqui você precisava antes ter pego o que o merge devolveu. Repare na pequena alteração:

Cliente c = new Cliente();
c.setId(1);
c = em.merge(c);
c.setNome("Cliente com nome alterado");

Pronto. Uma pequena introdução sobre o ciclo de vida de uma entidade em relação a um EntityManager: transient (a especificação chama de new), managed e detached! Ainda temos o estado removed, quando uma entidade está marcada para a remoção.

  • Share/Bookmark

Ajax no VRaptor: JSON da maneira fácil

Por Guilherme Silveira em 08/11/06

Vamos falar um pouco de web antes que o Paulo Silveira transforme esse blog em um livro de Hibernate!

O Fabio Kung deu a idéia de usar JSON para fazer a estrutura básica de Ajax do VRaptor. O Paulo, que detesta(va) trabalhar com JavaScript e interfaces com o usuário, gostou bastante da maneira simples de se representar objetos com JSON. Ele correu atrás de diversas bibliotecas como a JSON-tools e a JSON-lib mas achou que uma dependência apenas para isso seria muita coisa, já que não haveria a necessidade de consumir JSON como Java, apenas produzir. Ele encontrou um código na internet bem simples, mas quando foi usar descobriu uma série de testes que falhariam, então ele, juntamente com o Nico Steppat, mexeu e criou o próprio JSONWriter do VRaptor, junto com um longo test case.

JSON é uma notação simples para definir uma variável em JavaScript que pode ser facilmente traduzida para um objeto de outras linguagens. Muitos frameworks para AJAX, como o Dojo e o Prototype, consomem mensagens JSON. E agora no VRaptor, dada uma simples classe como essa:

@Component
public class ContatoController {

        private List<User> users = new ArrayList<User>();

        @Remotable
        public void lista() {
                // puxaria do banco
                users.add(new User(1,"Paulo"));
                users.add(new User(2,"Guilherme"));
        }
        
        public List<User> getUsers() {
                return users;
        }
}

Basta você anotar o método como @Remotable e acessar contato.list.ajax.logic pela URL, que o resultado será:

{"users":[{"nome":"Paulo","id":1},{"nome":"Guilherme","id":2}]}

Pronto para ser consumido por um browser! Você pode ver no site mais detalhes sobre o AJAX com o VRaptor.

O Paulo ficou empolgado com a remotabilidade, e agora está implementando o RESTful do VRaptor da mesma maneira: contatos.lista.xml.logic vai te renderizar os mesmos objetos só que em XML! Já está no CVS e estará presente uma versão básica na versão 2.2.4 (assim como integração com Spring). Isso será não só útil para expor serviços, mas também para integrar seus componentes VRaptor com consumidores de XML como o Adobe Flex e o OpenLaszlo. O próximo passo é aceitar as requisições XML e JSON, não só apenas produzi-los.

Dei hoje uma palestra sobre esse assunto no SouJava, fazendo analogias com SOA e Service Component Architecture que o Paulo tanto elogia no Apache Tuscany. Ele quer dirigir o desenvolvimento do VRaptor para esse lado: poder expor facilmente seus componentes web como serviços, facilitando a futura integração e manutenção, dois pontos que nós desenvolvedores sofremos muito atualmente com os sistemas legados.

  • Share/Bookmark

TransientObjectException, LazyInitializationException e outras famosas do Hibernate

Por Paulo Silveira em 01/11/06

Para quem desenvolve com Hibernate, sem dúvida as exceptions que aparecem mais são a TransientObjectException (TOE), LazyInitializationException (LIE) e a PersistentObjectException (POE). Semana passada tive o prazer de ministrar um treinamento de EJB3 e JSF para o pessoal da Petrobras de 5 cidades diferentes e durante o curso várias TOEs, POEs e LIEs apareceram. Vamos ver quando cada uma ocorre.

Estou usando aqui a API do Java Persistence, mas a relação é direta com o Hibernate, já que este é o meu provider. Considere duas entidades, Autor e Livro, cada uma com atributos triviais, um id que é @GeneratedValue e uma relação @ManyToMany entre elas, sendo Livro que possui o lado mappedBy. Vamos ao código:

Autor a = new Autor();
Livro l = new Livro();
a.setLivros(Collections.singleton(l));
manager.persist(a);

Resultado:

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: br.com.caelum.hibernate.testes.Livro

Essa exception não ocorreria se você tivesse chamado manager.persist(l); dentro dessa mesma transação, ou utilizasse cascade=CascadeType.PERSIST no relacionamento. Um objeto transiente, ao ser persistido, não pode se referenciar a outros objetos transientes, a não ser que haja cascade!

Agora considere:

Autor a = new Autor();
a.setId(1L);
a.setNome("paulo");
    
manager.persist(a);

Resultado:

org.hibernate.PersistentObjectException: detached entity passed to persist: br.com.caelum.hibernate.testes.Autor

O id de Autor está anotado com @GeneratedValue, quando ele percebe que há um id setado ele imagina que provavelmente esse objeto já deve existir no banco de dados (repare que ele não faz select, se você colocar um id que não existe, a mesma exception ocorrerá). persist não aceita objetos detached, apenas transient e managed. Essa exception não ocorreria se seu id não fosse @GeneratedValue, ou se você tivesse puxado esse Autor através do manager.find, por exemplo.

Vamos passar para o método merge. Porque o utilizamos tanto? Pois na web, quando recebemos os parâmetros e populamos nosso Entity para atualizá-lo, ele está detached: não foi pego através do EntityManager/Session, porém possui um id setado (o que anteriormente gerou a PersistentObjectException). O merge tem um detalhe importantíssimo: ele não vai passar o estado daquela entidade para managed (como faz o saveOrUpdate do Hibernate), e sim devolver uma versão da mesma entidade que seja managed (como fazia o velho saveOrUpdateCopy), em outras palavras, futuras mudanças na entidade passada não surtirão efeito. A outra grande diferença é que agora podemos passar como argumento um objeto transiente que já possua um id setado:

Autor a = new Autor();
a.setId(1L);
a.setNome("livro1");  
manager.merge(a);

a.setNome("livro2");

O código irá atualizar o nome do Livro de id 1 para livro1. Repare que a mudança para livro2 não surtirá efeito, já que o merge não faz attach do objeto passado como argumento, então nesse caso ele continua detached. Uma opção seria você pegar o retorno do método merge, que é o mesmo Autor, porém agora managed. A documentação do hibernate sobre o merge parece ser melhor que a especificação.

Aqui temos de ter muito cuidado, como não puxamos esse Autor pelo entityManager, caso ele possua alguns livros no seu relacionamento, nesse merge perderíamos todas essas informações!

Parece fácil evitar todas essas exceptions, então porque eu disse que ocorreram tanto no curso? Bem, estávamos usando a JPA de dentro de um container, não standalone. Considere então um session bean de granularidade fina que esteja agindo apenas como um dao (esse não é o ideal, mas fica para os testes):

@Remote
interface SessionBeanRemote {
  void persiste(Autor a);
  void persiste(Livro l);
  Livro buscaLivroPorNome(String nome);
}

E considere o cliente:

Livro l = new Livro();
sessionBeanRemoto.persiste(l);

Autor a = new Autor();
a.setLivros(Collections.singleton(l));
manager.persiste(a);

Repare como esse código é muito parecido com o primeiro desse post, porém já persistindo o Livro anteriormente, para evitar a TransientObjectException. Mas adivinhe, aqui é lançada uma TOE! Isso ocorre porque, apesar do Livro ter sido persistido, no cliente remoto ele estará transiente, pois sua chave primária não foi populada, já que o objeto foi serializado e só no servidor se encontra uma versão desse Livro com sua respectiva chave! Se fosse no EJB 2.x, onde o Entity bean também é um componente, isso não ocorreria. Mas aqui, fora do container, o Entity bean age realmente como um valeu object: não há ligação dele com o servidor.

Para resolver isso temos alguns idiomismos, mudaríamos nosso bean remoto para:

@Remote
interface SessionBeanRemote {
  Autor persiste(Autor a);
  Livro persiste(Livro l);
  Livro buscaLivroPorNome(String nome);
}

Um tanto estranho, e na nossa implementação devolveríamos o próprio argumento:

@Stateless
class SessionBean implements SessionBeanRemote {
  @PersistentContext
  private EntityManager manager;

  public Livro persiste(Livro l) {
    manager.persist(l);
    return l;
  }
  // outros metodos
}

Dessa maneira passaríamos de volta ao cliente uma versão detached do novo livro: agora com ID! Nosso cliente ficaria:

Livro l = new Livro();
l = sessionBeanRemoto.persiste(l)// agora pegamos o retorno!

Autor a = new Autor();
a.setLivros(Collections.singleton(l));
manager.persiste(a);

Existem outras alternativas, como retornar apenas a chave primária, utilizar session beans com maior granularidade, etc.

E a LazyInitializationException que mencionei? Usando hibernate ou JPA standalone estamos cansados de saber como evitá-la: basta manter a sessão aberta durante a renderização na camada de visualização. Mas com EJB a história é outra, sendo muito mais sutil: chame o método buscaPorNome, ele vai retornar ao cliente um Livro detached. Adivinhe o que acontece ao invocar o getAutores() no cliente?

  • Share/Bookmark



Caelum | Ensino e Inovação
São Paulo: Rua Vergueiro, 3185, cj. 87, próximo ao Metrô Vila Mariana   |   Tel. (11) 5571-2751
Rio de Janeiro: Rua Senador Dantas, 80, cj. 307/308 - Centro   |   Tel. (21) 2220-4156 ou 2297-0033
Brasília: SCS Qd. 8 Bl. B-50, Sala 521 - Ed. Venâncio 2000   |   Tel. (61) 3039-4222