Repositórios Genéricos
Repositórios Genéricos no Repository Pattern
Introdução aos Repositórios Genéricos
Com os fundamentos do Repository Pattern estabelecidos, avança-se para um tópico mais avançado: os repositórios genéricos. Esta abordagem, embora popular em determinados contextos, apresenta considerações arquiteturais importantes que devem ser ponderadas antes de sua adoção.
A implementação de um repositório genérico visa criar uma classe base que encapsule operações CRUD comuns, permitindo que repositórios específicos herdem essa funcionalidade. Contudo, esta estratégia nem sempre se alinha com práticas de design como Domain-Driven Design (DDD) ou Arquitetura Limpa, onde as operações de acesso a dados frequentemente possuem requisitos específicos do domínio.
Design da Interface Genérica
Princípio de Segregação de Interface (ISP)
Ao definir uma interface de repositório genérico, é essencial considerar o Princípio de Segregação de Interface (Interface Segregation Principle - ISP) dos princípios SOLID. Este princípio estabelece que uma interface não deve forçar suas implementadoras a depender de métodos que não utilizam.
Uma interface genérica IRepository que define um CRUD completo obrigaria todas as entidades do sistema a implementar todas as operações, mesmo quando algumas delas não são necessárias para determinado domínio.
namespace RepositoryStore.Repositories.Abstractions;
// Implementação inicial - potencial violação do ISP
public interface IRepository<T> where T : class
{
Task<T> CreateAsync(T entity, CancellationToken cancellationToken = default);
Task<T> UpdateAsync(T entity, CancellationToken cancellationToken = default);
Task<T> DeleteAsync(T entity, CancellationToken cancellationToken = default);
Task<T?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
Task<List<T>> GetAllAsync(int skip = 0, int take = 25,
CancellationToken cancellationToken = default);
}Consideração arquitetural: Em sistemas que seguem rigorosamente o ISP, poderiam ser definidas interfaces separadas:
Modelagem com Entidade Base
Para sistemas que utilizam uma hierarquia de entidades, pode-se definir uma classe base abstrata:
A interface genérica pode então restringir o tipo T para derivados de Entity:
Para fins demonstrativos, manteremos a restrição where T : class por ser mais genérica e aplicável a diferentes cenários.
Refatorando as Interfaces Específicas
Com a interface genérica definida, as interfaces específicas podem ser simplificadas. A interface IProductRepository passa a herdar da interface genérica, especificando o tipo concreto:
Observação: Em versões mais recentes do C# (12+), a sintaxe pode ser ainda mais concisa:
Esta abordagem mantém a flexibilidade para adicionar métodos específicos do domínio quando necessário, enquanto herda automaticamente todas as operações CRUD definidas na interface genérica.
Implementação da Classe Base Genérica
Estrutura da Classe Abstrata
Cria-se uma classe abstrata Repository<T> que implementa a interface IRepository<T>:
Análise da estrutura:
A classe é declarada como
abstractpara prevenir instanciação diretaO construtor primário recebe
DbContext(nãoAppDbContext) para maior reutilizaçãoA propriedade
_dbSeté inicializada usandocontext.Set<T>(), que obtém oDbSet<T>apropriado para a entidade genéricaO
DbContexté armazenado em campo protegido para uso nas operações de persistência
Obtendo o DbSet Genérico
O método context.Set<T>() é uma funcionalidade fundamental do Entity Framework que retorna a instância de DbSet<T> correspondente à entidade T. Este mecanismo permite que a classe base opere com qualquer entidade mapeada no contexto, sem precisar conhecer especificamente qual DbSet utilizar.
Implementação das Operações CRUD
Implementação Completa da Classe Base
Refatorando os Repositórios Específicos
Com a classe base implementada, os repositórios específicos tornam-se extremamente concisos:
Repositório de Produtos
Versão concisa (C# 12+):
Repositório de Categorias
Repositório de Usuários
Manutenção da Configuração de Dependência
Um dos benefícios significativos desta abordagem é que a configuração de injeção de dependência no Program.cs permanece inalterada:
Os consumidores da interface IProductRepository continuam funcionando normalmente, sem necessidade de alterações, demonstrando a compatibilidade retroativa da refatoração.
Vantagens e Desvantagens
Vantagens dos Repositórios Genéricos
Redução de Código Duplicado: Operações CRUD comuns são implementadas uma única vez
Consistência: Garante padrão uniforme em todas as operações de persistência
Manutenibilidade Simplificada: Correções e melhorias aplicam-se automaticamente a todos os repositórios
Velocidade de Desenvolvimento: Novas entidades podem ter repositórios funcionais com mínimo esforço
Curva de Aprendizado: Desenvolvedores novos compreendem rapidamente o padrão estabelecido
Desvantagens e Considerações Críticas
Potencial Violação do ISP: Entidades podem ser forçadas a implementar operações não utilizadas
Limitação em Consultas Complexas: Consultas específicas do domínio frequentemente exigem métodos adicionais
Acoplamento ao Entity Framework: A implementação genérica está intimamente ligada ao EF Core
Dificuldade com Agregados Complexos: Em DDD, agregados com múltiplas entidades podem não se adequar ao modelo genérico
Falta de Especificidade do Domínio: Operações de negócio específicas ficam fora do escopo do repositório genérico
Casos de Uso Recomendados
Sistemas CRUD Simples: Aplicações com operações básicas de persistência
Prototipagem Rápida: Desenvolvimento inicial onde a agilidade é prioritária
APIs Administrativas: Interfaces onde operações genéricas são predominantes
Sistemas com Muitas Entidades Similares: Domínios com múltiplas entidades que compartilham comportamento idêntico
Casos onde Evitar
Sistemas com Lógica de Domínio Complexa: Onde cada entidade tem comportamentos únicos
Arquitetura Limpa/DDD Rigorosa: Onde cada agregado tem requisitos específicos de persistência
Consultas Otimizadas para Performance: Onde consultas personalizadas são necessárias
Sistemas com Múltiplas Fontes de Dados: Onde diferentes entidades podem usar diferentes estratégias de persistência
Extensões e Personalizações
Adicionando Métodos Específicos
Mesmo com a classe base genérica, repositórios específicos podem estender a funcionalidade:
Implementando Interfaces Adicionais
Para maior flexibilidade, podem ser definidas múltiplas interfaces:
Conclusão e Recomendações Práticas
A implementação de repositórios genéricos representa uma ferramenta poderosa no arsenal do desenvolvedor, mas sua adoção deve ser ponderada em relação aos requisitos específicos do projeto.
Diretrizes de Decisão
Avaliar a Complexidade do Domínio: Sistemas com regras de negócio simples beneficiam-se mais da genericidade
Considerar a Evolução do Sistema: Prever se operações específicas serão necessárias no futuro
Analisar os Requisitos de Performance: Consultas genéricas podem não ser otimizadas para cenários específicos
Manter a Flexibilidade Arquitetural: Garantir que a solução não comprometa princípios de design importantes
Melhores Práticas Identificadas
Começar com Especificidade: Iniciar com repositórios específicos e extrair padrões comuns posteriormente
Manter a Capacidade de Extensão: Projetar a classe base para permitir sobrescrita de métodos quando necessário
Documentar Suposições e Limitações: Clarificar os cenários onde a solução genérica é apropriada
Implementar Progressivamente: Adicionar genericidade gradualmente à medida que os padrões emergem
Alternativas Consideradas
Repositórios Específicos por Entidade: Máximo controle e especificidade, com custo de duplicação
Padrão Specification: Separação das regras de consulta dos repositórios
Query Objects: Encapsulamento de consultas complexas em objetos especializados
CQRS (Command Query Responsibility Segregation): Separação radical entre operações de leitura e escrita
A decisão final deve equilibrar os benefícios da reutilização de código com a necessidade de expressar adequadamente a lógica de domínio e atender aos requisitos específicos do sistema em desenvolvimento. A implementação apresentada serve como base sólida que pode ser adaptada conforme as necessidades do projeto evoluem.
Atualizado