terça-feira, 28 de dezembro de 2010

Tagged under:

Fluent Interface como apoio ao código legado


Falando em Java, a cultura de desenvolvimento é marcada por uma série de sufixos e prefixos que geralmente separam uma camada da outra, ou até uma determinada "responsabilidade" específica. Há um excelente post no blog de Philip Calçado que fala um pouco sobre a arquitetura "BOLOVO" e recomendo a leitura. O fato é que existem muitos patterns que são utilizados e muitas alternativas veem surgindo.
Antes de mais nada, não estou julgando o que é bom ou o que é ruim, cada caso é um caso, cada cenário será perfeito para o uso de qualquer design ou modelo. O que quero me referir neste texto é apenas mais uma das tantas abordagens de desenvolvimento.
Sem mais delongas, um modelo arquitetural que tem chamado minha atenção é a Fluent Interface. Quem quiser saber mais pode ler também esse excelente post de Guilherme Chapiewski. Fluent Interface nada mais é que uma interface com métodos sugestivos, mais próximos da linguagem natural dos humanos ou, se preferirem, uma DSL para os desenvolvedores.
Particularmente, trabalhei em alguns projetos que eu temia mover uma linha de código de lugar diante de tantos getters e setters, metodos de busca via query, com os chamados "obterPorFiltro" com mil linhas de ifs e elses.  Fora quando a regra de negócio determinava qual tipo de query seria montada em runtime, haja ifs. Em alguns projetos simples não tive o que me queixar, porém houve aqueles que hesitei muitas vezes em continuar ou até cogitei criar o meu próprio método para não quebrar alguma regra que esteja em funcionamento a anos.  Sim, estou falando, principalmente, dos sistemas legados.
Para os adeptos do TDD, uma das perguntas que o seu "design" deve responder é: "o meu código é testável?"
E para testar funcionalidades, nada melhor que programar para interfaces, como manda a orientação a objetos. Alguns questionamentos talvez tenham aberto a oportunidade de evidenciar as interfaces fluentes, que contribuem com a legibilidade e ao mesmo tempo facilita os testes unitários, documentações e tudo mais.

Para ilustrar, vou propor um problema. Digamos que na empresa em que trabalho todos os sistemas se comunicam via banco através de schemas e 80% dos sistemas estão utilizando query nativa nos códigos com jdbc, chamadas de procedures, etc. Agora vou exemplificar uma situação em que desejo desacoplar a minha query, que atualmente é do tipo "obterPorFiltro" cheio de ifs, getters e setters, e poder compor N combinações, de forma que, de acordo com a minha regra de negócio, posso fazer, ou não, uma junção, obter alguns campos, etc.


/**
 * SELECT
             MATRICULA || DIGITO,
             NOME
      FROM
             FUNCIONARIOS FUNC
 * @return
 * @throws Exception
 */
public interface Funcionarios extends FluentRepository<Funcionario> {
      
    /**
     * SELECT SITU.CODIGO, SITU.DESCRICAO
     *
     * FROM SITUACOES SITU
     *
     * AND SITU.CODIGO = FUNC.SITUACAO
     * AND SITU.CODIGO = 'A'
     *
     * @return
     */
    Funcionarios ativos();
    
    /**
     * AND FUNC.MATRICULA || FUNC.DIGITO = ?
     *
     * @param matricula
     * @return
     */
    Funcionarios com(String matricula);
  
    /**
     * SELECT EMPR.CODIGO, EMPR.DESCRICAO
     *
     * FROM EMPRESAS EMPR
     *
     * AND EMPR.CODIGO = FUNC.EMPR_CODIGO
     * AND EMPR.CODIGO = ?
     *
     * @param empresa.codigo
     * @return
     */
    Funcionarios da(Empresa empresa);

    T get() throws Exception;
   
    Collection<T> list() throws Exception;
}


public class Funcionario {

       private Long id;
       private String nome;
       private String matricula;
      
       // getters e setters
}


Pronto... temos as nossas interfaces fluentes e a nossa classe de domínio chamada Funcionario. Notem que evitei o uso de sufixos e prefixos (Funcionario - Domínio, Funcionarios - Repositório). A chamada pode ser similar a esta abaixo:


Funcionarios funcionarios = new FuncionarioDaoImpl();
Funcionario funcionarioPesquisado = funcionarios.ativos().com("1234").da(new Empresa(EmpresasDisponiveis.XPTO)).get();

Collection<Funcionario> funcionariosAtivos = funcionarios.ativos().list();


Esse exemplo pode servir também para quem estiver utilizando JPA ou Hibernate, mas abaixo preferi colocar um exemplo utilizando jdbc, ficando você livre de colocar o jdbcTemplate do Spring, Jdbc puro ou o framework de sua empresa/sua casa.



public class FluentJdbcSupport {

       private StringBuilder queryBuilder;
       private StringBuilder selectBuilder;
       private StringBuilder joinTablesBuilder;
       private StringBuilder clausesBuilder;
    private List<Object> params;
   
    public FluentJdbcSupport(String tableName, String firstField, String... otherFields) {
       this.initializeSelectBuilder(firstField, otherFields);
       this.initializeJoinTables(tableName);
       this.initializeClauses();
       this.initializeParams();
    }
   
    private void initializeSelectBuilder(String firstField, String... otherFields) {
       selectBuilder = new StringBuilder();
       selectBuilder.append(" SELECT ").append(firstField);
       for (String otherField : otherFields) {
                    selectBuilder.append(" , ").append(otherField);
             }
    }
   
    private void initializeJoinTables(String tableName) {
       joinTablesBuilder = new StringBuilder();
       joinTablesBuilder.append(" FROM ").append(tableName);
    }
   
    private void initializeClauses() {
       clausesBuilder = new StringBuilder();
       clausesBuilder.append(" WHERE 1 = 1 ");
    }
   
    private void initializeParams() {
       params = new ArrayList<Object>();
    }
   
    protected FluentJdbcSupport addSelectedField(String field) {
             selectBuilder.append(" , ").append(field);
             return this;
    }
   
    protected FluentJdbcSupport addJoinTable(String table) {
             joinTablesBuilder.append(" , ").append(table);
             return this;
    }
   
    protected FluentJdbcSupport addClause(String clause) {
       clausesBuilder.append(" AND ").append(clause);
       return this;
    }
   
    protected FluentJdbcSupport addParams(Object param) {
       params.add(param);
       return this;
    }
   
    protected void buildQuery() {
       queryBuilder = new StringBuilder().append(selectBuilder).append(joinTablesBuilder).append(clausesBuilder);
    }

       public StringBuilder getQuery() {
             return queryBuilder;
       }
}


Abaixo segue a classe filha da abstração, que seria a implementação do repositorio de funcionarios:

public class FuncionarioDao implements Funcionarios {
      
       private FluentJdbcSupport<Funcionario> fluentJdbcSupport;
      
       public FuncionarioDao() {
             this.fluentJdbcSupport = new FluentJdbcSupport<Funcionario>("FUNCIONARIOS""ID""NOME""MATRICULA || DIGITO");
       }

       @Override
       public Funcionarios ativos() {
             this.fluentJdbcSupport.addSelectedField("SITU.CODIGO");
             this.fluentJdbcSupport.addSelectedField("SITU.DESCRICAO");
             this.fluentJdbcSupport.addJoinTable("SITUACOES SITU");
             this.fluentJdbcSupport.addClause("SITU.CODIGO = FUNC.SITUACAO");
             this.fluentJdbcSupport.addClause("SITU.CODIGO = 'A'");
             return this;
       }

       @Override
       public Funcionarios com(String matricula) {
             this.fluentJdbcSupport.addClause("FUNC.MATRICULA || FUNC.DIGITO = ?");
             this.fluentJdbcSupport.addParams(matricula);
             return this;
       }

       @Override
       public Funcionarios da(Empresa empresa) {
             this.fluentJdbcSupport.addSelectedField("EMPR.CODIGO");
             this.fluentJdbcSupport.addSelectedField("EMPR.DESCRICAO");
             this.fluentJdbcSupport.addJoinTable("EMPRESAS EMPR");
             this.fluentJdbcSupport.addClause("EMPR.CODIGO = FUNC.EMPR_CODIGO");
             this.fluentJdbcSupport.addClause("EMPR.CODIGO = ?");
             this.fluentJdbcSupport.addParams(empresa.getCodigo());
             return this;
       }

       @Override
       public Funcionario get() throws Exception {
             this.fluentJdbcSupport.buildQuery();
             System.out.println(this.fluentJdbcSupport.getQuery());
             return null;
       }

       @Override
       public Collection<Funcionario> list() throws Exception {
             this.fluentJdbcSupport.buildQuery();
             System.out.println(this.fluentJdbcSupport.getQuery());
             return null;
       }

}


Bom... é isso pessoal. Como podem ver é uma solução bem caseira, mas o foco desse artigo é que há possibilidade de refatorar o seu código, construir DSL's internas para ilustrar melhor o domínio da aplicação, criar Facades com chamadas pré-definidas (sempre que obter usuarios obter sempre os ativos), enfim, é só mais uma forma de codificar. 
Guilherme Chapiewski também postou um excelente artigo sobre um exemplo prático de fluent interface, recomendo que deem uma lida.

Se alguém quiser discutir, criticar, opinar, dúvidas, só comentar! Abraço