É muito comum termos que duplicar partes dos critérios feitos em consultas a banco de dados para atender regras de negócio. O Specification pattern tenta resolver esse problema agrupando esses critérios em classes, e usa uma composição de specifications para unir todos os critérios. QueryOverSpecification é uma pequena biblioteca implementando uma variação do padrão para uso com NHibernate QueryOver.
Specification Pattern
Basicamente o padrão é uma interface com um método que diz se a entidade satisfaz ou não um critério, ou seja, um predicado sobre um objeto.
O problema com essa abordagem é que a implementação pode causar problemas de performance se usado em um base de dados com uma grande quantidade de registros, já que para cada registro, deverá ser feita a chamada do método para verificação se o objeto satisfaz ou não a Specification.
Uma alternativa para .NET é o uso do LINQ, mas para projetos que preferivelmente seja melhor o uso do QueryOver, a biblioteca QueryOverSpecification dá o suporte necessário para o reuso das consultas.
Domain
Fazendo esse mesmo exemplo usando QueryOverSpecification, teríamos:
Veja que mudamos a forma como a Specification é usada. Criamos um delegate action sobre uma instância de QueryOver de Course, ou seja, o que eu devo adicionar ao QueryOver para efetuar o critério sobre o Course. Passamos essa action para a classe QueryOverSpecification, que cria uma specification usando um QueryOver.
Repository
Abaixo segue um exemplo de um método Find() genérico da classe Repository, que recebe uma ISpecification e retorna a lista de objetos que satisfazem ao critério:
*Esse método é somente um exemplo com nenhum tratamento de exception ou transaction.
QueryOverSpecificationVisitor é uma classe que recebe um QueryOver e adiciona todos os critérios necessários para efetuar a consulta, depois é só executar o método List() do QueryOver para ter a lista de objetos.
Service
Dentro de uma classe Service, teríamos a chamada do Repository:
Operadores
Criando outras Specifications, podemos uni-lás usando os operadores And e Or, e até passar parametros para o método By():
IIdAccessor
Uma restrição da biblioteca que é todas as suas entidades devem implementar a interface IIdAccessor. Ela descreve um Id único para aquela entidade.
Para maioria dos projetos, uma simples classe base abstrata para suas entidades já resolve essa restrição:
Atualmente, temos quatro tipos de formas de criar Specifications e três operadores para uní-las:
- QueryOverSpecification: usa um QueryOver para fazer os critérios e é preferível por padrão, como mostrado acima.
- LambdaSpecification: usa uma expressão para adicionar o critério.
- CriterionSpecification: usa a API Criterion do NHibernate para adicionar os criterios.
- NoRestrictionsSpecification: não adiciona nenhum critério. Útil para criação de Specifications condicionais.
Operador With
With é um operador adicional que inclui um critério a uma propriedade de uma entidade sem a necessidade de fazer um Join usando o QueryOver.
Exemplo:
Liste todos os 'cursos com votos' com o 'instrutor que tem o nome parecido com fulano'.
*Atualmente, o operador With não tem suporte a propriedades IEnumerable.
Conclusão
QueryOverSpecification é uma simples biblioteca para construir classes com critérios para consultas em base de dados usando NHibernate de forma a aumentar o reuso e diminuir a complexidade das consultas sem problemas de performance.
As interfaces criadas como ICourseWithVotes, são conceitos e regras vindos dos requisitos e dos casos de uso e que podem conviver sem dependências com frameworks dentro da camada de modelo (domain), deixando a implementação das specifications separada do modelo, preferivelmente em outro namespace e/ou assembly.
Referências
Source no codeplex: http://queryoverspec.codeplex.com/SourceControl/BrowseLatest
Nuget: http://nuget.org/packages/Naskar.QueryOverSpec
Specification Pattern
Basicamente o padrão é uma interface com um método que diz se a entidade satisfaz ou não um critério, ou seja, um predicado sobre um objeto.
public interface ISpecification { bool IsSatisfiedBy(object candidate); } public class CourseWithVotes : ISpecification { public bool IsSatisfiedBy(Course course) { return course.Votes.Count > 0; } }
O problema com essa abordagem é que a implementação pode causar problemas de performance se usado em um base de dados com uma grande quantidade de registros, já que para cada registro, deverá ser feita a chamada do método para verificação se o objeto satisfaz ou não a Specification.
Uma alternativa para .NET é o uso do LINQ, mas para projetos que preferivelmente seja melhor o uso do QueryOver, a biblioteca QueryOverSpecification dá o suporte necessário para o reuso das consultas.
Domain
Fazendo esse mesmo exemplo usando QueryOverSpecification, teríamos:
public interface ICourseWithVotes { ISpecification<Course> By(); }
public class CourseWithVotes : ICourseWithVotes { public ISpecification<Course> By() { Action<IQueryOver<Course, Course>> action = x => x.JoinQueryOver(y => y.Votes); return new QueryOverSpecification<Course>(action); } }
Veja que mudamos a forma como a Specification é usada. Criamos um delegate action sobre uma instância de QueryOver de Course, ou seja, o que eu devo adicionar ao QueryOver para efetuar o critério sobre o Course. Passamos essa action para a classe QueryOverSpecification, que cria uma specification usando um QueryOver.
Repository
Abaixo segue um exemplo de um método Find() genérico da classe Repository, que recebe uma ISpecification e retorna a lista de objetos que satisfazem ao critério:
public IList<TEntity> Find<TEntity>(ISpecification<TEntity> spec) where TEntity : class, IIdAccessor { var session = Factory.OpenSession(); var query = session.QueryOver<TEntity>(); var visitor = new QueryOverSpecificationVisitor<TEntity>(query); spec.Accept(visitor); var list = query.List(); session.Close(); return list; }
*Esse método é somente um exemplo com nenhum tratamento de exception ou transaction.
QueryOverSpecificationVisitor é uma classe que recebe um QueryOver e adiciona todos os critérios necessários para efetuar a consulta, depois é só executar o método List() do QueryOver para ter a lista de objetos.
Service
Dentro de uma classe Service, teríamos a chamada do Repository:
var list = _repository.Find(courseWithVotes.By());
Operadores
Criando outras Specifications, podemos uni-lás usando os operadores And e Or, e até passar parametros para o método By():
var list = _repository.Find( courseWithVotes.By() .And(courseByExample.By(new Course() { Name = "C#" })));
IIdAccessor
Uma restrição da biblioteca que é todas as suas entidades devem implementar a interface IIdAccessor. Ela descreve um Id único para aquela entidade.
namespace Naskar.QueryOverSpec { public interface IIdAccessor { long? Id { get; set; } } }
Para maioria dos projetos, uma simples classe base abstrata para suas entidades já resolve essa restrição:
public abstract class Entity : IIdAccessor { public virtual long? Id { get; set; } }
public class Course : Entity { public Course() { this.Votes = new List<Vote>(); } public virtual string Name { get; set; } public virtual Instructor Instructor { get; set; } public virtual IList<Vote> Votes { get; set; } }
Atualmente, temos quatro tipos de formas de criar Specifications e três operadores para uní-las:
- QueryOverSpecification: usa um QueryOver para fazer os critérios e é preferível por padrão, como mostrado acima.
- LambdaSpecification: usa uma expressão para adicionar o critério.
new LambdaSpecification<Course>(x => x.Name.IsInsensitiveLike("ava", MatchMode.Anywhere))
- CriterionSpecification: usa a API Criterion do NHibernate para adicionar os criterios.
new CriterionSpecification<Instructor>(Property.ForName("Name").Like("ciclano", MatchMode.Anywhere))
- NoRestrictionsSpecification: não adiciona nenhum critério. Útil para criação de Specifications condicionais.
Operador With
With é um operador adicional que inclui um critério a uma propriedade de uma entidade sem a necessidade de fazer um Join usando o QueryOver.
Exemplo:
var list = _repository.Find( courseWithVotes.By().With(x => x.Instructor, instructorByExample.By(new Instructor() { Name = "fulano" })))
Liste todos os 'cursos com votos' com o 'instrutor que tem o nome parecido com fulano'.
*Atualmente, o operador With não tem suporte a propriedades IEnumerable.
Conclusão
QueryOverSpecification é uma simples biblioteca para construir classes com critérios para consultas em base de dados usando NHibernate de forma a aumentar o reuso e diminuir a complexidade das consultas sem problemas de performance.
As interfaces criadas como ICourseWithVotes, são conceitos e regras vindos dos requisitos e dos casos de uso e que podem conviver sem dependências com frameworks dentro da camada de modelo (domain), deixando a implementação das specifications separada do modelo, preferivelmente em outro namespace e/ou assembly.
Referências
Source no codeplex: http://queryoverspec.codeplex.com/SourceControl/BrowseLatest
Nuget: http://nuget.org/packages/Naskar.QueryOverSpec