segunda-feira, 15 de setembro de 2008

Busca com Procedure Utilizando Castle Activerecord

Olá! Recentemente me deparei com um desafio interessante: criar um mecanismo de busca para todo um site (que possui diversos módulos, e cada um com suas tabelas no SGBD), sendo que este site havia sido desenvolvido utilizando o Castle Activerecord. Para fazer isso, encontrei 3 caminhos possíveis:
  1. Para cada módulo, desenvolver uma busca utilizando o Activerecord, depois criar uma classe que executa todas essas buscas e junta os resultados.
    • Esta solução tem 3 grandes problemas: esforço muito grande para criar essa busca, não é nada amigável para manutenção (em alguns casos teria que alterar o código da busca de cada módulo para implementar uma nova característica, e não permite utilizar plenamente características de busca do SGBD que eu estava utilizando (Full-Text Search do SQL Server 2005).
  2. Desenvolver uma query em HQL que busque em todos os módulos.
    • Esta solução é mais interessante que a anterior, ela resolve o problema do esforço e em parte também resolve o problema da manutenção. Somente em parte porque é difícil dar manutenção em queries HQL, não há breakpoints e os erros que o NHibernate gera são confusos. Quanto à possibilidade de utilizar o Full-Text Search, também não seria possível nesta solução.
  3. Desenvolver uma stored procedure de busca e utilizá-la através do NHibernate.
    • Esta foi a slução que escolhi, pois do ponto de vista da manutenção é mais simples pois procedures em T-SQL são fáceis de alterar e não é necessário muito código para utilizá-las através do NHibernate. O esforço é pequeno para o desenvolvimento (uma classe simples e uma procedure de complexidade média). Finalmente, esta solução permite que eu utilize quaisquer características do SGBD.

A solução que escolhi também tem problemas: utilizando ela estou "violando" a arquitetura proposta para o site, pois não utilizarei o ActiveRecord e criarei uma nova camada de lógica dentro do SGBD. Outro problema é que a solução não é independente de SGBD, se um dia o SQL Server for trocado por um Oracle, por exemplo, a procedure deverá ser reescrita. Porém, analisando os prós e contras de todas as alternativas, ela se mostrou a mais adequada para a situação.

Como desenvolver

Primeiramente, criei uma enumeração com todos os módulos nos quais seriam feitas as buscas:

Public Enum Modulo as Byte
Modulo1
Modulo2
Modulo3
End Enum
Depois, criei uma classe para guardar os resultados da busca:
Public Class Busca
Private _Titulo As String
Public Property Titulo() As String
Get
Return _Titulo
End Get
Set(ByVal value As String)
_Titulo = value
End Set
End Property
Private _ModuloRelacionado As Modulo
Public Property ModuloRelacionado() As Modulo
Get
Return _ModuloRelacionado
End Get
Set(ByVal value As Modulo)
_ModuloRelacionado = value
End Set
End Property
Private _ID As String
Public Property ID() As String
Get
Return _ID
End Get
Set(ByVal value As String)
_ID = value
End Set
End Property
End Class
Após isso, criei a procedure com a busca, esta procedure recebe um parâmetro chamado @Keywords e devolve uma tabela com os campos: ID, ModuloRelacionado e Titulo.
Depois de criar a procedure, adicionei um método na classe Busca que executa a procedure, devolvendo seu resultado como um vetor de Buscas:
Public Shared Function Buscar(ByVal KeyWords As String) As Busca()
Dim s As NHibernate.ISession = Castle.ActiveRecord.ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(GetType(Busca))
Dim q As NHibernate.IQuery = s.CreateSQLQuery("exec pr_Busca ?").AddScalar("Titulo", NHibernate.NHibernateUtil.String).AddScalar("ModuloRelacionado", NHibernate.NHibernateUtil.Byte).AddScalar("ID", NHibernate.NHibernateUtil.String).SetResultTransformer(New NHibernate.Transform.AliasToBeanResultTransformer(GetType(Busca)))
q = q.SetString(0, KeyWords)
Return DirectCast(q.List(Of Model.Busca)(), Generic.List(Of Model.Busca)).ToArray()
End Function

Neste método, primeiro inicio uma nova sessão do NHibernate.
Depois, crio uma nova Query em SQL (não HQL). Para cada atributo que meu conjunto de resultados possuir, executo o método AddScalar, passando o nome desse atributo e seu tipo.
O método SetResultTransformer informa o objeto que o NHibernate deverá utilizar para fazer a transformação dos resultados da forma relacional para objetos, neste caso utilizei o AliasToBeanResultTransformer, que para cada registro retornado cria um novo objeto e para cada atributo dos registros procura uma property no objeto criado com o mesmo nome do atributo e copia seu valor:

Após isso, insiro o valor do parâmetro da procedure e executo o método List, que executa a procedure e devolve o resultado como IList, o qual eu converto em um vetor utilizando o método ToArray().

Dessa forma, a solução ficou simples e atendeu a necessidade de encapsular a busca do site.

Abrassssss!

Nenhum comentário: