Query Object

Query Object

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. Martin Flowler[1] define Query Object como uma especialização do padrão Interpreter [2] que constroi 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).

Na minha opinião limitar o Query Object a um Interpreter de SQL é contra-producente. Podemos usar este padrão para muito mais coisas. A definição apresentada aqui é um pouco mais genérica que a apresentada por Fowler.

O problema

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 parametros 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.

A solução

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

Como funciona

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 propria resulta de uma pesquisa

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

Conforme a complexidade das operações necessárias o objeto de pesquisa tem que ser mais, ou menos, inteligente. Vários objetos podem ser criados conforme necessário. Isso representaria um mapeamento entre um objeto de pesquisa e um comando de pesquisa. Ou, um só objeto pode ser criado com a sua estrutura dependente de um conjunto de metadados. Neste caso além do interpretador é necessário um dicionário de metadados onde o objeto de pesquisa ( e até o proprio interprestador) possam consultar informações. Esta capacidade generica do Query Objet é a mais atraente mas também a mais complexa.

Implementação

Idealmente o Query Objet é auto-suficiente para representar os parametros de uma pesquisa num conjunto de dados mas na prática a implementação de um Query Objet pode depender do interpretador associado ou do mecanismo de dicionário de metadados. A complexidade da implementação depende principalmente da complexidade que você quer abstrair. Quanto mais coisas quiser abstrair, mais rico tem que ser o seu Query Objet e mais inteligentes têm que ser os seus interpretadores.

Duas coisas que não podem faltar no Query Object são o critério de pesquisa e o alvo da pesquisa ( ou seja, o tipo de objeto a retornar ). É facil entender que os critérios de pesquisa podem ser conjugados com condições logicas ( and e or) 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 Composite pode ajudar. Critérios simples podem ser implementados em classes especificas que implementam uma interface comun ( por exemplo Criterion) e as operações logicas são composições desses objetos às quais é adicionado um operador logico.

01
02 class Criteria {
03
04 Class targetClass;
05 LogicCriterion root = new LogicCriterion ( LogicOperator.AND ) ;
06
07 public void add ( Criterion r ){
08 root.add ( r ) ;
09 }
10 }
11
12 interface Criterion {}
13
14 class LogicCriterion implements Criterion {
15
16 List children = new LinkedList () ;
17 LogicOperator operator = LogicOperator.AND;
18
19 public void add ( Criterion r ){
20 children.add ( r ) ;
21 }
22
23 }
24
25 class FieldCriterion implements Criterion {
26
27 String fieldName;
28 Object value;
29 Operator operator;
30 }
31
32 Enum Operator {
33
34 EQUALS,
35 GREATER_THEN,
36 LESS_THEN,
37 EQUAL_OR_LESS_THAN,
38 EQUAL_OR_GREATER_THEN;
39
40 }

Código 1:

O resto da implementação são métodos utilitários para que a construção do critério seja o mais fluente possivel. 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. Se revertemos isso 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.

Relação com outros padrões

Já vimos que o Query Object se relaciona com outros padrões como Interpreter ou Composite. Ele pode ainda ser visto como uma especialização de Specification.

O padrão Query Object é especialmente útil em 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 util. Ao usar um Builder apresentam-se ainda mais hipoteses para o uso de Query Object já que o builder não tem necessáriamente que ser populado via codigo. Pode ser populado via XML, por exemplo, ou qualquer outra forma de persistencia de informação. Com isto poderemos retirar ao máximo a dependencia entre o codigo e o critério de pesquisa. Contudo esta tecnica resultará no anti-padrão descutido 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 criterio pode ser util, 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. Perfira uma interface fluente para o seu objeto de pesquisa.

Referências

[1] Patterns of Entreprise Application Architecture
Martin Fowler et al.
URL: http://www.martinfowler.com
[2] Design Patterns: Elements of Reusable Object-Oriented Software
Gamma et al.
URL:

Licença

Creative Commons License Sérgio Taborda
Este trabalho é licenciado sob a
Licença Creative Commons Atribuição-Uso Não-Comercial-Não a obras derivadas 3.0 Genérica .

24 opiniões sobre “Query Object”

  1. Opa sergio, tudo bom, eu fiz algo parecido com a proposta desse padrão, mas não esta totalmente legal, e gostaria de uma openião sua ^^

    meu objeto de pesquisa é montado mais ou menos assim

    public static Pais findPais(String name) {
    Query<Pais> query = new PaisWhere()._nome()._equals(name)._build();
    return Repositories.get(query);
    }

    public static CatalogoDePaises findAllByNameLike(String name) {
    Query<Pais> query = new PaisWhere()._nome()._like(name)._build();
    return new CatalogoDePaises(Repositories.list(query));
    }

    esse meu objeto PaisWhere eu to montando 1, para cada Entidade minha, nele eu so informo os campos que utilizo (“nome” e “id” por exemplo) ai na hora q faço new PaisWhere() as unicas opções que tenho são _nome() e _id() …

    quando escolho _nome() as opções são _equals(),_like(),_notLike(),_startLike(), etc etc etc

    quando escolho _id() as opções são outras já que ID é um número _equals(), _greater(), _less() etc etc ….

    depois de escolher o operador as opções são _and(), _or() e _build(), este ultimo monta o objeto de pesquisa, os dois primeiros retornam para o inicio, mostrando os campos _nome() e _id()

    eu tenho 1 classe abstrata chamada Where tenho que extender ela, e so informar os campos e seus da minha entidade, e foi assim que montei o PaisWhere ….

    gostaria de saber se esta muito longe do padrão e o q posso fazer para melhorar…. vlw ^^

  2. o modo que crio um objeto (Where) como o PaisWhere é o seguine


    public class PaisWhere extends Where<Pais,PaisWhere> {

       public PaisWhere() {
          super(Pais.class);
       }
       
       public QueryStringArgument<Pais, PaisWhere> _nome() {
          return createStringColumn("nome");
       }
       public QueryNumericArgument<Pais, PaisWhere> _id() {
          return createNumberColumn("id");
       }
    }

  3. Vc não está longe do padrão. Acho que o seu problema é com a implementação. A sua implementação não é flexivel ou abstrata o suficiente de forma que um objecto query possa ser criado para qualquer entidade de qualquer tipo e com qualquer modelo.
    Dê uma olha como eu implementei para o middleHeaven
    http://code.google.com/p/middleheaven/source/browse/
    Procure no pacote src/main/java/org/middleheaven/storage/criteria O QueryObject do MiddleHeaven se chama Criteria ( que significa critérios , e é o plural de Criterion : critério)

    A dieia básica é usar o padrão composite para criar um arvore logica com o filtro para cada campo

  4. Dei uma lida, mas confesso que não absorvi … =/
    c vc puder mostrar um trecho de código de uma pesquisa, e se possivel mostrar como ela funciona dentro do seu criteria eu agredeceria ^^

    vlw de toda forma, vou tentar ler mais, pra ver c descobru como funciona o tal criteria

    1. O criteria é criado usando um criteriaBuilder para facilitar. Criteria é uma interface , várias implementações são possiveis. O builder cria um BuildedCriteria que implementa a interface.

      Um exemplo seria assim :

      Para a classe Estado que contem os campos nome do tipo string e pais do tipo Pais que é uma outra entidade com o campo nome a pesquisa por estados do brasil que começam com a letra M e contém a letra “A” seria

      Criteria criteria = CriteriaBuilder.searh(Estado.class) // o que vou encontrar
      .and(“nome”).startsWith(“M”)
      .and(“nome”).contains(“A”)
      .all() // encontra todos que casam com os parametros

  5. Só uma duvida ai nesse seu exemplo…

    Pelo seu código da pra ver:

    o que vc vai encontrar: Estado.class
    como vc vai encontrar: inicia com M e tem A

    só que faltou o onde ?? na hora que vc da o .all() ele busca isso onde ?

    1. O CriteriaBuilder apenas constroi o critério. O critério, tal como um frase SQL não contêm o resultado, ele contém a informação para encontrar o resultado.
      O Criteria é passado a um objeto do tipo DataStorage para obter um objeto Query ( o resultado da pesquisa) É este objeto Query que contém os resultados da pesquisa. Ele tem métodos para obter os objetos resultado tal como list() e para saber se a pesquisa é vazia – isEmpty() – ou quantos elementos tem – count ().

      O principio de separação de responsabilidade não permite que o Criteria tenha acesso ao sistema de preservações dos dados e não permite ao sistema de preservação definir as pesquisas. Com dois objetos , cada um fazendo o seu papel, temos um sistema mais desacoplado e simples.

      O DataStorage tb é apenas um contrato de interface, várias implementações existem, em especial uma que guarda tudo apenas em memoria, uma que interage com banco de dados via JDBC e uma outra que iterage com arquivos XML. As possibilidades são ilimitadas e o objeto de Criteria é criado sempre da mesma forma e independentemente do DataStorage de onde os dados serão lidos.

    1. O Repository conversa com o DataStorage para obter os dados das pesquisas, mas é o DataStorage que procura os dados e os carrega.
      Básicamente o Repository diz o que é para trazer e o DataStorage sabe onde os dados estão e como os trazer de lá.

      Um Repository está associado a um DataStorage.

  6. Hmm… então o Repository ta em uma camada acima do DataStorage ??
    tipo, quando vai c fazer uma pesquisa, eu crio um criteria através do builder … envio o criterio de pesquisa pra um repositorio, que manda pro datastorage que encontra no banco e devolve os dados ?? seria isso ?

    claro estou supondo que o datastorage é de um banco de dados.

    1. É quase isso. O Criteria é construido pelo Repositorio e enviado ao DataStorage. O CriteriaBuilder é apenas uma forma facil – para o programador- de escrever os critérios. Os dados são retornados pelo DataStorage, tratados pelo repositorio, se necessário, e enviados a quem chamou o repositorio. A função do repositorio é encapsular a criações dos critérios de pesquisa.

  7. Opa sergio… eu tava dando uma olhada no seu framework, Tendando entender os criteria dele, mais confesso que não fui bem sucedido, andei lendo algumas coisas nele, e pelo que vi, vc esta tentando fazer um framework, com todas as funcionalidades possiveis, e sem nenhuma especialização, é isso ?? onde teoricamente o framework atenderia a tudo, porem sem usar nada de outros framework ?

    ele tem muitos pacotes e é tudo muito divido, confesso que me perco ali =/ …. esta pensando em usar a parte de critria do framework, mas logo vi, que ele é todo intrelaçado, e não da pra usar uma parte, sem portar o todo, estou engado ?? o framework é divido em pequenas partes idependete ?? ou ele é todo dependete ?

    1. Tomaz,

      O MiddleHeaven ainda está em fase de construção (versão alfa). Ele é construido como uma série de macro-pacotes chamados toolboxes que são independentes uns dos outros, mas ao nivel do codigo ainda ha uma certa dependencia – aliás isso é um dos motivos para que ainda esteja na fase alfa. Ainda não ha um arquivo para baixar, mas vc pode puxar tudo o SVN (instruções em http://code.google.com/p/middleheaven/source/checkout ) e construir o seu proprio jar. O maven 2 é usado, portanto não ter muito problemas.

      O pacote que lhe interessa é o de storage.

      Para funcionar o MiddleHeaven usa o conceito de bootstrap ( que é outra parte que ainda nã oestá completa ) e injeção de dependencias isso pode ser um pouco complexo de setar a frio.

  8. Sergio estou dando uma olhada nas suas classes novamente… acho que um pouco do seu conceito chegou….

    acredito que estou vislumbrando um pouco melhor suas ideias… gostaria de pedir suas opiniões valorosas aqui neste post

    http://www.guj.com.br/posts/list/116251.java

    outra coisa…. existe algum link de JavaDoc do seu framework ?? estou buscando as 1° classes da sua implementação de Criteria, mais não sei bem onde o código começa… ai fico um pouco perdido

    vlw obrigado a atenção…

    1. O JavaDoc pode ser encontrado em http://middleheaven.sourceforge.net/apidocs/index.html Lembrar que está em esta alfa ( ou seja, tem poucas informações)

      Para usar Criteria vc precisa usar o CriteriaBuilder a não ser que queira implementar a interface Criteria vc mesmo. O CriteriaBuilder tem uma interface fluente que o ajudará a construir suas queries.

      Quanto às dependencias sim são precisos outros pacotes. Recomendo que baixe todo o codigo e produza um jar dele. Como já disse o projeto está em estado alfa e o problema das dependencias ainda não está mapeado ( se bem que é uma preocupação minha desde o inicio é difcil manter registro do que depende do quê )

  9. 1 duvida, qual classes eu preciso pra conseguir usar o Criteria do seu framework ?? so o storage pacote basta ?? ou existe + dependecias ?

  10. Pro caso do meu post, que colei aqui, quanto a performance, vc sabe dizer c é melhor realizar nova pesquisa no banco de dados com os novos parametros ?? ou c é melhor filtrar as coleções ?

    1. Isso devia ser descutido no post, mas em geral vc deve refazer a pesquisa. Não é uma questão de velocidade é uma questão de que podem haver mais registros que entram no critério que foram colocados por outro usuário ( isso em sistemas distribuidos. sistemas web são distribuidos). Se o sistema não é distribuido e vc tem a certeza que nenhum outro processo mudou os dados, então pode fazer direto sem refazer a pesquisa.

  11. Eu sei que o post é antigo, mas você tem um exemplo testável disso, que rode dentro de um Main? Em [http://www.javabuilding.com/search/architecture/arquitetura-com-domainstore-repositorio-e-query-object.html] você tem um exemplo onde usa DomainStore, QueryObject e Repository, mas não tem como é a implementação da interface DomainStore, seria a classe DomainStoreImpl onde eu passaria a Session do Hibernate? Não vejo a diferença com ter uma interface Dao e uma classe DaoImpl. E a classe QueryResult como ela é? Lá você também não tem a implementação da classe Criteria, dai eu vim aqui, mas ai compreendi menos ainda, por que Criterion é um interface vazia? LogicOperator é um enum com AND e OR? O que entendi mais ou menos foi: Na classe Criteria eu tenho a classe que será alvo da pesquisa, mas eu não entendi o LogicCriterion root = new LogicCriterion ( LogicOperator.AND ) e o que o metodo da classe faz. Nao entendi o que a classe LogicCriterion faz. A classe FieldCriterion pelo que entendi eu tenho o campo de pesquisa, o valor e operador.

    1. As implementacoes nao estao la porque ha varias opcoes. para ter uma ideia pratica de como seria lembre que os criteria do hibernate sao query objects e o proprio hibernate implementa o padrao domain store. isso lhe da uma ideia de como pode ser complexo implementar um domainstore.

      1. Eu normalmente faço assim com Hibernate:
        interface Dao{
        save(); delete(); findById(); findAll();
        }
        class HibernateDao implements Dao{
        save(entidade){
        session.saveOrUpate(entidade);
        }
        }
        interface ProdutoDao extends Dao{
        findByAlgumaCoisa(); findByNome(nome);
        }
        class ProdutoHibernateDao extends HibernateDao implements ProdutoDao{
        findByNome(nome) {
        Criteria crit = session.createCriteria(Produto.class);
        crit.add(Restrictions.eq(“nome”, nome));
        List results = crit.list();
        }
        }
        Foi isso que aprendi na faculdade. Pelo que li sobre DomainStore, Repository e QueryObject a implementação seria mais ou menos assim?
        interface DomainStore(){
        save(); delete(); executeQuery();
        }
        class DomainStoreHibernate implements DomainStore{}
        class ProdutoRepository{
        DomainStore store;
        add(){
        store.save();
        }
        remove(){
        store.delete();
        }
        findByNome(nome){
        store.executeQuery(QueryObject);
        }
        }
        Nos métodos de pesquisa do repositório eu montaria uma QueryObject com o criterio de busca. Esse QueryObject será passado para o método executeQuery() da interface DomainStore e na classe onde esse metodo é implementado esse QueryObject seria desmontado e adequado para a forma de busca o que eu esteja usando como persistencia.

      2. Sim. isso seria o repositorio (nao o dao) acessando o domain store que é o hibernate usando o criteria como query object

Deixe um comentário