terça-feira, 5 de outubro de 2010

Utilizando o operador like em consultas ao datastore do GAE

É uma tendência natural e inevitável quando migramos de tecnologia fazer comparações entre elas. Recentemente comecei um projeto utilizando a estrutura Cloud do Google e, consequentemente, utilizando um banco NoSQL, o BigTable.

Em projetos normais costumamos utilizar um banco de dados relacional, onde estamos acostumados a utilizar comandos SQL para efetuar consultas no nosso banco. Porém, quando migramos para uma plataforma cloud e bancos NoSQL, não temos mais uma base relacional e consequentemente nossa estrutura de armazenamento muda um pouco (ou muito!). Não vou me aprofundar neste assunto por haver muitas discussões interessantes pela internet.

Em bancos NoSQL temos sérias restrições a consultas que estamos acostumados a fazer em bancos relacionais. Agrupamentos como counts e sums, operadores like e alguns joins são bem restritos. Neste post irei focar no uso do operador like para consultas a campos String.

Bem, chega de papo e vamos ao que interessa. Nestes exemplos, farei analogias entre os bancos relacionais para facilitar o entendimento. Utilizarei também como framework de persistência da minha aplicação o Objectify (existem outros frameworks focados em bancos NoSQL, como o twig e o SimpleDS, mas ainda não os testei). Antes que perguntem, não, não é possível utilizar Hibernate. Além da diferença conceitual pelo fato do Hibernate ser um framework ORM, o próprio google disponibizou a sua lista de compatibilidade. Vale ressaltar que o GAE suporta JPA, portanto é possível fazer "algumas coisas" bem parecidas de como estamos acostumados. Entretanto já ouvi, li e concordo que o conceito destes bancos é diferente, portanto é um "workaround" utilizar JPA ou JDO com estes bancos, visto que algumas funcionalidades não são suportadas.

Na prática, vamos supor que você tem um requisito no qual seu cliente quer consultar uma cidade pelo nome. Precisamos então da nossa entidade Cidade:











Instantâneamente na sua cabeça lógica de desenvolvedor aparece a sentença:

"select nome, uf from Cidade where nome like 'paulo%'"

Certo? Num banco relacional sim. Porém, o foco aqui é o nosso BigTable. Análogamente utilizando o Objectify faríamos:









Ótimo. Porém o requisito diz que devemos buscar as cidades não importando a ordem das palavras. Ou seja, se o usuário digitar "paulo", a lista deve conter Paulo Jacinto, Paulo Afonso, São Paulo, Monsenhor Paulo, etc. Fácil. É só colocar mais um %, não é? Ficaria:

"select nome, uf from Cidade where nome like '%paulo%'"

#ihcomplicou. Uma das restrições no uso de bancos NoSQL que citei no início é justamente esta. A solução? Quem sabe indexar os termos de busca da sua entidade? Parece uma boa...

Precisamos então adicionar um novo atributo na nossa entidade Cidade. Criei então um List do tipo string chamado indexedName, como segue:



Agora, precisamos preencher esta lista de termos com os termos relevantes à pesquisa. No meu caso, criei um "IndexBuilder" responsável por elencar os termos pesquisáveis. Esta classe tem a função de "limpar" a string que estou passando à ela, removendo acentos, pontos, vírgulas e passando tudo pra lowercase (selects com lowercase e uppercase também não são possíveis no BigTable). Ele me retorna um List contendo cada palavra quebrada pelo " " (espaço, via split) entre uma palavra e outra do termo. Caso necessário, é perfeitamente possível componentizar este "builder" para tipos de indexadores específicos para o seu domínio. Basta interfacear este cara e invocá-lo por um container IoC ou uma factory qualquer. Use a imaginação!

Ótimo. Já temos o atributo de indexação de pesquisa e também o componente responsável por lidar com estes termos. Agora precisamos de algo que ligue os pontos. Para isto, o Objectify provê anotações para métodos das nossas entidades em 2 momentos. @PostLoad (logo após recuperar o objeto do datastore) e @PrePersist (antes de gravar no datastore). Utilizei a anotação @PrePersist no método onSave que irá chamar o nosso componente indexador e preencher o atributo indexedName da nossa entidade. Falei também para o Objectify não indexar os campos nome e uf (uma vez que não vou pesquisar por eles), e explicitamente disse que o indexedName é indexado (por padrão, todos os campos são indexados):



Feito isto, basta agora buscar a cidade pelo nosso atributo indexedName, ao invés do atributo nome. Algo como:



Desta forma o usuário terá a experiência de estar utilizando uma busca "full text", ou seja, o termo digitado será utilizado independente de maiúsculas, minúsculas, acento e formatação (de acordo com o seu componente de indexação). Caso o usuário busque por "paulo", o sistema retornará São Paulo, São Paulo da Fartura, Pedro Paulo Diniz, Padaria do Paulo, Seu Paulo da Esquina, etc...


O resultado é algo como: 




Até! :)


referências: http://groups.google.com/group/objectify-appengine/browse_thread/thread/be46b724c5176f61

Um comentário:

  1. Xenevreu, excelente post.

    Creio que o grande problema quando trocamos de tecnologia é tentar fazer com ela tudo que fazemos na "tecnologia antiga". Ou pior, tentar fazer da mesma forma. A JPA é excelente para bases relacionais, porém no GAE é uma grande gambiarra. O Objectify me parece muito bom para facilitar o uso do BigTable.

    Com um pouco de tempo, percebesse que os bancos NoSQL não são tão complexos assim. O que precisamos é mudar a forma como pensamos em persistência. Coisas como normalização, joins e agrupamentos não fazem parte do mundo NoSQL.

    Bem... fico esperando por mais novidades sobre o uso do GAE.

    Abraços,
    Marcelo Madeira

    ResponderExcluir