.NET

4 abr, 2016

O padrão repositório revisitado – Parte 01

Publicidade

Neste artigo, vamos rever os conceitos relacionados ao padrão repositório com um exemplo prático em uma aplicação ASP .NET MVC 5 usando a linguagem C#.

Atualmente, trabalhar com uma arquitetura em camadas no desenvolvimento de aplicações é quase um padrão.

E podemos implementar a arquitetura com 3, 4, 5 e n camadas, dependendo do contexto do nosso projeto.

Adotar uma arquitetura em camadas para o seu projeto visa a estimular a organização do sistema em camadas coesas, mas com um acoplamento fraco entre elas.

Cada camada é formada por um conjunto de classes com um propósito e responsabilidade definidas.

Podemos representar uma arquitetura em 3 camadas (a mais usada) da seguinte forma:

net-1

A camada superior conhece apenas a camada imediatamente inferior.

Ocorre que não é uma boa ideia acessar a lógica de acesso a dados diretamente na camada de negócios (BLL), pois isso cria um forte acoplamento da lógica de acesso a dados na lógica de negócios, tornando a aplicação mais difícil de testar, estender e manter.

Assim, o acesso direto da camada de acesso a dados na camada de negócios causa os seguintes problemas:

  1. Dificulta a realização de testes unitários da camada de negócios;
  2. Cria dependências externas (banco de dados) na lógica de negócio;
  3. Duplica o código de acesso a dados por meio da camada de negócio.

Esse cenário pode ser representado da seguinte forma:

net-2

É justamente para resolver esse problema que a utilização do padrão repositório é indicada.

Martin Fowler afirma:

O padrão  Repository faz a mediação entre o domínio e as camadas de mapeamento de dados, agindo como uma coleção de objetos de domínio em memória…..
Conceitualmente, um repositório encapsula o conjunto de objetos persistidos em um armazenamento de dados e as operações realizadas sobre eles, fornecendo uma visão mais orientada a objetos da camada de persistência…..
e também dá suporte ao objetivo de alcançar uma separação limpa e uma forma de dependência entre o domínio e as camadas de mapeamento de dados. (http://martinfowler.com/eaaCatalog/repository.html)

O padrão Repository separa a lógica de acesso a dados e mapeia essa lógica para entidades na lógica de negócio. Ele trabalha com as entidades de domínio e realiza a lógica de acesso a dados.

No padrão Repository, as entidades de domínio e a lógica de acesso a dados se comunicam usando interfaces, e isso esconde os detalhes do acesso a dados da camada de negócios.

Trocando em miúdos, a camada de negócio pode acessar os objetos de dados sem ter conhecimento da arquitetura de acesso a dados correspondente.

Dessa forma, no padrão Repository, a lógica de negócio não tem conhecimento se o aplicativo está usando LINQ to SQL ou Entity Framework ou qualquer outro ORM e, no futuro, as fontes de dados subjacentes podem ser alteradas sem afetar a lógica de negócio.

net-3

Assim, podemos destacar as seguintes vantagens na aplicação do padrão Repository:

  • A camada de negócios pode ser testada sem a necessidade de fontes externas;
  • A lógica de acesso a dados pode ser testada separadamente;
  • Não existe a duplicação de código;
  • A estratégia de cacheamento dos dados para a fonte de dados pode ser centralizada;
  • O desenvolvimento orientado ao domínio fica mais fácil;
  • A centralização da lógica de acesso a dados torna a manutenção mais fácil.

Vamos, então, para a parte prática, na qual irei implementar o padrão Repository criando um repositório genérico.

Recursos usados :

Implementando o padrão Repository

Abra o VS 2013 Express for web e clique em New Project. A seguir, selecione Visual C# -> ASP .NET Web Application. Informe o nome Mvc_Repositorio e clique no botão OK.

net-4

A seguir, selecione o template Empty, marque MVC  e clique no botão OK.

mvc_net-5

Será criado um projeto contendo toda a estrutura de pastas criadas pelo framework ASP .NET MVC.

Vamos incluir um novo projeto do tipo Class Library em nossa solução.

No menu File, clique em Add -> Project, selecione o template Class Library e informe o nome Mvc_Repositorio.Dominio.

net-5

Vamos incluir uma referência ao Entity Framework neste projeto via Nuget.

No menu TOOLS, clique em Nuget Package Manager e a seguir em Manage Nuget Packages for Solution.

net-6

Selecione o Entity Framework e clique no botão Install, escolhendo para ser instalado somente no projeto Mvc_Repositorio.Dominio.

Apague o arquivo Class1.cs criado por padrão e a seguir crie uma pasta chamada Repositorio no projeto Mvc_Repositorio.Dominio.

Nessa pasta, vamos criar a nossa interface do Repositorio.

No menu PROJECT, clique em Add New Item. Selecione o template Interface e informe o nome IRepositorio.cs.

Agora vamos definir os métodos na nossa interface que deverão ser implementados para realizar o acesso e a persistência dos dados na camada de acesso a dados.

Lembre-se de que uma interface é um contrato que define como uma classe deve ser implementada; assim, vamos definir assinaturas de métodos que deverão implementados por qualquer classe que desejar usar a nossa interface.

Abaixo vemos os métodos definidos na nossa interface IRepositorio:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Mvc_Repositorio.Dominio.Repositorio
{
    public interface IRepositorio<T> where T : class
    {
        IQueryable<T> GetTodos();
        IQueryable<T> Get(Expression<Func<T, bool>> predicate);
        T Procurar(params object[] key);
        T Primeiro(Expression<Func<T, bool>> predicate);
        void Adicionar(T entity);
        void Atualizar(T entity);
        void Deletar(Func<T, bool> predicate);
        void Commit();
        void Dispose();
    }
}

Vamos entender o código acima:

1 – Note que estamos usamos o namespace using System.Linq.Expressions que contém classes e enumerações que permitem representar expressões de código no nível da linguagem como objetos na forma de árvores de expressões;
2 – Na assinatura da classe, estamos declarando public interface IRepositorio<T> where T : class; aqui, T é uma classe;
3 – IQueryable<T> GetTodos() – Esse método retorna todos os dados como IQueryable; dessa forma, podemos retornar a lista e aplicar expressões lambdas para filtrar e classificar os dados;
4 – IQueryable<T> Get(Expression<Func<T, bool>> predicate) – Retorna os dados que atendem ao critério informado em tempo de execução via expressão lambada. Estamos usando o delegate Func, e aplicando o predicate para verificar se o dado atende ao critério (retorna true ou false);
5 – T Find(params object[] key) – Recebe um array de objetos e efetua a pesquisa pela chave primária;
6 – T First(Expression<Func<T, bool>> predicate) – Retorna o primeiro dado que atende ao critério informado via expressão lambda. Usamos novamente o delegate Func e aplicamos o predicate para verificar se o dado atende ao critério;
7 – void Adicionar(T entity) – Recebe o objeto T para realizar a inclusão no banco de dados;
8 – void Atualizar(T entity) – Recebe o objeto T para realizar a atualização no banco de dados;
9 – void Deletar(<Func<T, bool>> predicate) – Excluir registros usando uma condição definida na expressão lambda (via delegate Func) e aplicando o predicate (retorna true ou false) para verificar o critério;
10 – void Commit() – Chama o método ChaveChanges() do contexto para efetivar todas as alterações realizadas no contexto. Ao final de cada operação, você deve sempre chamar este método para efetivar as operações que foram feitas na memória no banco de dados. Se não fizer isso, irá perder todas as operações realizadas;
11 – void Dispose() – Executa a limpeza dos objetos.

Observe que não temos nenhum comando SQL, nenhuma declaração de objetos ADO .NET como connection, command, dataset, datareader etc.

Já temos o contrato definido e agora vamos definir a classe que irá implementar esse contrato.

Antes de implementar a interface IRepositorio, vamos definir o nosso domínio.

Definindo o domínio

Vamos criar uma pasta chamada Entidades no projeto Mvc_Repositorio.Dominio.

Selecione o projeto e, no menu PROJECT, clique em New Folder e informe o nome Entidades. Nessa pasta, vamos definir as entidades do nosso domínio.

Para tornar as coisas bem simples, eu vou definir apenas uma entidade chamada Usuario e vou mapear essa entidade para uma tabela Usuarios existente em um banco SQL Server chamado Cadastro.mdf.

Abaixo vemos a estrutura da tabela Usuarios:

net-7net-7

Selecione a pasta criada e, no menu PROJECT, clique em Add Class e informe o nome Usuario.

Defina o código baixo nessa classe:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Mvc_Repositorio.Dominio.Entidades
{
    [Table("Usuarios")]
    public class Usuario
    {
        [Key]
        public int UsuarioId { get; set; }
        [Required(ErrorMessage = "Informe o login do usuário.")]
        [Display(Name = "Usuário")]
        public string Nome { get; set; }
        [Required(ErrorMessage = "Informe a senha do usuário.")]
        [DataType(DataType.Password)]
        public string Senha { get; set; }
        [Required(ErrorMessage = "Informe o email do usuário.")]
        public string Email { get; set; }
    }
}

O código acima usa os atributos do Data Annotations para definir o mapeamento para a tabela Usuarios e restrições de validações que deverão ser aplicadas na renderização das views que iremos criar.

Agora, para realizar o mapeamento ORM, vamos definir uma classe na qual iremos usar os recursos do Entity Framework por meio do DbSet.

Selecione a pasta criada e, no menu PROJECT, clique em Add Class e informe o nome Usuario.

Defina o código baixo nessa classe:

using System.Data.Entity;

namespace Mvc_Repositorio.Dominio.Entidades
{
    public class UsuarioContexto : DbContext
    {
         public UsuarioContexto()
            : base("name=ConexaoUsuarios")
        { }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            Database.SetInitializer<UsuarioContexto>(new CreateDatabaseIfNotExists<UsuarioContexto>());
        }

        public DbSet<Usuario> Usuarios { get; set; }
    }
}

Nessa classe, usamos o DbContext e realizamos o mapeamento da entidade Usuario com a tabela Usuarios.

O último detalhe que não podemos esquecer é definir no arquivo web.config da aplicação web a string de conexão  ConexaoUsuarios do banco de dados Cadastro.mdf.

Implementando a interface IRepositorio na classe Repositorio

Selecione a pasta Repositorio do projeto de Dominio e selecione o menu PROJECT; clique em Add New Item.

Selecione o template Class, informe o nome Repositorio.cs e clique no botão Add.

Vamos definir a assinatura da classe Repositorio conforme abaixo:

using Mvc_Repositorio.Dominio.Entidades;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace Mvc_Repositorio.Dominio.Repositorio
{
    public class Repositorio<T> : IRepositorio<T> ,  IDisposable where T : class
    {
        private UsuarioContexto Context;

        protected Repositorio()
        {
            Context = new UsuarioContexto();
        }

        public IQueryable<T> GetTodos()
        {
            return Context.Set<T>();
        }

        public IQueryable<T> Get(Expression<Func<T, bool>> predicate)
        {
            return Context.Set<T>().Where(predicate);
        }

        public T Procurar(params object[] key)
        {
            return Context.Set<T>().Find(key);
        }

        public T Primeiro(Expression<Func<T, bool>> predicate)
        {
            return Context.Set<T>().Where(predicate).FirstOrDefault();
        }

        public void Adicionar(T entity)
        {
            Context.Set<T>().Add(entity);
        }

        public void Atualizar(T entity)
        {
            Context.Entry(entity).State = EntityState.Modified;
        }

        public void Deletar(Func<T, bool> predicate)
        {
            Context.Set<T>()
           .Where(predicate).ToList()
           .ForEach(del => Context.Set<T>().Remove(del));
        }

        public void Commit()
        {
            Context.SaveChanges();
        }

        public void Dispose()
        {
            if (Context != null)
            {
                Context.Dispose();
            }
                GC.SuppressFinalize(this);
            }
        }
   }

Dessa forma, como já temos o nosso repositório criado, vamos definir agora as interfaces para representarem o repositório específico para a entidade Usuario.

Então, selecione a pasta Repositorio e, no menu PROJECT, clique em Add New Item.

Selecione o template Interface, informe o nome IUsuarioRepositorio.cs e clique no botão Add.

A seguir, defina o código abaixo para a interface IUsuarioRepositorio:

using Mvc_Repositorio.Dominio.Entidades;

namespace Mvc_Repositorio.Dominio.Repositorio
{
    public interface IUsuarioRepositorio : IRepositorio<Usuario>
    {
    }
}

Deveremos criar também a classe que implementa a interface IUsuarioRepositorio.

Então, selecione a pasta Repositorio e, no menu PROJECT, clique em Add New Item.

Selecione o template Class, informe o nome UsuarioRepositorio.cs e clique no botão Add.

A seguir, defina o código abaixo para a classe UsuarioRepositorio:

using Mvc_Repositorio.Dominio.Entidades;

namespace Mvc_Repositorio.Dominio.Repositorio
{
    public class UsuarioRepositorio :  Repositorio<Usuario>, IUsuarioRepositorio
    {
    }
}

Observe que não precisamos definir nenhum código nas interfaces e classes acima, pois estamos usando o mecanismo da herança e da implementação da interface; assim, estamos usando os métodos definidos na interface IRepositorio e na classe Repositorio. Note também que estamos usando as entidades que foram separadas no projeto de Dominio.

Neste momento, nossa solução tem a seguinte estrutura:

net-8

Na próxima parte do artigo, iremos criar a camada de negócios da nossa aplicação.