Repository Pattern na prática

Muitas vezes nos deparamos com problemas relacionados a não separação da persistência de dados e regras de negócio nas aplicações. Esse é um problema muito comum que vem afetando a maioria das empresas de TI, que em muitos casos impede a evolução rápida dos produtos devido à dificuldade de realização de testes, refactoring, etc.

É por isso que quando formos desenvolver novas aplicações temos que pensar nestes detalhes, e é pra auxiliar nesta empreitada que irei falar um pouco sobre o padrão Repository.

O padrão Repository nasceu com o intuito de isolar a lógica de acesso a dados de qualquer outra lógica da sua aplicação, permitindo que a mesma seja projetada com o mínimo de dependência de recursos de infra-estrutura de acesso a dados.

Vamos à implementação…

Para este exemplo iremos implementar um codigo simples, a nivel de aplicação, para um sistema que faça manipulação de clientes (cadastro, consulta, etc.). Para isso teremos a seguinte estrutura:

Começamos definindo o nosso modelo, que neste caso é bem simples e contém apenas uma Entidade.

public class Cliente
{
     public int ID { get; set; }
     public string Nome { get; set; }
     public string Telefone { get; set; }
     public bool Inadimplente { get; set; }
}

Teremos a Interface IRepository para manter um padrão entre os repositórios.

public interface IRepository<TEntity> where TEntity : class
{
     TEntity Add(TEntity item);
 
     TEntity Modify(TEntity item);
 
     void Remove(TEntity item);
 
     TEntity Get(object key);
 
     IEnumerable GetAll();
 
     IEnumerable GetFiltered(Expression<Func<TEntity, bool>> filter);
}

Agora iremos definir a interface para o repositorio de clientes, neste caso já iremos herdar algumas operações da interface padrão para repositórios então teremos que implementar apenas os métodos que forem específicos do repositório de clientes.

public interface IClienteRepository : IRepository<Cliente>
{
     IEnumerable<Cliente> BuscarClientesInadimplentes();
}

Até então só definimos a interface que será consumida por toda a aplicação na hora de acessar o repositório de clientes, portanto, precisamos ter uma classe que faz a implementação dos métodos dessa interface. Abaixo, seguindo o mesmo padrão das interfaces, teremos uma classe que implementa a lógica de persistência de dados que é genérica entre os repositórios e em seguida na classe específica teremos a lógica especifica.

public class Repository<T> : IRepository<T> where T : class
{
    public T Add(T item)
    {
        //Aqui teriamos a lógica de persistência utilizando a tecnologia escolhida para infraestrutura de dados
        //ADO.NET, ORM`s, etc...
    }
 
    public T Modify(T item)
    {
        //Aqui teriamos a lógica de persistência utilizando a tecnologia escolhida para infraestrutura de dados
        //ADO.NET, ORM`s, etc...
    }
 
    public void Remove(T item)
    {
        //Aqui teriamos a lógica de persistência utilizando a tecnologia escolhida para infraestrutura de dados
        //ADO.NET, ORM`s, etc...
    }
 
    public T Get(object key)
    {
        //Aqui teriamos a lógica de persistência utilizando a tecnologia escolhida para infraestrutura de dados
        //ADO.NET, ORM`s, etc...
    }
 
    public IEnumerable<T> GetAll()
    {
        //Aqui teriamos a lógica de persistência utilizando a tecnologia escolhida para infraestrutura de dados
        //ADO.NET, ORM`s, etc...
    }
 
    public IEnumerable<T> GetFiltered(System.Linq.Expressions.Expression<Func<T, bool>> filter)
    {
        //Aqui teriamos a lógica de persistência utilizando a tecnologia escolhida para infraestrutura de dados
        //ADO.NET, ORM`s, etc...
    }
}

Implementação concreta do repositório de clientes.

public class ClienteRepository : IClienteRepository
{
    public IEnumerable<Cliente> BuscarClientesInadimplentes()
    {
        throw new NotImplementedException();
    }
}

Reparem que não fizemos nenhuma implementação concreta no repositório, até porque o intuito deste post não é mostrar como utilizar tecnologias de infraestrutura de dados.

Agora que temos os repositórios implementados, temos um isolament da persistência de dados do restante da aplicação, e ainda, devido ao uso de interfaces, conseguimos facilmente testar e refatorar o nosso sistema, uma vez que temos um baixo acoplamento entre classes.

Abaixo segue um exemplo de utilização da estrutura de repository que criamos utilizando o padrão ServiceLocator –assunto de posts futuros – para obter uma instancia concreta do repositório.