Seções iMasters
.NET + Agile + Analytics

Specification Pattern em .NET: vale a pena?

Desde quando começamos a desenvolver software com testes unitários e TDD (Test-Driven Development) aqui na Qualidata, evoluímos muito no modo como separamos as responsabilidades do sistema.

Técnicas de inversão de controle, frameworks de mock, AOP, persistence ignorance, foco no domínio, domain-driven design e muitos design patterns tem conduzido nosso modo de pensar e construir software.

Um pattern em particular que temos usados nos últimos projetos, e que tem me surpreendido por sua elegância e praticidade, é o Specification Pattern. Eric Evans e Martin Fowler apresentam esse pattern em um breve artigo de 19 páginas.

A ideia

No desenvolvimento orientado a objetos, consideramos que dados e comportamentos estão unidos nos objetos. Logo, parece fazer muito sentido chamarmos algo como aluno.EstaAptoParaMatricula(disciplina) para verificarmos se um determinado aluno está apto para ser matriculado em uma certa disciplina.

Entretanto, a proposta do Specification Pattern é desacoplar o critério de seleção de um objeto (quem está apto para matricular-se em uma certa disciplina) do objeto alvo (o aluno, no caso).

Muitas vezes, um critério desses, apesar de ser aplicado ao aluno, precisará envolver muitos outros objetos, tendo complexidade e relevância em termos de entendimento do domínio que justifica considerá-los objetos (especificações) à parte – uma classe como EspecificaoAlunoEstaAptoParaMatricula.

Além disso, a classe poderia ser combinada a outras especificações para formar especificações ainda mais complexas, utilizando: AND, OR, NOT, etc. Isso nos permite descrevê-las, desenvolvê-las e testá-las à parte do restante do sistema, nos fornecendo uma abordagem muito rica para lidar com situações nas quais precisamos responder quando um objeto atende ou não a um certo critério.

Performance

Em software, o mundo perfeito em termos de elegância nem sempre é viável para acompanhar a eficiência e a escalabilidade. No meu exemplo anterior, se quisermos listar todos os alunos aptos para se matricularem em certa disciplina, provavelmente seria inaceitável termos de carregar todos os alunos do sistema para perguntarmos um a um quem está apto para se matricular na disciplina.

Numa arquitetura CS, poderíamos ter uma consulta SQL, que retornaria de forma extremamente eficiente apenas os alunos que atendem a esse critério. O problema é que as regras de negócio estariam altamente acopladas e dependentes do banco de dados, o que é muito mais difícil de testar e de manter.

Porém, a estratégia que vou apresentar em .NET é ao mesmo tempo elegante e eficiente, já que ao final as especificações e suas composições irão gerar de forma transparente consultas SQL, que retornarão apenas os dados dos objetos que atendem à especificação – unindo o melhor dos dois mundos.

Uma “especificação” bem simples

Ok, admito que frequentemente eu sou o primeiro a criticar aqueles exemplos didáticos triviais, mas para começar vamos manter a coisa mais simples. Veja o código abaixo da entidade “Pessoa”:

Seguindo o exemplo, imagine que precisamos listar todas as mulheres disponíveis, ou seja, pessoas do sexo feminino que não são casadas nem estão em união estável. Ao invés de montarmos uma consulta, criamos uma especificação que indicará o que significa ser mulher disponível.

O teste abaixo mostra um exemplo de aplicação da especificação em um objeto pessoa.

Agora o próximo teste mostra a mesma verificação sendo aplicada sobre uma coleção de pessoas.

O que faz o método EhSatisfeita()?

Uma especificação é um objeto que, aplicado a outro (uma Pessoa, por exemplo), é capaz de responder se esse objeto a satisfaz ou não. Mas se observar, EhSatisfeita não retorna um bool, mas um Expression<Func<Pessoa, bool>>.

Em outras palavras, retorna uma expressão Linq (que é uma árvore de expressões, na verdade) capaz de gerar uma função que mapeia uma pessoa em um bool.

Quando chamamos Compile no primeiro teste, estávamos transformando a expressão Linq em uma função que mapeia Pessoa -> bool. Com isso, pudemos em seguida invocar a função, passando uma pessoa como parâmetro e esperando seu retorno true ou false.

Já no segundo caso de teste, aproveitando o fato de a lista implementar IQueryble, passamos a expressão Linq para o método extension Where(), que aplicou a especificação a cada item de lista, retornando apenas aqueles itens que atendem à especificação.

Mas como trabalhar com bancos de dados?

Aqui está a beleza da solução. Apesar de conseguir implementar e testar as especificações com objetos e coleções POCO (Plain-Old C# Objects), sem qualquer relação com bancos de dados, podemos na verdade aplicar a consulta a qualquer coleção IQueryble – o que inclui por exemplo um DbSet do Entity Framework.

Em outras palavras, poderia substituir a lista por um DbContext.Set<Pessoa>(), que representa a coleção de pessoas persistidas via EF4. Dessa forma, o EF4 irá receber uma expressão Linq relativa à especificação e convertê-la em uma consulta SQL equivalente.

Assim, podemos testar a especificação sem bancos de dados, mas ao final ela pode ser utilizada livremente para, por exemplo, compor critérios de consultas aos objetos persistidos no banco via repositório.

Funciona bem com NHibernate e EF4?

O primeiro projeto em que aplicamos o pattern começou com NHibernate. Aparentemente tudo funcionava muito bem. Precisamos obviamente do NHibernate.Linq, mas estava aparentemente tranquilo. À medida que começamos a construir especificações mais complexas, começamos a esbarrar em limitações do NHibernate que não implementava alguns tipos de join e group, e nem sequer gerava uma exceção quando isso acontecia.

Sempre que escrevíamos uma especificação mais complexa, ficávamos com aquela dúvida: será que NH vai mapear direito para SQL? E se não mapear, vou ter de refazer tudo tomando outro caminho? Para encurtar a história, abandonamos o NHibernate e adotamos o EF4 – que nesse quesito estava muito mais completo, gerando as SQLs adequadas para todo tipo de expressões Linq que experimentamos.

Apesar disso, como o mundo não é perfeito, esbarramos com outros problemas com o EF4, que não tínhamos no NH, mas essa história precisará ficar para outro artigo.

Não perca de vista a real motivação

Trabalhar com Specification Pattern não é uma questão de estilo de programação. Utilizamos esses e outros patterns visando principalmente a nos munir de ferramentas de abstração que facilitem o trabalho de lidar com a complexidade de domínio dos sistemas.

Se o domínio não possuir muitas regras complexas, não vejo grandes benefícios em utilizarmos especificações. Analisando o diagrama abaixo, que apresenta com mais detalhes a estrutura do pattern, notamos que muito mais do que transformar expressões em funções ou consultas SQL, ele busca resolver a composição de especificações visando a formar estas mais complexas.

Utilizando métodos como o Expression.AndAlso, por exemplo, podemos combinar expressões Linq retornadas pelo EhSatisfeita() de diferentes especificações, montando assim árvores de expressões mais complexas.

Isso nos permite implementar com facilidade o pattern apresentado no diagrama em C#.NET. Podemos também definir o comportamento dos operadores booleanos de conjunção, disjunção e negação, quando aplicados a especificações – o que nos permite combiná-las de forma mais legível.

Conclusão

Essa é sua última chance neste artigo para entender que esse esforço para aumentar nosso poder de abstração só se justifica quando temos domínios suficientemente complexos. Isso é apenas a minha opinião e não vou cansar de repetir isso.

Mas nesses casos os benefícios são tremendos e valem realmente a pena. Contudo, vemos com certa frequência pessoas utilizando diferentes patterns por mero modismo, sem avaliarem de fato sua relevância para o projeto em questão. Por tudo que foi dito, recomendo o Specification Pattern.

Além dos exemplos apresentados aqui, incluí uma implementação mais completa do pattern no ExemploPatternSpecification.zip. Aprecie com moderação!

Comente também

7 Comentários

Lucas Souza

Muito interessante.

Felipe Volpatto

Muito bacana.

João Vitor Paes de Barros do Car

Ótimo artigo!
Realmente utilizar Expression<Func> com POCO em DAL é a uma ótima forma de manter o código simples e desacoplado.

René Bizelli de Oliveira

Bacana!

Faça mais materias sobre Pattern ..

Alex Almeida

Boa!

Alexandre Rech

Muito legal o seu artigo, objetivo e claro.
Entretanto poderia comentar um pouco mais sobre problemas encontrado com o EF4?

Qual a sua opinião?