Repositório Genérico - Violando o princípio ISP
Neste artigo veremos como o uso do Repositório Genérico pode violar o princípio SOLID ISP - Interface Segregation Principle. |
Um repositório genérico é uma classe ou interface que define um conjunto de operações padrão para acesso a dados. Essas operações incluem adicionar, atualizar, excluir e consultar dados em um banco de dados ou outra fonte de dados.
O repositório genérico é uma abstração que pode ser usada para lidar com diferentes tipos de entidades de dados em um sistema. Ele geralmente é implementado como uma classe genérica, que pode receber um tipo de entidade específico como parâmetro de tipo genérico.
As vantagens do uso de um repositório genérico incluem:
As desvantagens do uso de um repositório genérico incluem:
Além das desvatangens apresentadas vamos incluir aqui que o uso de um repositório genérico geralmente nos leva a violar o princípio SOLID ISP e é nisto que vou focar neste artigo.
Violando o principio ISP
Vamos definir um cenário bem simples onde devemos tratar com Pedidos e Itens de Pedidos em uma aplicação web e onde estamos usando o EF Core que usamos para definir um contexto e realizar o mapeamento ORM.
Para isso temos uma interface genérica de repositório com métodos CRUD básicos, como o exemplo abaixo:
public interface IRepository<T> where T :
class { void Add(T entity); void Delete(T entity); void Update(T entity); T GetById(int id); IEnumerable<T> GetAll(); } |
Com essa interface, é possível criar um repositório genérico para qualquer entidade, incluindo as entidades Pedido e ItemPedido:
public class Repository<T> : IRepository<T> where T : class
{
private readonly DbContext _context;
public Repository(DbContext context)
{
_context = context;
}
public void Add(T entity)
{
_context.Set<T>().Add(entity);
_context.SaveChanges();
}
public void Delete(T entity)
{
_context.Set<T>().Remove(entity);
_context.SaveChanges();
}
public void Update(T entity)
{
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
}
public T GetById(int id)
{
return _context.Set<T>().Find(id);
}
public IEnumerable<T> GetAll()
{
return _context.Set<T>().ToList();
}
}
|
Embora o uso de um repositório genérico possa parecer vantajoso por ser mais fácil de implementar e economizar tempo, ele pode violar o princípio ISP se houver necessidade de adicionar funcionalidades específicas para entidades específicas.
Para ilustrar isso vamos considerar que na implementação do repositório de pedidos precisamos obter os pedidos por data e para isso fizemos a seguinte implementação :
1- interface IPedidoRepository
public
interface
IPedidoRepository :
IRepository<Pedido> { IEnumerable<Pedido> GetPedidosPorData(DateTime data); } |
2- classe PedidoRepository
public
class
PedidoRepository :
Repository<Pedido>, IPedidoRepository { public PedidoRepository(DbContext context) : base(context) { public IEnumerable<Pedido> GetPedidosPorData(DateTime data) { return _context.Set<Pedido>().Where(p => p.Data == data).ToList(); } } } |
Nesse caso, a
interface IPedidoRepository herda a interface
genérica IRepository<T> e adiciona um novo método
GetPedidosPorData. O problema é que a classe
Repository<T> não implementa esse método, e,
portanto, a classe PedidoRepository precisa
implementá-lo diretamente.
Isso viola o princípio ISP, pois obriga a classe
PedidoRepository a implementar métodos que não são necessários para
outras entidades e, portanto, pode resultar em uma classe com muitas
responsabilidades e difícil de manter.
Para evitar essa violação do ISP, seria necessário criar uma interface separada
para cada entidade, permitindo que cada uma tenha seus próprios métodos
específicos e evitando que as implementações sejam forçadas a ter métodos
desnecessários.
Para isso podemos definir uma interface IPedidoRepository:
public
interface
IPedidoRepository { void Add(Pedido pedido); void Delete(Pedido pedido); void Update(Pedido pedido); Pedido GetById(int id); IEnumerable<Pedido> GetAll(); IEnumerable<Pedido> GetPedidosPorData(DateTime data); } |
E então criar uma classe concreta de repositório PedidoRepository que implementa essa interface e fornece a lógica específica para a entidade Pedido:
public class PedidoRepository : IPedidoRepository { private readonly DbContext _context;
public PedidoRepository(DbContext context)
public void Add(Pedido pedido)
public void Delete(Pedido pedido)
public void Update(Pedido pedido)
public Pedido GetById(int id)
public IEnumerable<Pedido> GetAll()
public IEnumerable<Pedido> GetPedidosPorData(DateTime data) |
Observe que a
classe PedidoRepository não herda de uma classe
genérica de repositório, mas sim implementa a interface
IPedidoRepository e fornece a lógica específica para a entidade Pedido.
Dessa forma, podemos criar classes de repositório separadas para cada entidade e
fornecer a lógica específica necessária para cada uma, evitando a violação do
princípio ISP. E, para usar esses repositórios, podemos injetar as dependências
correspondentes em outras classes de negócio ou controladores de API.
Como evitar a violação do ISP em repositórios genéricos ?
Interfaces Coesas: Crie interfaces específicas para cada tipo de cliente, expondo apenas os métodos necessários para cada caso
public interface IReadOnlyRepository<T> where T : class { T GetById(int id); } public interface IWriteOnlyRepository<T> where T : class { void Create(T entity); void Update(T entity); void Delete(T entity); } |
Interfaces Base: Utilize uma interface base para definir métodos comuns e interfaces mais específicas para funcionalidades adicionais.
public interface IRepository<T> where T : class { T GetById(int id); } public interface IWriteRepository<T> : IRepository<T> where T : class { void Create(T entity); void Update(T entity); void Delete(T entity); } |
Ao aplicar o ISP em seus repositórios genéricos, você estará construindo um sistema mais flexível, escalável e de fácil manutenção. Ao evitar interfaces "gordas" e garantir que cada cliente dependa apenas do necessário, você estará promovendo um design mais coeso e modular.
Naturamente devemos levar em conta a relação custo/benefício que a implementação ou não do repositório genérico poderá nos trazer. Se valer a pena , que se dane a violação do princípio ISP, desde que isso não traga efeitos colaterais indesejáveis que vão afetar a nossa aplicação.
E estamos conversados...
"Ensina-me, Senhor, o teu caminho, e andarei na tua verdade; une o meu
coração ao temor do teu nome."
Salmos 86:11
Referências:
NET - Unit of Work - Padrão Unidade de ...