C# with Patterns: Specification

Muitas vezes precisamos abstrair características complexas, tentando simplificar uma consulta Linq para um campo que retorne apenas true ou false, pois isto é muito útil, principalmente quando usamos Entity Framework. O padrão de comportamento especificação busca tornar características, que muitas vezes complexas, em algo mais simples, legível, reusável e de fácil manutenção, com uma abordagem mais elegante.

Além de auxiliar na criação de consulta, Specification pode ser bastante útil em validações, onde uma entidade de domínio precise checar uma série de informações e retornar se ela satisfaz um cenário válido para alguma finalidade específica ou até mesmo persistir os dados no banco. Em resumo, Specification é a união de regras de negócios e expressões boolianas.

Projeto de exemplo

O projeto de exemplo mostra dois usos da abordagem: uma para definir entidades com características específicas e outra para validar entidades, que pode usado juntamente com notificações e eventos. Para isso crie uma solução chamada Specification, e adicione um projeto do tipo Class Library, chamado Specification.Domain.

A interface ISpecification<T> que define o contrato de implementação do método IsSatisfiedBy() e que recebe a especificação baseada em regras de negócio, que são geradas a partir dos métodos And(), AndNot(), Or(), OrNot() e Not() ou uma expresão Linq.

Crie uma pasta chamada Specifications no projeto de domínio, e dentro dela inclua a classe CompositeSpecification, que será a base para as especificações, implementando todos os métodos da interface anterior.

Com a classe base criada, podemos criar as classes de operadores para definir a regra de negócio, que irá retornar uma resposta true ou false. Para isso, adicione as classes a seguir na pasta Specifications:

No projeto de domínio, crie duas pastas chamada Entities e ValueObjects. E em ValueObjects inclua uma classe base para os objetos de valor chamada ValueObject e dentro de Entities inclua uma classe base para as entidades chamada Entity. Estas duas classes terão as propriedades e métodos básicos para as especificações de validação e uma função IsValid que retornará true se os dados preenchidos atenderem os requisitos.

Na pasta ValueObjects teremos uma classe de Email que herda de ValueObject, e na pasta de Entities as classes Category e Person e ambas herdam de Entity.

Neste ponto, a propriedade ValidSpecification ainda não possui suas especificaçãos que validam pessoa e o e-mail, e para isso temos que criar as classes com as regras de negócio.

Entre na pasta Specification, e dentro dela crie uma pasta chamada ValueObjects e neste diretório inclua uma classe chamada EmailValidSpecification que herda de CompositeSpecification.

Na pasta Entities de Specification, inclua uma classe chamada PersonNameValidSpecification, que irá validar o nome da pessoa.

Com as especificação de validação de e-mail e de nome prontas, podemos adicionar uma especificação que irá validar a pessoa como um todo, e para isso inclua uma classe chamada PersonValidSpecification na pasta Entities de Specifications.

Ainda temos um especificação para definir se uma pessoa é cliente, que pode ser usada em filtros de listas baseadas em Linq, portanto inclua o objeto PersonCustomerSpecification na pasta Entities de Specifications.

Projeto de Apresentação

Crie um projeto do tipo Class Library, chamado Specification.Domain, para que, via console, testarmos algumas formas de usar as especificações. E então, no método Main da classe Program, crie uma lista de pessoas através de uma variável do tipo List.

A seguir vamos listar apenas os clientes, usando a especificação PersonCustomerSpecification.

Neste outro exemplo, retornarmos todas as pessoas que são parceiras, usando uma expressão Linq:

A seguir é usada uma especificação que filtra apenas entidades válidas usando a especificação pronta, mas esta operação pode ser feita também chamando o método IsValid da própria entidade.

Toda a classe Program pode ser vista a seguir, com outros exemplos:

E aqui temos a saída completa do console.

Conclusão

Specification é um padrão bastante flexível, tornando as regras de negócio do domínio mais claras. Entretanto, as validações podem ser simplificadas, usando pacotes de terceiros, como por exemplo o Flunt, que une esta abordagem com a Domain Notification, e pode residir em uma camada transversal do projeto.

O projeto completo pode ser baixado no GitHub:

https://github.com/tiagopariz/Specification

Referências