Dica: Assine o RSS feed ou e-mail para esta página Wiki para obter notificação automática quando ela for atualizada! Table of Contents IntroduçãoCriando um serviço para o CustomerValidação na camada de serviçosRemovendo metadata desnecessário do modeloMapeando das entidadesCriando um DataContextInitializer e re-gerando o bancoCriando um dicionário de validaçõesModificando o CustomerServiceConsumindo o serviço criadoConsideraçõesDownloadArtigos Relacionados
Neste artigo, falarei sobre como desacoplar a interface do repositório, tendo como objetivo, deixar todo o fluxo da aplicação em uma camada separada.
Para exemplificar esta necessidade, imagine o cadastro de um novo Cliente, onde temos o seguinte fluxo:
Se este fluxo ficasse no controller do nosso projeto Web, teríamos um problema, pois caso outra interface seja criada, este fluxo também deve ser seguido, e teria que ser duplicado nesta nova interface.
Separando este fluxo em uma outra camada, as interfaces apenas fariam uso dela, que por sua vez tomaria conta deste fluxo, tornando ainda mais transparente para a nossa interface, o cadastro de um cliente por exemplo.
Para facilitar a criação dos serviços, criaremos uma interface base, onde todas as entidades que possuírem métodos CRUD poderão herdar.
Podemos também ter um serviço genérico, mas cada serviço terá um fluxo diferente neste caso, então implementaremos todos.
As assinaturas dos métodos criadas foram reaproveitadas da interface genérica do repositório, mas poderiam ser outros nomes.
AnotherMvcStore.Service\Interfaces\IBaseService.cs
Deste modo, caso a entidade possua apenas os métodos CRUD, implementaremos esta interface.
Posteriormente, precisamos criar a interface para o CustomerService, que neste caso herdará da interface base criada.
AnotherMvcStore.Service\Interfaces\ICustomerService.cs
A implementação da interface fica da seguinte forma.
Note que agora controlamos a transação dentro do método Add, Delete, Edit, pois delegamos esta função ao serviço, e não ao controller.
Deste modo, removemos as responsabilidades de fluxo do controller, tornando-o mais limpo e leve, como deve ser.
Um outro exemplo de fluxo seria este abaixo, onde antes de incluir um cliente, verificamos se o mesmo já existe.
Até agora, vimos como decorar as classes com os atributos do Data Annotations, mas existem algumas validações que não podem ser feitas lá.
Validações de fluxo, como pode exemplo, a submissão de um pedido somente se o cliente conter um histórico positivo, ficaria complicado através dos Data Annotations, pois exige conectar ao banco e verificar o status do cliente por exemplo. Por estas razões, existem as validações na camada de serviço, onde o fluxo é executado.
Também precisamos pensar no princípio DRY (Don’t repeat yourself) e no SPOF (Single Point of Failure), e o que estes dois princípios empregam neste caso é não repetir as validações, e concentrar as validações somente em um lugar (Serviço ou Domínio), de forma que se algo falhar nas validações, sabemos onde elas estão.
Embora não seja um “pecado” deixar parte destas validações no domínio e parte na camada de serviços, não queremos ferir nossos princípios, certo?
Além disso, apesar deste cenário ser composto de uma interface que aceita os Data Annotations facilmente, podemos ter outras interfaces que não aceitem-os tão amigavelmente.
No ASP.NET Web Forms por exemplo, temos um cenário um pouco diferente, explicado neste artigo: http://www.asp.net/web-forms/tutorials/getting-started-with-ef/the-entity-framework-and-aspnet-getting-started-part-8
Outro ponto que devemos nos atentar é que o ASP.NET MVC por exemplo, possui o ModelState, que recupera informações (Chave/Valor) do erro e o controle que ele pertence. Neste ponto, não podemos utilizar o ModelState para armazenar os erros, pois o mesmo serviria apenas para o ASP.NET MVC, ou irá requerer o uso do namespace System.Web.Mvc em aplicações ASP.NET Web Forms.
O que queremos é que qualquer interface possa implementar esta camada de serviços.
O mapeamento do EntityFramework pode ser feito tanto utilizando DataAnnotations quanto no evento OnModelCreating do Data Context (DefaultDataContext.cs neste caso).
A vantagem de deixar os DataAnnotations no modelo, é que ele se encarrega tanto das regras de mapeamento e criação do banco quanto das validações, mas como estamos movendo as validações para camada de serviço, vamos também remover o metadata para e deixar o mapeamento para o DataContext.
Remover o metadata também não significa remover todos os DataAnnotations, pois temos por exemplo o atributo Display, que ainda é importante e será mantido.
AnotherMvcStore.Domain\Entities\Customer.cs
Obs:. As demais classes seguiram este mesmo exemplo.
Ter o banco de dados como um repositório de dados não significa esquecê-lo, e o EntityFramework permite-nos configurar desde tamanho/tipo do campo, até chaves primárias e estrangeiras, e este mapeamento também é importante.
Quando herdamos do DbContext, temos a possibilidade de sobrescrever o método OnModelCreating, que será responsável por aplicar estas configurações ao banco de dados na criação dele.
Na imagem abaixo, podemos notar que informamos a entidade, propriedade e por fim podemos aplicar as restrições a ela, como definir se a mesma é requerida ou seu tamanho. Estas informações refletem apenas na criação do banco de dados.
AnotherMvcStore.Data\DataContexts\DefaultDataContext.cs
Obs:. Aplicar esta ação para as demais entidades.
Existem vários atributos que podemos aplicar para controlar a criação do banco de dados, estes foram apenas como exemplo.
Quando utilizamos um ORM, devemos manter o banco de dados atualizado em relação ao modelo, e isto pode ser feito de duas formas.
Manualmente, a cada alteração no domínio, conectarmos ao banco de dados e refletir a informação via CREATE, ALTER TABLE ou automaticamente, criando um DataContextInitializer, onde definiremos que o EntityFramework será responsável por esta atualização.
Obs:. Após implementar este modelo automático, a cada modificação no domínio, o EF recriará o banco de dados todo, não somente o que foi mudado. Como estamos em ambiente de desenvolvimento, vamos deixar esta opção habilitada para poupar tempo, mas é necessário uma atenção redobrada para não enviar estas instruções para o ambiente de produção.
Desta forma, criaremos um novo arquivo, chamado DefaultDataContextInitializer, que fará a função de determinar se o banco será recriado a cada vez que a aplicação rodar, ou somente quando o domínio for diferente do banco de dados.
AnotherMvcStore.Data\DataContexts\DefaultDataContextInitializer.cs
Posteriormente, precisamos definir no arquivo Global.asax1 que temos um inicializador para o DataContext, e que antes de criar o banco de dados ele deve se informar sobre como e quando o mesmo será gerado.
Sendo assim, no método Application_Start do arquivo Global.asax (AnotherMvcStore.Web), utilizamos a seguinte definição para a implementação do inicializador.
AnotherMvcStore.Web\Global.asax
Por fim, iremos acertar a connection string, onde não havia modificado ainda e cujo DataContext depende para saber o servidor/banco de dados a se conectar. (Necessita permissões de master – criação)
O nome da connection string também é fundamental, e pode ser utilizado de duas formas, sendo a primeira onde o nome tem o mesmo nome do Data Context (Nesse caso DefaultDataContext) e outra onde no data context informamos o nome da Connection String no construtor, como realizamos neste artigo.
Deste modo, a connection string fica desta forma:
AnotherMvcStore.Web\Web.Config
1 – É necessário referenciar o projeto AnotherMvcStore.Data e o namespace System.Data.Entity para completar a ação do Database.SetInitializer.
Com os ajustes finalizados, vamos partir então para a validação na camada de serviços.
Sabendo que tanto o ASP.NET MVC quanto o ASP.NET Web Forms utilizam chave/valor para destinar os erros na interface, criaremos um dicionário que servirá para ambos, e que neste caso chamei de IValidationDictionary.
O ASP.NET MVC dispõe de um recurso chamado ModelState, que consegue armazenar os erros gerados pelos modelos e facilmente repassá-los para interface, porém, utilizar ModelState aqui criaria um vinculo muito forte com o ASP.NET MVC, e a ideia é que este modelo possa ser utilizado também por aplicações ASP.NET WebForms.
Para melhor organizar a aplicação, criei uma pasta no projeto AnotherMvcStore.Service chamada Validation, onde criaremos a seguinte interface.
AnotherMvcStore.Service\Validation\IValidationDictionary.cs
Agora vamos implementar este dicionário na entidade de serviço do cliente, juntamente com o método IsValid, que fará as validações necessárias. Note que temos duas assinaturas na interface, AddError e IsValid.
Desta forma, adicionaremos este dicionário ao CustomerService, e estenderemos os construtores, onde um receberá o dicionário e utilizará o DataContext padrão e o outro receberá o dicionário e tem a possibilidade de receber também um DataContext.
O dicionário é obrigatório e será preenchido com os erros encontrados e posteriormente enviado a interface, que por sua vez converterá ele para um ModelStateDictionary (No caso do ASP.NET MVC) e repassará para a View, exibindo os erros aos usuários.
O fato de podermos informar um DataContext deve-se que na fase de testes, podemos ter um repositório falso (FakeRepository) para realizar testes independentes de banco de dados.
AnotherMvcStore.Service\CustomerService.cs
As validações agora são feitas manualmente e ficam no método IsValid. Toda vez que precisar validar uma entidade (Ao salvar ou ao editar por exemplo), este método é chamado e realiza as validações.
AnotherMvcStore.Service\CustomerService.cs (Método IsValid)
Para finalizar, vamos modificar a classe CustomerService, para que se adeque a utilização destas novas validações, e também retorne algo mais consistente para a interface.
Para começar, no arquivo IBaseService.cs, alteramos os métodos Add e Edit para retornarem um booleano ao invés de vazio. Este retorno depende muito de como cada um gosta de trabalhar.
Desta forma, a implementação da interface no CustomerService.cs também deve ser alterada, e agora como usufruímos do método IsValid, vamos aproveitar e modificar os métodos Add e Edit para retornarem um booleano também.
Note que a implementação do método em si é simples, bastando informar a entidade a ele, e deixar ele realizar as outras validações.
Por fim, precisamos modificar o CustomerController para que o mesmo se adeque a utilização do CustomerService ao invés do CustomerRepository.
Como comentei anteriormente, tanto o ASP.NET Web Forms como o ASP.NET MVC tem seu dicionário de erros, e no caso do ASP.NET MVC este dicionário se chama ModelStateDictionary.
Apesar do ModelStateDictionary ser um dicionário chave/valor, como o IValidationDicionary que criamos acima, precisamos fazer um “cast” do IValidationDictionary para o ModelStateDictionary, e para isto criaremos um “helper”.
Para melhor organizar a aplicação, criei uma pasta chamada Helpers, onde ficará este helper e outros que virão a surgir.
AnotherMvcStore.Web\Helpers\ModelStateWrapperHelper.cs
Este helper apenas converte o IValidationDictionary para um ModelStateDictionary.
Por fim, no CustomerController vamos consumir o serviço, e ao instanciá-lo, informamos o ModelState convertido para o IValidationDictionary, com ajuda do helper criado.
AnotherMvcStore.Web\Controllers\CustomerController.cs
Como resultado, ao rodar a aplicação e tentar cadastrar um cliente sem nenhuma informação, teremos a seguinte tela.
Criando um cliente
O uso do service pattern desacopla a interface do repositório, e permite que várias interfaces compartilhem o mesmo fluxo de informações.
As validações na camada de serviço tem a vantagem de serem mais amplas e extensíveis, porém são mais custosas de implementar, não fornecem tanta integração com EF (Validação com DataAnnotation já implicam na geração correta do banco) e não são assíncronas por padrão.
Podemos ter validações na camada de serviços e no domínio, porém isto infere alguns princípio (DRY e SPOF), e também podemos deixar as validações somente no domínio.
Código Fonte
Inside .NET Users Group Leader http://www.insidedotnet.com.br/
Luciano Lima [MVP] Brazil edited Revision 1. Comment: Corrigido formatação.
Olá, pessoal! Ótima série de artigos, porém há algum problema na visualização das imagens desta última parte, pois o link aponta para o download do zip com o código fonte (todos os links).
Deem uma conferida.
Abraços.
Muito boa a explicação dos 3 artigos, da para o pessoal ter uma ideia geral dos processos..
Parabéns