terça-feira, 1 de dezembro de 2009

Repositorio, e especificações como implementar

terça-feira, 1 de dezembro de 2009
Algo que inconfundivelmente geralmente toda aplicação precisa ter, é o acesso a dados em um banco de dados. Antigamente as aplicações acessavam os dados gerando consultas diretamente da interface do usuário, esta abordagem acoplava a consulta com a interface visual, o que inevitavelmente levava a duplicar consultas em mais de uma área, como para tirar um relatório e para uma consulta ao sistema.

Mas ai as coisas foram evoluindo, a melhor coisa seria então separar a camada que acessava os dados da camada visual ou qualquer outra camada, para isto foi introduzido um conjunto de classes que faziam a separação dos dados sendo que estas classes agora eram responsáveis por fazer de fato o acesso aos dados, converter em objetos e repassar para o seu cliente (quem invocou o método) uma lista de objetos que representavam assim uma consulta ao banco de dados.

Quando uma solução se repete demais, podemos dizer que ela é um padrão, portanto assim surgiu o DAO ou Pattern DAO.

Este padrão visava além de desacoplar do cliente que necessita de dados do banco, tinha também o propósito de permitir o suporte a diversos bancos de dados, podendo a fonte dos dados vir até mesmo de um arquivo de texto, um WebService, XML, ou outra fonte qualquer, mas isto trouxe uma complexidade que ainda precisava ser atacada, a questão dos bancos de dados, diversos bancos de dados também era um problema visto que geralmente aquele o aquele outro banco tinha as suas peculiaridades, embora o uso do pattern DAO tenha desacoplado e permitido diversas implementações de acesso a fontes de dados diferentes, ainda era custoso ter que desenhar para bancos diferentes, além de ser extremamente repetitivo a aplicação do padrão, para permitir o acesso a uma fonte diferente de dados.

Como forma de unificar isto surgiu em cena um framework muito conhecido, o Hibernate, que agora fazia com que o acesso aos dados fosse facilitado, os DAOs agora podiam ser feitos de uma forma mais fácil.

Ainda em outro campo o Hibernate influenciou fortemente a criação da especificação JPA, que veio para substituir as Entity Beans CMP da especificação EJB 2, esta foi mais uma evolução onde agora mapeávamos diretamente através de anotações uma entidade e assim fazer o acesso aos dados.

Ao analisar, hoje vemos que progredimos muito com respeito ao acesso de dados, mas vemos que muitos tem implementado o padrão DAO como uma receita de bolo, muitas vezes sem entender o que ele faz. Muitos acabam implementando ele com a seguinte interface:
public interface DAO<t> {
    int count();
    
    List<t> findAll(int start, int max);

    Lis<t> findAll();

    T findById(Long id);
}
E quando precisão implementar uma consulta mais especifica acabam fazendo assim
public class DAOCliente {
    private List findClientesPorNome(String nome){
    .....
    }
} 
E se agora precisar filtrar por CPF e CNPJ ?

A resposta mais obvia era, implemento mais um método
private List<cliente> findClientesPorCNPJ(String CNPJ){
  .....
}

private List<cliente> findClientesPorCPF(String CPF){
  .....
}
Se pararmos para pensar por um momento veremos que isto faz com que a interface fique inchada, fazendo assim que o sistema fique muito rígido, exemplo de rigidez? Digamos que você precise contar os clientes que começam com um nome, outro método seria então implementado.

Pois bem percebendo isto muitos desenvolvedores, assim como eu vinham buscando soluções para tornar o sistema, mas flexível para consultas.

Ao ler o livro de Eric Evans Domain Driven Design, e ver diversos post do Shoes e muitos outros, pudemos ver uma abordagem melhor para acesso aos dados. Do que uma interface inflada.

Bem vamos explicar a que conclusão chegamos, um dos primeiros pontos que vimos foi que o cliente era responsável por dizer o que queria, isto também ocorria antes, visto que o cliente tinha de dizer que método do DAO era necessário invocar, agora o que fizemos foi diferente, ao invés de implementar rigidamente a consulta sobre o DAO que segundo Shoes, nos passaremos a chamá-lo a partir daqui de Repositório foi encapsular o filtro da consulta sob uma especificação, o que é uma especificação? Nada mais é do que dizer como você quer as coisas, sendo que ela serve tanto para acesso a dados de um banco mais também para validação, vamos a um exemplo para que fique mais claro:

- Primeiro vamos ver um exemplo de uma especificação para validação:

Digamos que você está montando um sistema de estoque e que agora você necessita comprar mais itens dos produtos que estão acabando, geralmente você iria fazer um IF sendo que os produtos que tiverem uma quantidade X seria adicionado:

for ( Produto produto : produtosEstoque) {
    if (produto.getQuantidadeEmEstoque()<15){
        listaDeProdutosAComprar.add(produto);
    }
}
Com certeza este código funciona e você obterá uma lista de produtos que estão com menos de 15 unidades no estoque. Mas um problema é que a lógica de produtos como esta pode ficar dispersa em vários locais da aplicação. Sendo assim você deveria encapsular a lógica de verificação, fazendo uma especificação assim:

Você tem a seguinte interface:

public interface Especificacao {
    boolean satisfaz(Object o);
}

public class EspecificacaoDeProdutosAcabando implements Especificacao {

    private int quantidadeMinima;

    public EspecificacaoDeProdutosAcabando(int quantidadeMinima){
        this.quantidadeMinima = quantidadeMinima;
    }

    public boolean satisfaz(Object o){
 
        // aqui vai a lógica para verificar se o produto não está acabando
 
        if (!(o instanceof Produto)){
            return false;
        }

        return ((Produto) o).getQuantidadeEmEstoque()<quantidadeMinima;
    }
}
Para usar agora você faz assim:

Especificacao produtoAcabando = new EspecificacaoDeProdutosAcabando(15);

for ( Produto produto : produtosEstoque) {
    if (produtoAcabando.satisfaz(produto)){
        listaDeProdutosAComprar.add(produto);
    }
}
Repare que ao fazer assim você encapsulou a lógica de verificação de um produto quanto a se ele deve ser comprado ou não, se agora para modificar a lógica se um produto está acabando, você simplesmente altera em um lugar.

Este sistema tem grande poder para objetos agora também pode ser usado para filtrar registros no banco de dados.

Para fazer isto eu uso para as consultas a api criteria do Hibernate.

A interface da especificação precisa ser modificada agora para o seguinte:

public interface Especificacao {

    boolean satisfaz(Object o);

    Criterion configurarCriteria(Criteria criteria,Object o);

}
O papel do método Criterion configurarCriteria(Criteria criteria,Object o) é criar um criterion do tipo restriction para que possa ser adicionado a criteria e assim efetuar o filtro no sistema.

Veja uma real implementação onde é criado a especificação de login valido para ser usado por um repositório.

Em uma aplicação apenas os usuários com senha e login validos e que estão ativos devem acessar ao sistema. Portanto teremos a seguinte especificação:

public class EspecificacaoLoginValido implements Especificacao {

    private String login;
    private String senha;
    
    public EspecificacaoLoginValido(String login,String senha){
        this.login = login;
        this.senha = senha;
    }
 
    public boolean satisfaz(Object o) {
        if (!(o instanceof Usuario) || o ==null){
            return false;
        }

        Usuario u = (Usuario) o;

        return login.equals(u.getLogin()) 
        && senha.equals(u.getSenha()) 
        && u.isAtivo();

    }    

    public Criterion configurarCriteria(Criteria criteria,Object o){

        if (!(o instanceof Usuario) || o ==null){
            return null;
        }
        
        Conjunction and = Restrictions.conjunction();
        and.add(Restrictions.eq("login",login);
        and.add(Restrictions.eq("senha",senha);
        and.add(Restrictions.eq("ativo",true);
        return and;
    }
}
O seu repositório (DAO) seria assim:

public class RepositorioUsuarios {

    public List recuperarPorEspecificacao(Especificacao espec) {

        Criteria criteria = session.createCriteria(Usuario.class);
        Criteria.add(espec.configurarRestricao(criteria,Usuario.class));
        return criteria.list();
    }
}
Agora você tem um Especificacao que filtra os registros no banco fazendo com que a criteria seja configurada fora do repositório.

Para usar para validação seria assim:

Especificacao loginValido = new EspecificacaoLoginValido("carlos","123");
Usuario usuario = new Usuario();
usuario.setLogin("Carlos");
usuario.setSenha("123");
usuario.setAtivo(true);

loginValido.satisfaz(usuario) // true login, senha e ativo
Para ser executada sobre o repositório seria assim

RepositorioUsuario usuarios = new RepositorioUsuario();
List<usuario> listagem = usuarios.recuperarPorEspecificacao(loginValido);

Isto recupera uma listagem de usuários que safisfazem a especificação.

Este é um exemplo de como usar.

Depois em um outro post explicarei como usar o padrão para casar filtros com o hibernate, seria algo como o exemplo abaixo:

EspecificacaoFaturasVencidas
EspecificacaoClientesInadiplenentes

Como usá-las em conjunto?

Isto fica para outro post.


Nenhum comentário :

Postar um comentário