sexta-feira, 12 de agosto de 2011

Maus cheiros nos códigos

As vezes na ânsia de terminar e ver o programa funcionando, o programador acaba implementando as funcionalidades de forma descuidada e acaba por ter um código de baixa qualidade.

vejamos um exemplo:

public void baixarCliente(){
  boolean abertas = false;
  
  for (Fatura fatura : cliente.getFaturas()){
    if (fatura.getStatus().equals(Status.EM_ABERTO)){
      abertas = true; 
      break;
    }
  }

  if (abertas)
    throw new FaturasEmAbertoException();

  cliente.setBaixado(true);
  cliente.setDataBaixa(new Date());
  MailSender mail = new MailSender("Baixa.modelo");
  mail.send(cliente.getEmail());

}
Este Código por exemplo ao ler ele com cuidado podemos entender e ele faz o que é necessário.
Mas poderia ser simplificado para poder transmitir mais conhecimento. Vejam como ficaria refatorado.

public void baixarCliente(){
  
  if (clientePossuiFaturasEmAberto())
    throw new FaturasEmAbertoException();

  registraBaixaDoClienteEEnviaEmailParaOCliente();

}

private void registraBaixaDoClienteEEnviaEmailParaOCliente(){
  cliente.setBaixado(true);
  cliente.setDataBaixa(new Date());
  enviaEmailParaOClienteNotificandoABaixa();
}

private void enviaEmailParaOClienteNotificandoABaixa(){
  new MailSender("Baixa.modelo").send(cliente.getEmail());
}

private boolean clientePossuiFaturasEmAberto(){
  for (Fatura fatura : cliente.getFaturas())
    if (fatura.getStatus().equals(Status.EM_ABERTO))
      return true;

  return false;
}


Com um código como esse fica mais claro as operações que estão sendo executadas. Ao ler o código que faz a baixa do cliente:


if (clientePossuiFaturasEmAberto())
throw new FaturasEmAbertoException();

registraBaixaDoClienteEEnviaEmailParaOCliente();

fica claro as verificações que são feitas.

ao desenvolver pense em fazer isso, pois os benefícios serão vistos na manutenção do seu sistema.

quinta-feira, 14 de outubro de 2010

Habilitando dominios ricos no SEAM

A metodologia DDD enfatiza que ao desenvolvermos as regras de negócios devem ficar na camada de domínio, concentrando todo os relacionamentos e o funcionamento do sistema nesta camada, portanto poderíamos ter um método como:

cliente.pagar(faturaEmAtrazo);
Cliente é uma entidade do sistema, com isso não quero dizer que o cliente deve ter a lógica de pagamento de faturas, mais é ele quem inicia o processo, de onde então vem a lógica? De uma service:
public class ControleDeFaturas {
  public void receberPagamento(Cliente cliente,Fatura fatura){
    .... // aqui residiria a lógica de 
  }
}
e o cliente como fica? Ele recebe por Dependency Injection o serviço de controle de faturas, usando o framework seam teriamos algo como:
@Entity
public class Cliente {

  @In
  private ControleDeFaturas controleDeFaturas;

  ...

  public void pagar(Fatura fatura){
    controleDeFaturas.receberPagamento(this,fatura);
  }

}
Assim o modelo fica melhor, agora um problema que temos no SEAM é que ele não faz injeção de dependências nas entitys, para resolver este problema temos várias soluções, poderíamos usar a entity com uma factory que receberia o controleDeFaturas, ou mesmo fazer lookup da dependência usando Component.getInstance("controleDeFaturas"), uma abordagem que usei a qual estou gostando é o uso de Aspectos, aspecto resolve este problema, de uma forma transparente, onde mesmo se instanciarmos o Cliente por meio de um new Cliente(); ainda teríamos a injeção da dependência.

você pode visualizar a classe aqui que resolveu meu problema (usa aspectj).


Com certeza a classe pode ser melhorada, mas inicialmente resolveu o problema e permitiu uma habilitação de um domínio rico usando o SEAM, basicamente ele injeta uma instancia sempre que encontra uma classe com a anotação @Entity e um @In tanto faz ser nos atributos da classe, ou nos métodos acessores.

Devo agradecer ao Alessandro Lazarotti que sugeriu o aspecto acima como solução no fórum do guj.

quarta-feira, 6 de outubro de 2010

Fluent Interfaces e operadores Lógicos

Algo com que todos os desenvolvedores tiveram como problema é a multiplicade de operações repetidas, creio que um exemplo seja melhor.

Reparem neste trecho de código:
public class LocalizadorDeClientes{
    public List<cliente> buscarPorCPF(String cpf){
        //implementação da busca.
    }
    
    public List<cliente> buscarPorNome(String nome){
        // implementação da busca.
    }
}
O cliente solicita agora que a busca seja feita por cliente inadimplente, a implementação mais comum seria mais um método de pesquisa:
public List<cliente> buscarInadiplentes(){
...
}
Essa abordagem é ruim? Não necessariamente, mas podemos usar um esquema diferente, o cliente te solicita agora quero combinar os filtros, muitos diriam que não dá ou fariam métodos diversos para todas as combinações possíveis.

Para casos como estes em que queremos combinar comportamentos poderemos tirar proveito da fluent interfaces:

Os exemplos apresentados aqui foram feitos apenas para exemplificar a metodologia podendo variar em implementação.

Reparem no exemplo modificado, usando fluent interfaces:
public class LocalizadorDeClientes{
public LocalizadorDeClientes porCPF(String cpf){
//configura a busca para ser realizada por cpf
return this;
}

public LocalizadorDeClientes porNome(String nome){
//configura a busca para ser realizada por nome
return this;
}

public LocalizadorDeClientes inadimplente(){
//configura a busca para ser realizada por inadimplentes
return this;
}

public List<cliente> fazerBusca(){
//configura a busca para ser realizada por inadimplentes
return this;
}

}
o uso seria assim:
LocalizadorDeClientes buscador = new LocalizadorDeClientes();
buscador.PorCPF(numCPF).porNome("carlos").inadimplente().fazerBusca();
Reparem que agora você pode encadear a chamada, fazendo com que a busca por exemplo possa ser configurável.

Este exemplo pode ser estendido usando um esquema de operadores lógicos, bastaria adicionar os métodos na classe:
public LocalizadorDeClientes e(){
// configura a busca para executar com o operador e
// no hibernate seria um conjunction em memória seria um &&
return this;
}

public LocalizadorDeClientes ou(){
// configura a busca para executar com o operador ou
// no hibernate seria um disjunction em memória seria um ||
return this;
}

public LocalizadorDeClientes nao(){
// configura a busca para executar com o operador nao
// no hibernate seria um Restriction.not em memória seria um !
return this;
}
Agora poderia fazer as consultas por exemplo assim:
LocalizadorDeClientes buscador = new LocalizadorDeClientes();
buscador.PorCPF(numCPF).e().porNome("carlos").nao().inadimplente().fazerBusca();
um esquema como este de fluent interfaces permite compor comportamento complexo a partir de um conjunto de comportamentos simples, a projetos que já utilizam isso como o Calopsita isso foi até mesmo discutido recentemente o Tectura no tópico nomes Complexos/descritivos no modelo e Martin flower fala exatamente sobre isso em seu artigo sobre fluent interfaces.

Tente entender o que é fluent interfaces, e o seu uso com operadores lógicos, pois ajudam a ter um domínio mais rico em seu software.

quinta-feira, 19 de agosto de 2010

Repita comigo cada teste no seu galho

Algo que pode dificultar um pouco para quem está iniciando a programar usando TDD ou BDD e conseguir fazer a distinção do que é um teste de unidade de um teste funcional.

O problema que geralmente se encontra é o que o teste funcional pode parecer muito com o seu teste de unidade, mas sem estar mockado.

Isso é normal e não deve ser encarado como uma duplicação do código de teste.

Basta lembrar do objeto dos testes, um teste Funcional responde se determinada funcionalidade esta ok, portanto não usamos mocks mas objetos reais, o que desejamos é validar a interação entre os objetos, esse tipo de teste te responde "você está com problema aqui nessa funcionalidade", o que difere do teste de unidade pois o objetivo dele é validar a lógica interna de sua classe, portanto a resposta de uma falha em um teste de unidade é "você esta com problema na linha x".

quinta-feira, 12 de agosto de 2010

TDD e o design de sua aplicação

Ao programarmos é muito comum não termos um escopo muito bem definido do deve ser desenvolvido, embora saibamos como devemos criar os nossos códigos.

Em que TDD ajuda no design da sua aplicação? Simples, ele o mantém limpo a medida que descobre novas funcionalidades.

Suponhamos que estamos usando o estilo de fora para dentro na construção de nossos testes, devemos então começar pela interface do usuário, portanto teríamos o seguinte teste:

public class CadastroClienteTest {
  @Test public void deveriaEmitirMensagemDeAvisoParaClienteEmAtrazoAoSelecionarCliente(){}
}

Esse teste serve de ponto de partida para o seu design lembre-se que aqui você ainda não pensou em acesso ao banco e em nada de services etc..., apenas disse uma funcionalidade que a sua interface deveria ter, ao ir implementando os métodos e usando uma classe para mockar as suas dependências, você vai desenhando o seu código, implementando apenas aquilo que deve ser implementado.

Teriamos o seguinte teste:
public class CadastroClienteTest {
  
  private CadastroClienteManageBean telaCadastro;  
  private FacesMessages facesMessages;

  @Before public void configuraDependencias(){
    facesMessages = Mockito.mock(FacesMessages.class);
    telaCadastro = new CadastroClienteManageBean();
    telaCadastro.setFacesMessages(facesMessages);
  }

  @Test public void deveriaEmitirMensagemDeAvisoParaClienteEmAtrazoAoSelecionarCliente(){
     telaCadastro.selecionarCliente(1);
     Mockito.verify(facesMessages).add(FacesMessage.SEVERITY_ERROR,"cliente em atrazo");
  }
}
e sua respectiva classe:

public class CadastroClienteManageBean {

  private FacesMessages facesMessages;

  public void selecionarCliente(Integer id){
    // deveria ir ao banco
    facesMessages.add(FacesMessage.SEVERITY_ERROR,"cliente em atrazo");
  }
  
  public void setFacesMessages(FacesMessages facesMessages){
    this.facesMessages = facesMessages;
  }

}

Agora reparando neste código perceba que o sistema não está indo até o banco, para de fato trazer o resultado, ai entra algo que acho incrível do TDD, ele te ajuda a ter um design limpo.

vamos agora melhorar o teste, para fins de simplificação não iremos criar um service para o acesso ao banco, vamos conectar diretamente:

public class CadastroClienteTest {
  
  private CadastroClienteManageBean telaCadastro;  
  private FacesMessages facesMessages;

  @Before public void configuraDependencias(){
    facesMessages = Mockito.mock(FacesMessages.class);
    registrosDeClientes = Mockito.mock(RegistrosDeClientes.class);
    telaCadastro = new CadastroClienteManageBean();
    telaCadastro.setFacesMessages(facesMessages);

  }

  @Test public void deveriaEmitirMensagemDeAvisoParaClienteEmAtrazoAoSelecionarCliente(){
     Cliente clienteEmAtrazo = new Cliente();
     clienteEmAtrazo.setEmAtrazo(true);
     Mockito.when(registrosDeClientes.buscarCliente(1)).thenReturn(clienteEmAtrazo);
     telaCadastro.selecionarCliente(1);
     Mockito.verify(registrosDeClientes).buscarCliente(1);
     Mockito.verify(facesMessages).add(FacesMessage.SEVERITY_ERROR,"cliente em atrazo");
  }
}

public interface RegistrosDeClientes {

  public Cliente buscarCliente(Integer id);

}

Repare que o seu teste agora está sugerindo que você tenha um método de busca, uma vez que você tenha terminado o seu teste na tela, uma abordagem seria descer o nível e pegar todas as classes que você mockou no teste da interface e agora ir implementando o teste para as camadas mais baixas.

TDD ajuda a melhorar o design, fazendo você programar aquilo que você precisa implementar, ao invés de ficar criando métodos para classes que você acha que irá precisar.

O método usado aqui baseasse no teste de fora para dentro, onde fomos construindo um teste para a interface e mockando os objetos das camadas abaixo, e posteriormente deveríamos implementar os testes das camadas mais baixas, com os dados que coletamos dos mocks da camada superior.

Em resumo o mock te sugere o que pode ser necessário que você tenha que implementar, caso não tenha usado ainda TDD, faça um teste e se esforce, pois realmente vale a pena.

quinta-feira, 14 de janeiro de 2010

Framework JBee Behavior-driven development Lançado

É bem aceito na comunidade um novo paradigma de desenvolvimento de sistemas o TDD, em que consiste o desenvolvimento de testes antes da aplicação ser construida, utimamente em ganhado muita força o BDD que é o desenvolvimento orientado a comportamento, os benefícios de aplicar o BDD é a facilidade de fazer testes de aceitação e regressão no sistema.

Para programar usando a metodologia do BDD (Behavior-driven development) o desenvolvedor escreve uma história que consiste nos passos para o usuário, sendo que esta história é coduzida pelo uso de palavras chaves (Dado que, Quando, Então, E) que são interpretadas e executadas no sistema.

Para facilitar o desenvolvimento orientado a BDD eu desenvolvi um framework chamado JBee onde o objetivo dele é facilitar o uso desta metodologia de testes.

O framework é compatível com o Junit.

Acessem a pagina http://sites.google.com/site/jbeetest/ para conhecer o framework e ver a sua utilidade em ação.

segunda-feira, 21 de dezembro de 2009

Best Pratices - Static factory

Ao iniciarmos no conceito da orientação a objetos, muitas vezes não entendemos plenamente como usar a orientação a objetos, ninguém está isento disso, e o maior problema é que muitas vezes as pessoas se fecham a acham que sabem tudo sobre orientação a objetos, ou que apenas por usar uma classe já está usando orientação a objetos, estes são erros comuns, por exemplo veja os programadores que utilizam o Delphi, embora usem objetos, a programação parece ser mais estruturada e a lógica fica dispersa nas ações, o código fica de difícil manutenção.

Geralmente quando ocorre este problema o mais fácil as vezes é reescrever todo o sistema, isto mesmo se você já teve este pensamento, você possivelmente está fazendo alguma coisa errada.

Martin Flower se refere a estes problemas como mal cheiros no código, bem agora iremos abordar exatamente um destes problemas.

A forma mais natural de obtermos uma instância de um objeto é através do uso de um construtor, usado em java da seguinte forma:
Relatorio relContas = new Relatorio();
ao executarmos este código obteremos um objeto chamado relatório, mas uma abordagem como está é pouco flexível, considere que você precisa configurar dados do relatório para que o mesmo seja usado, uma forma de fazer isto é por oferecer a os dados necessários para a classe relatório funcionar, como parametros no construtor, veja o exemplo:
Relatorio relContas = new Relatorio("Relatorio de contas dos clientes",new Date());
repare que agora o nosso objeto relatório é gerado usando parâmetros no construtor, qual o problema desta abordagem? apenas analisando este trecho de código você não seria capaz de dizer o que os parâmetros que estão sendo passados representam, repare que você neste momento teria de fazer uma consulta ao javadoc da classe ou mesmo acessá-la para verificar o que ela está querendo dizer.

Outro problema que temos é que não conseguimos expandir as funcionalidades da classe, repare que se requisermos fazer cache o objeto relatório isto não será possível.

Usarmos uma abordagem como está com certeza (uso do operador new), torna o sistema menos flexível e conseqüentemente de mais difícil manutenção.

Podemos resolver este problema de duas maneiras, usarmos StaticFactorys.

Uma static factory permite você configurar melhor a sua classe e expor também o que você deseja fazer.

Veja o exemplo usando o conceito de static factory:
Relatorio relContas = Relatorio.comTitulo("Relatorio de contas do Cliente");
repare que agora o código parece fazer mais sentido, ao ver esta linha fica claro que você está dizendo qual é o título do relatório.

a classe fica assim:
public class Relatorio{
    ...

    private Relatorio(){}
    
    public static Relatorio comTitulo(String titulo){
        Relatorio rel = new Relatorio();
        rel.setTitulo(titulo);
        return rel;
    }

    ...
}
O uso de static factory method possui as vantagens que se você deseja tornar o objeto um Sigleton, basta modificar a fábrica estática, se você deseja fazer cache de objetos num pool para compartilhar entre as chamadas, uma abordagem como está com toda certeza permite esta flexibilidade.

Posteriormente irei abordar o uso do padrão Builder para a construção de objetos.

quarta-feira, 9 de dezembro de 2009

Repositorio, e especificações como implementar - Parte 2

Em meu post anterior citei como fazer a implementação flexível do padrão Repositório e Especificação, citado por Eric Evans juntamente com o Hibernate, realmente as pessoas tem muita confusão de como implementar, uma coisa sempre e certa a sempre 1001 maneiras de implementar algo, e como Eric Evans, em seus exemplos, foi dado do SQL direto a implementação da Especificação e do Repositório, mas nós podemos usar o Hibernate que facilita muito a vida, e ele disse também sobre isto, que muitas vezes o sistema de mapeamento objeto relacional pode nos ajudar.

Mas vamos ao que interessa, no post anterior falei a respeito de como implementar a Especificação e vimos que a mesma serve para manter as regras de negócio da aplicação toda sobre um único lugar, assim o sistema se torna de mais fácil manutenção, e podemos ganhar muito com isto, nem sempre e fácil fazer a implementação destes padrões, visto a grande confusão e insistência em fazer as coisas usando as receitas de bolo da programação.

Algo que com certeza podemos mais cedo ou mais tarde fazer nos nossos sistemas é a junção de filtros para obter um resultado, vimos que a Especificação pode nos ajudar a abstrair do cliente toda a complexidade do filtro da consulta e manter as coisas simples, mas vamos a um exemplo real no sistema.

Digamos que o cliente diga o seguinte a você que está desenvolvendo o sistema.

"O relatório de clientes está bom mas, gostaríamos que na tela que faz a configuração do relatório seja incluído outros filtros onde gostaríamos de filtrar os clientes do relatório que possuem não contatamos a mais de 20 dias, visto que estes clientes devem ter uma atenção especial, assim como já funciona na tela do cadastro, e gostaria além disso também que fosse exibido os que tiveram uma reclamação do nosso serviço no histórico."

Repare que neste dialogo o cliente solicitou que você casa-se ou junta-se filtros diferentes, um filtro já existe, sendo que já está funcionando na tela de cadastro, a Especificação que já está implementada e a de clientes sem contato a mais de 20 dias, agora ele te Especificou algo novo, o cliente que possui uma ou mais reclamações. Vejamos então o que temos e o que deveríamos desenvolver:

public class EspecificacaoClientesComReclamacao implements Especificacao {

    private Integer numReclamacoes;
 
    public EspecificacaoClienteComReclamacao(Integer numReclamacoes){
        this.numReclamacoes = numReclamacoes;
    }
 
    public Criterion configurarCriteria(Criteria criteria,Class alvo){
        if (alvo != Cliente.class) return null;
        return Restrictions.gt("numReclamacoes", numReclamacoes );
    }
}
E a outra especificacao:

public class EspecificacaoClientesSemContato implements Especificacao {
    private Integer dias;
 
    public EspecificacaoClienteSemContato(Integer dias){
        this.dias = dias;
    }
 
    public Criterion configurarCriteria(Criteria criteria,Class alvo){
        if (alvo != Cliente.class) return null;
        Date dataAtual = new Date();  
        Calendar calendarData = Calendar.getInstance();  
        calendarData.setTime(dataAtual);  
        calendarData.add(Calendar.DATE,dias);  
        Date dataInicial = calendarData.getTime();  
  
        return Restrictions.gt("ultimoContato", dataInicial );
    }
}

Repare que no dialogo o cliente queria executar estas duas especificações para obter a lista?

Como fazer isto então? Bem conforme Evans comentou podemos pegar emprestado o conceito de operadores lógicos, o que são estes operadores lógicos? Seria o seguinte:

AND, OR, NOT

Veja como ficaria para usarmos os operadores lógicos, e assim agrupar as duas Especificações:
Especificacao clientesSemContato = new EspecificacaoClientesSemContato();

Especificacao clientesComReclamacao = new EspecificacaoClientesComReclamacao();

Especificacao especificacoes = clientesSemContato.and(clientesComRelamacao);
Repare que na ultima linha juntamos as duas especificações e agora poderíamos passar para o repositório as especificações agrupadas assim.
RepositorioCliente clientes = new RepositorioCliente();
clientes.recuperarPorEspecificacao(especificacoes);
Desta forma estaríamos passando não uma mas sim duas especificações que poderiam ser executadas.

E digamos que queríamos os clientes sem contato OU os clientes com reclamação? Poderíamos fazer assim:
Especificacao especificacoes = clientesSemContato.or(clientesComRelamacao);
Repare agora o uso do operador or? O mesmo se aplica ao NOT onde queremos os clientes sem contato que não tiveram reclamação:
Especificacao especificações = clientesSemContato.not(clientesComRelamacao);

Repararam no poder deste simples sistema? O cliente tem total liberdade para especificar que resultado deseja sem se preocupar em como ele será obtido, sendo que como ele é obtido fica a cargo do repositório.


Pois bem como implementar isto em Java e usando o hibernate?

A Especificação que era uma interface agora irá usar o padrão Template Method, ela passará a ser conforme mostrado abaixo:

public abstract class Especificacao {
    public Especificacao and(Especificacao... especificacoes) {
        List<especificacao> especs = new ArrayList<especificacao>();
        for (Especificacao e : especificacoes)
            especs.add(e);
        especs.add(this);

        return new EspecificacaoAnd(especs.toArray(new Especificacao[especs.size()]));
    }

    public Especificacao not() {
        return new EspecificacaoNot(this);
    }

    public Especificacao or(Especificacao... especificacoes) {
        List<especificacao> especs = new ArrayList<especificacao>();
        for (Especificacao e : especificacoes)
            especs.add(e);
        especs.add(this);

        return new EspecificacaoOr(especs.toArray(new Especificacao[especs.size()]));
    }

    @SuppressWarnings("unchecked")
    public abstract Criterion configurarRestricao(Criteria criteria, Class classe) throws DomainException;

    public abstract boolean satisfazEspecificacao(Object o) throws DomainException;
    }
}
E os objetos que fazem uso do operador AND,OR,NOT são implementados no exemplo abaixo:

Especificação Or:

class EspecificacaoOr extends Especificacao {

    private Especificacao[] especificacoes;

    public EspecificacaoOr(Especificacao... especificacoes) {
        this.especificacoes = especificacoes;
    }

    @SuppressWarnings("unchecked")
    public Criterion configurarRestricao(Criteria criteria, Class classe) throws DomainException {
        Disjunction or = Restrictions.disjunction();

        for (Especificacao espec : especificacoes) {
            if (espec == null) continue;
            Criterion ret = espec.configurarRestricao(criteria, classe);
            if (ret != null)
                or.add(ret);
        }

        return or;
    }

    public boolean satisfazEspecificacao(Object o) throws DomainException {
        for (Especificacao espec : especificacoes) {
            boolean retorno = espec.satisfazEspecificacao(o);
            if (retorno)
                return true;
        }
        return false;
    }
}
Especificação AND:

class EspecificacaoAnd extends Especificacao  {
    private Especificacao[] especificacoes;
    public EspecificacaoAnd(Especificacao... especificacoes) {
    this.especificacoes = especificacoes;
    }
    @SuppressWarnings("unchecked")
    @Override
    public Criterion configurarRestricao(Criteria criteria,Class classe) throws DomainException {
        Conjunction and = Restrictions.conjunction();
        List<criterion> retorno = new ArrayList<criterion>();
        for (Especificacao espec : especificacoes) {
            if (espec == null) continue;
            Criterion ret = espec.configurarRestricao(criteria,classe);
            if (ret != null){
                retorno.add(ret);
                and.add(ret);
            }
        }

        if (retorno.size() == 0) return null;
  
        return and;
    }

    @Override
    public boolean satisfazEspecificacao(Object o) throws DomainException{
        for (Especificacao espec : especificacoes) {
            Boolean retorno = espec.satisfazEspecificacao(o);
            if (!retorno)
                return false;
        }
        return true;
    }
}
Especificação NOT:

class EspecificacaoNot extends Especificacao {

    private Especificacao espec;

    public EspecificacaoNot(Especificacao especificacao) {
        this.espec = especificacao;
    }

    @SuppressWarnings("unchecked")
    public Criterion configurarRestricao(Criteria criteria,Class classe) throws DomainException {
        if (espec == null) return null;
        Criterion ret = espec.configurarRestricao(criteria,classe);
        if (ret != null)
            return Restrictions.not(ret);

        return null;
    }

    public boolean satisfazEspecificacao(Object o) throws DomainException {
        return !espec.satisfazEspecificacao(o);
    }
}
Com estas especificações agora podemos usar o sistema conforme exemplificado e passar para o repositório agora não mais uma especificação e sim varias.

Conforme podemos ver alisto abaixo o real benefício desta implementação:

1. Classes mais leves: O repositório passa a ter uma interface simples e não recheada de métodos.
2. As regras da aplicação ficam em apenas um lugar.
3. As especificações podem ser combinadas e assim filtros mais complexos, com uso dos operadores lógicos.
4. Maior clareza, e encapsulamento da regra.

Em outro Post mostrarei como utilizar as especificações para executar outras coisas como recuperar um conjunto de registros paginado, Ordenado, usando especificações e como fazer isto na prática, postarei um código de exemplo.

terça-feira, 1 de dezembro de 2009

Repositorio, e especificações como implementar

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.

segunda-feira, 28 de setembro de 2009

Expressões de ligação - validações customizadas em banco

Hoje, me deparei com um problema interessante e a sua solução foi realmente muito simples.

Eis o problema os campos de uma determinada tela seriam construídos dinâmicamente, e cada campo tinha as suas peculiaridades de validação, a idéia que tivemos foi então efetuar também a validação de forma dinâmica, veja o exemplo, temos um formulário dinâmico com um campo chamado telefone e o campo telefone é de preenchimento obrigatório se fosse feito em código, você escreveria algo parecido com isto:
"".equals(telefone);
caso o código acima tenha um retorno verdadeiro, o campo não foi preenchido, mas ai vem o problema, não dá para implementar isto de forma dinâmica, onde apenas uma alteração no banco já mudaria as regras de validação, pensei em usar o drools, mas a solução do drools parecia complexa demais para resolver apenas um simples problema de validação. Assim cheguei a solução descrita aqui, basicamente usei algo que todo projeto web tem, o EL (Expressions Language), esta linguagem de expressões foi usada de forma realmente interessante com o uso do SEAM, o SEAM contém uma classe chamada Expressions que cria MethodExpressions e ValueExpressions, no caso o que queria saber é se um VALOR era verdadeiro ou não, portanto usei o ValueExpressions, fiz a seguinte prova conceito.

Em um managedBean coloquei o seguinte código:
Object retorno = Expressions.instance().createValueExpression("#{1<2}").getValue();
System.out.println(retorno);
O interessante deste código foi que ele retornou corretamente o valor true, visto que 1 é menor que 2, portanto percebi que podemos validar assim agora o problema era como validar o formulário, bem foi usado outro objeto do seam chamado Contexts, este objeto permite acesso aos contextos do seam, Contexts.getSessionContext() por exemplo retorna um Context que contém os objeto colocado em sessão, o código ficou assim:
Contexts.getEventContext().set("a", 1);
Contexts.getEventContext().set("b", 2);
Object retorno = Expressions.instance().createValueExpression("#{a<b}").getvalue(); 
Contexts.getEventContext().remove("a");
Contexts.getEventContext().remove("b");
System.out.println(retorno);
Agora ele avaliou o valor de a e b e retornou true, reparem que os valores 1 e 2 não estão mais na expressão, mas sim no contexto de evento, que é o primeiro a ser consultado pelo seam usei ele para evitar conflito de nomes nos outros contextos sendo que logo após a execução o contexto foi limpo, de posse do retorno da expressão podemos agora validar o campo de forma dinâmica, realmente interessante a solução e muito simples de ser usada visto que reutiliza o conhecimento que o programador já possui ao criar as expressões de valor EL.