Archive for setembro, 2006

genesis 3.0-EA4: Suporte SWT e tutorial pelo Edgar

segunda-feira, setembro 25th, 2006

Foi lançada alguns dias atrás a nova release do genesis, a 3.0-EA4. Uma das novidades é o binding SWT, expandindo o suporte desktop do framework, que já inclui Swing (desde a 3.0-EA3) e Thinlet.

O princípio do binding é o mesmo usado pelas outras tecnologias: você constrói sua interface normalmente, usando o Visual Editor do Eclipse ou codificando na mão mesmo, com o único requerimento de definir o nome dos seus componentes igual ao da propriedade ou ação com o que você deseja ligá-lo. No caso do SWT, usamos a propriedade data dos componentes como seu nome, mas isto pode ser mudado.

Escrever uma tela de login em SWT com o genesis é bem fácil. Primeiro definimos o form, que é o mesmo independente da tecnologia de GUI utilizada:


@Form
public class LoginForm {
   private String usuario;
   private String senha;

   public String getUsuario() {
      return usuario;
   }

   public void setUsuario(String usuario) {
      this.usuario = usuario;
   }

   public String getSenha() {
      return senha;
   }

   public void setSenha(String senha) {
      this.senha = senha;
   }

   @Action
   public void login() {
      System.out.println(usuario);
      System.out.println(senha);
   }

   @Action
   public void limpar() {
      setUsuario(null);
      setSenha(null);
   }
}

E depois o código que constrói a GUI SWT e faz a ligação com o form:


@ViewHandler
public class LoginSWTView {
   private Shell shell = null;
   private Composite composite = null;
   private Label labelUsuario = null;
   private Text usuario = null;
   private Label labelSenha = null;
   private Text senha = null;
   private Composite compositeBotoes = null;
   private Button limpar = null;
   private Button login = null;

   public LoginSWTView() {
      createShell();
      SWTBinder binder = new SWTBinder(shell, new LoginForm(), this);
      binder.bind();
   }

   private void createShell() {
      shell = new Shell();
      shell.setText("Login");
      createComposite();
      createCompositeBotoes();
      shell.setLayout(new GridLayout());

      shell.pack();
   }

   private void createComposite() {
      GridLayout gridLayout = new GridLayout();
      gridLayout.numColumns = 2;

      GridData gridData = new GridData();
      gridData.horizontalAlignment = GridData.FILL;
      gridData.verticalAlignment = GridData.FILL;

      composite = new Composite(shell, SWT.NONE);
      composite.setLayoutData(gridData);
      composite.setLayout(gridLayout);

      labelUsuario = new Label(composite, SWT.NONE);
      labelUsuario.setText("Usuário");

      usuario = new Text(composite, SWT.BORDER);
      usuario.setData("usuario");

      labelSenha = new Label(composite, SWT.NONE);
      labelSenha.setText("Senha");

      senha = new Text(composite, SWT.BORDER | SWT.PASSWORD);
      senha.setData("senha");
   }

   private void createCompositeBotoes() {
      RowLayout rowLayout = new RowLayout();
      rowLayout.fill = true;

      GridData gridData = new GridData();
      gridData.horizontalAlignment = GridData.END;
      gridData.verticalAlignment = GridData.FILL;

      compositeBotoes = new Composite(shell, SWT.NONE);
      compositeBotoes.setLayoutData(gridData);
      compositeBotoes.setLayout(rowLayout);

      limpar = new Button(compositeBotoes, SWT.NONE);
      limpar.setText("Limpar");
      limpar.setData("limpar");

      login = new Button(compositeBotoes, SWT.NONE);
      login.setText("Login");
      login.setData("login");
   }

   public void display() throws Exception {
      final Display display = Display.getDefault();
      Rectangle bounds = display.getBounds();
      shell.setLocation(bounds.x
            + (bounds.width - shell.getBounds().width) / 2, bounds.y
            + (bounds.height - shell.getBounds().height) / 2);

      shell.open();
      shell.forceActive();

      while (!shell.isDisposed()) {
         if (!display.readAndDispatch()) {
            display.sleep();
         }
      }

      display.dispose();
   }

   public static void main(String[] args) throws Exception {
      new LoginSWTView().display();
   }
}

Outra novidade é que, atendendo aos pedidos de diversos usuários, agora é possível utilizar a parte client do genesis simplesmente colocando os jars no classpath. O Edgar postou no seu blog, de forma bem didática, como funciona o binding e como utilizá-lo com Swing num sistema de locadora. Vale a pena conferir.

Caso tenham sugestões ou dúvidas a respeito do genesis, podem perguntar aqui no blog ou na lista de discussão do projeto, conforme explicado na doc em português do framework.

Vale a pena abstrair? – Parte 2

sexta-feira, setembro 22nd, 2006

O post “Vale a pena abstrair?” gerou muito mais comentários do que eu esperava. Infelizmente não pude postar uma resposta antes devido a diversas razões, mas agora vamos finalmente ao round 2 da discussão.

Primeiro, vamos deixar claro o objetivo do post anterior, pois muitos de vocês aparentemente entenderam algo diferente do que eu quis dizer (ou eu escrevi algo diferente do que eu estou pensando :-P). A minha intenção era mostrar que criar diversas interfaces e factories e camadas de abstração indiscriminadamente, na ilusão de que isso tornará mais fácil fazer mudanças no futuro, simplesmente acrescenta complexidade ao código e acaba sendo inútil no final. Ponto. Foi só isso. Os DAOs foram um exemplo, e eu os citei porque antigamente (3-4 anos atrás, veja só), era costume ter uma DAOFactory, que era uma interface, uma OracleDAOFactory, que era a implementação, o UsuarioDAO, interface, e o UsuarioOracleDAO, a implementação. Contudo, foi um exemplo infeliz, porque quase todos os comentários focaram no exemplo ao invés do assunto que ele ilustrava, mas houve vários pontos interessantes que gostaria de comentar. Vamos um de cada vez:

Fabio Kung:

Eu discordo no ponto de não abstrair o framework de ORM. Acho útil abstrair não para trocar fácil de banco de dados, mas sim para otimizações que possam ser necessárias. É comum acontecerem casos que você precisa abrir mão das vantagens do ORM e acessar o banco direto via jdbc, ou persistir o objeto em um lugar diferente (num arquivo xml, por exemplo) porque o banco é legado e você não pode criar outra tabela).

Nada num DAO sem interface impede isso. Se eu declarar meu DAO como:


public class UmDAOQualquer  {
    // ...
    public List<EntidadeQualquer> findByAlgumCriterio(String umaPropriedadeQualquer) {
        // ...
    }
}

O que te impede de implementar o método com JDBC ou XML? No caso de exceções, pode-se usar um modelo genérico de exceção ou ainda as exceções específicas disponíveis nas novas versões do Hibernate. Não é necessário ter interfaces pra isso.

Rubem Azenha:

Michael, o problema é que o pessoal as vezes pode usar isso como desculpa para fazer as coisas mal feitas…

No caso o DAO por exemplo, eu prefiro usar DAOs, com interface, abstraido e tudo mais. A questão não é só “e se a gente trocar de banco” mas também da testabilidade e da manutenabilidade (existe essa palavra?) da aplicação. Se você colocar no teu objeto de negócio a session direto do Hibernate, como você vai fazer os seus testes unitários? E se der um problema? Será que é no DAO ou na classe de negócio? Com o código isolado, fica *bem* mais fácil encontrar o problema, bem mais fácil de dar manutenção, bem mais fácil de reaproveitar.

Veja o ponto acima sobre interfaces. Para testes unitários, você pode criar seu DAO como subclasse do original, usar um framework de mock objects ou algo do gênero. Contudo, essa questão de testes unitários e a distinção entre negócio e acesso a banco de dados é interessante.

Por exemplo, em projetos que usam DAO extensivamente, já vi várias vezes classes de negócio que simplesmente delegavam para os DAOs, sem adicionar lógica nenhuma (as vezes convertiam exceções, mas isso deveria ser feito com aspectos, anyway). Nesse caso, o uso de DAOs é completamente desnecessário.

Em diversas situações, quando existem DAOs e classes de negócio interagindo, normalmente você tem, efetivamente, um problema de purismo OO. Eu sou a favor de manter a lógica em Java quando faz sentido, mas esse “quando faz sentido” é o que a maioria nunca aplica. Por exemplo, aplicar lógica de negócios em Java onde será necessário carregar um grande número de entidades em memória somente para filtrá-las quando isso poderia ser feito num select simplesmente limita a escalabilidade da aplicação.

Em outros casos, os metódos do DAO são resultado de otimizações e não um design natural. Por exemplo, tinha-se um processo lento em Java; muda-se isso para stored procedures em banco e, além de perder legibilidade, a performance degrada e faltam recursos presentes na linguagem. Então chega-se a um modelo híbrido em que parte é feita pela classe de negócios e parte pelo banco, através do DAO. E aí está o ponto: neste caso, testes unitários têm muito pouco valor, pois é a interação inteira que importa. Um teste integrado seria muito mais interessante neste caso ou talvez o DAO devesse ser eliminado e os métodos movidos para a classe de negócio.

Por fim, não estou condenando a separação entre DAO e camada de negócio; apenas mencionando que os casos mais comuns, na verdade os DAOs não deveriam existir ou a separação agrega pouco valor ao design, as vezes simplesmente tornando o mesmo mais complexo. Concordo que a separação deve existir sempre que necessário, mas a necessidade de interfaces em si só costuma surgir em raros casos.

Gyowanny:

Todos os argumentos são válidos, mas e se eu não quiser usar Hibernate? Se vocês só trabalharam a vida toda com aplicações de grande porte, usando JEE e tudo o mais, com certeza um framework de mapeamento O/R se faz necessário, mas já tive casos, com aplicações JSE de médio porte, onde o Hibernate deixou a aplicação pesada e então fez-se necessário utilizar o acesso a JDBC direto, ou seja, se eu tivesse abolido as interfaces DAO da aplicação o esforço para efetuar essa mudança seria muito maior, sem contar que eu perco a liberdade de poder usar outros frameworks de persistência.

Alguns esquecem que um dos requisitos básicos para um sistema bem projetado é a existência de acomplamento fraco ou abstração entre as camadas , principalmente na camada em questão.

Bem, aqui temos diversos pontos interessantes. Primeiro, que o Hibernate deixa sistemas de médio porte pesados. Eu já fiz alguns sistemas menores com Hibernate para uso pessoal e nunca tive problemas desse tipo. Normalmente, os problemas de performance que se tem com Hibernate são causados por falta de configuração apropriada e/ou mau uso das funcionalidades. Contudo, como não conheco o projeto, vou assumir que sua afirmação está correta.

Partindo desse pressuposto, se o problema de performance foi descoberto depois de uma parte significativa do código ter sido escrita, então o uso dos DAOs não foi um benefício para o projeto: o erro foi a falta de testes de performance, quer na definição da arquitetura, quer durante o desenvolvimento. Se o problema fosse detectado no começo do projeto, provavelmente não haveria diferença de tempo na substituição do Hibernate por outra solução, até porque você tem que levar em conta o tempo requerido para projetar e implementar os DAOs na comparação.

Não estou condenando o uso de DAOs nesse projeto especificamente, mas sim mostrando que o motivo usado para justificar o seu uso não mostra nenhum benefício real e sim que existia um outro problema.

Continuando…

kalecser:

Se não abstrair o acesso aos dados, seja ele via framework OR ou não, é impossível testar unitariamente o código, é impossível entregar uma funcionalidade sem modelar a parte de acesso a dados e a sua aplicação terá, de cara, uma dependência estática ainda mais gosmenta que as ditas interfaces e abstrações, o banco de dados :P

Sim, isso concordo. Mas na realidade, dificilmente não usamos Hibernate, que é um framework ORM, então criar factories e interfaces e níveis e níveis de abstração não ajuda aqui…

Emerson:

Eu uso a abstração para ler de duas fontes de dados de maneira transparente. Ex: Aqui onde trabalho usamos oracle e arquivos VSAM do mainframe. Determinado Business Object pode ser persistido ou em VSAM ou no Oracle. Pode ser feito um create() de um VSAM ou da base Oracle. Isso tudo por motivos que não vem ao caso aqui, porém é a nossa necessidade. Para nós é util essa abstração.

….

No meu caso como é Oracle e VSAM o Hibernate não resolve meu problema heheh.

Sim, Emerson, o seu caso parece mesmo ser uma excelente exemplo de benefício do uso dos DAOs e, nesse caso, até mesmo de interfaces. Aí sim vale a pena abstrair, porque você tem um caso de uso que justifica isso.

Um outro exemplo seria num produto que deve ser estendido pela equipe de desenvolvimento de quem compra e que pode se reutilizar das classes usadas por outros projetos do cliente. Nesse caso, também seria importante poder trocar o mecanismo de persistência.

Marcos Silva Pereira:

No meu caso, não é apenas abstrair o banco de dados, mas abstrair o acesso aos dados. Definir uma interface é mesmo desnecessario se vc já usa um ORM da vida, mas ao menos no meu caso, acho valido criar um componente para eu dizer: ei, me dê os dados e não me preocupar se ele usa criteria, hql, jdbc ou o que seja. A responsabilidade é dele, quero apenas os dados.

É justamente aí que talvez DAOs tenham uma utilidade que não seja necessariamente abstrair O banco de dados mas sim o acesso aos dados. É mais sobre Separation of Concerns do que sobre ter que mudar o tipo de repositorio.

Comentário interessante, mas que faz sentido somente quando não se cai nas situações que citei acima. Clareza e separação de responsabilidades são importantes; agora ficar apenas delegando e delegando métodos sem acrescentar comportamento é simplesmente adicionar complexidade ao código.

Os outros comentários foram mais ou menos semelhantes (ou concordaram comigo, hehehe), então não vou citá-los aqui. Se aqueles que escreveram acharem que esqueci de algum ponto, podem comentar nesse post.

Em resumo, só crie uma abstração quando ela fizer sentido para você. De forma mais prática: a menos que esteja desenvolvendo um produto ou framework, abstraia somente se houver mais de uma implementação e não apenas a “possibilidade de”, “e se um dia eu precisar” etc.