Query Object

Objectivo

Permite estabelecer um critério de pesquisa de forma orientada a objetos.

Propósito

Query Object(Objeto de Pesquisa) é um padrão de projeto que visa libertar o programador de conhecer e/ou usar uma linguagem de pesquisa de dados como SQL ou uma API proprietária de um fornecedor de tecnologia.

Você tem um mecanismo de pesquisa de dados: um banco relacional, por exemplo. Você envia comandos para esse mecanismo através de uma linguagem própria (SQL) ou uma API própria. Você não quer que o seu sistema dependa dessa linguagem ou você não quer usar essa linguagem para especificar os parâmetros da pesquisa. Você pode não ter, ou querer, uma equipe que entenda ou domine essa linguagem. Ela pode ser muito complexa ou repetitiva destruindo a sua produtividade. A construção da frase pode ser dinâmica ou depender de condições que tornam a sua escrita desconexa. Os motivos podem ser vários.

O padrão propõe que se utilize um objeto para conter todos os detalhes (parâmetros) necessários a construir a frase. A execução é deixada a um Interpreter. Um Interpreter é um objeto que interpretará o Query Objet e o transformará na frase da linguagem ou executá mecanismos da API subjacente como se fosse utilizada diretamente.

O Query Objet funciona como uma especificação desacoplada da linguagem final. Isto permite que a mesma especificação seja usada por interpretadores diferentes para atuar em contextos diferentes. Por exemplo, o mesmo objeto de pesquisa poderia ser traduzido numa frase SQL, numa frase EQL ou numa frase XPath. O mesmo objeto poderia ser utilizado como filtro de uma coleção em memória transformando-o num objeto que processe uma Collection. O detalhe de um objeto de pesquisa no mundo OO e relacional resume-se a especificar uma relação entre o valor presente num campo e um valor passado. A relação é normalmente de 2 tipos:

  • Comparação – operações de igualdade ou relação de ordenação. As operações que comparam valores null são também comuns

  • Inclusão – operações que determinam se o valor está contido numa lista de valores. Esta lista pode ser fixa ou ela própria resulta de uma pesquisa.

Mais tipos podem ser adicionados como operações de agregação (somas) ou de determinação de extremos(máximo, mínimo). As operações sobre valores podem operar sobre valores fixos ou dinâmicos(resultantes de outras pesquisas) ou ainda sobre valores contidos em entidades que têm uma relação com a entidade pesquisada (join).

Implementação

Idealmente o Query Object é auto-suficiente para representar os parâmetros de uma pesquisa num conjunto de dados mas na prática a implementação de um Query Object pode depender do interpretador associado, de um mecanismo de dicionário de metadados, ou ambos.

A complexidade da implementação depende principalmente da complexidade que queremos abstrair. Quanto mais coisas quisermos abstrair, mais rico tem que ser o seu Query Object e mais inteligentes têm que ser os seus interpretadores.

Duas coisas que não podem faltar no Query Object : o critério de pesquisa e o alvo da pesquisa(ou seja, o tipo de objeto a retornar ). É fácil entender que os critérios de pesquisa podem ser conjugados com condições logicas(E e OU) para formarem critérios mais complexos. Se este tipo de construção genérica - que simula a condição where do SQL - for necessária, o padrão [.pattern][Composite Object] pode ajudar. Critérios simples podem ser implementados em classes especificas que implementam uma interface comum(por exemplo Criterion) e as operações logicas são composições desses objetos às quais é adicionado um operador logico.

class Criteria {

 private Class<?> targetClass;
 private LogicCriterion root = new LogicCriterion(LogicOperator.AND);

 public void add(Criterion r){
 	root.add(r);
 }
}


interface Criterion {}

class LogicCriterion implements Criterion {

 private List<Criterion> children = new LinkedList<>() ;
 private LogicOperator operator = LogicOperator.AND;

 public void add(Criterion r ){
	 children.add(r);
 }

}

class FieldCriterion implements Criterion {

 String fieldName;
 Object value;
 Operator operator;

}

enum Operator {

 EQUALS,
 GREATER_THEN,
 LESS_THEN,
 EQUAL_OR_LESS_THAN,
 EQUAL_OR_GREATER_THEN;

}

O resto da implementação são métodos utilitários para que a construção do critério seja o mais fluente possível. Um cuidado a ter com a implementação é não reverter o problema. Ou seja, inventar uma linguagem para escrever objetos de pesquisa que serão traduzidos para uma outra linguagem. (EQL e HQL são exemplos disto). Lembre-se que o objetivo do padrão é usar objetos como critérios. Não palavras. Se revertemos a implementação para usar textos como especificações estamos caindo no mesmo problema que originalmente queremos resolver.

Outras pessoas tentarão escrever Query Object e interpretadores para abstrair a nossa nova linguagem. Isto é um ciclo viciado que só é quebrado quando deixarmos o objeto de pesquisa como elemento principal. Implementar uma linguagem em cima de um Query Object é um anti-padrão. Cuidado com isso.

Discussão

Martin Flowler[1] define Query Object como uma especialização do padrão Interpreter [2] que constrói frases SQL de pesquisa com base numa estrutura de objetos. Exemplo de implementação deste conceito são os Criteria do Hibernate. Por este motivo este padrão é também chamado de Criteria ("Criteria" é o plural de "Criterion" que significa critério).

Limitar o Query Object a um Interpreter de SQL pode ser contra-producente já que tem potencialidade para muito mais.

Montar um Query Object é normalmente complexo e usar um Builder pode simplificar bastante esse trabalho para o programador, especialmente na sua vertente Chained Builder.

Ao usar o padrão Builder apresentam-se ainda mais hipóteses para o uso de Query Object já que o builder não tem necessariamente que ser montado via código. Pode ser populado via XML, por exemplo, ou qualquer outra forma de persistência de informação. Com isto poderemos retirar ao máximo a dependência entre o código e o critério de pesquisa. Contudo esta técnica resultará no anti-padrão discutido antes. Uma linguagem (XML) será usada para criar um Query Object que será usado para criar expressões numa outra linguagem. Em tese importar de um arquivo o critério pode ser útil, mas na prática, usar o compilador como verificador do correto uso do objeto é muito melhor. Sem falar de ferramentas de refractoring.

Enfim, use Builder com cuidado. Prefira uma interface fluente para o seu objeto de pesquisa.

Padrões Associados

Já vimos que o Query Object se relaciona com outros padrões como Interpreter e Composite Object . Ele pode ainda ser visto como uma especialização de Specification. O padrão Query Object é também especialmente útil em conjunção com implementações de Domain Store ou DAO (quando não queremos um método para cada consulta). Nestes casos o papel do interpretador é feito por esses outros objetos. Por exemplo, cada DAO pode usar os dados contidos no Query Object para produzir pesquisas conforme a sua tecnologia inerente.

Se a construção do critério é muito complexa ou tem muitas opções o padrão Builder poder ser útil, especialmente na sua variante Chained Builder.

Bibliografia

[1] Martin Fowler Patterns of Enterprise Application Architecture: Addison-Wesley Professional (2003)

[2] Ralph Johnson, Erich Gamma, John Vlissides, Richard Helm Design Patterns: Elements of Reusable Object-Oriented Software: Addison-Wesley (2005)

Scroll to Top