Estamos desenvolvendo um aplicativo ASP.NET MVC e agora estamos construindo as classes de repositório / serviço. Eu estou querendo saber se há alguma grande vantagem em criar uma interface genérica de IRepository que todos os repositórios implementam, contra cada Repositório com sua própria interface e conjunto de métodos.
Por exemplo: uma interface genérica de IRepository pode parecer (tirada desta resposta ):
public interface IRepository : IDisposable
{
T[] GetAll<T>();
T[] GetAll<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
void Delete<T>(T entity);
void Add<T>(T entity);
int SaveChanges();
DbTransaction BeginTransaction();
}
Cada repositório implementaria essa interface, por exemplo:
- CustomerRepository: IRepository
- ProductRepository: IRepository
- etc.
A alternativa que seguimos em projetos anteriores seria:
public interface IInvoiceRepository : IDisposable
{
EntityCollection<InvoiceEntity> GetAllInvoices(int accountId);
EntityCollection<InvoiceEntity> GetAllInvoices(DateTime theDate);
InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated);
InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique
InvoiceEntity CreateInvoice();
InvoiceLineEntity CreateInvoiceLine();
void SaveChanges(InvoiceEntity); //handles inserts or updates
void DeleteInvoice(InvoiceEntity);
void DeleteInvoiceLine(InvoiceLineEntity);
}
No segundo caso, as expressões (LINQ ou não) estariam totalmente contidas na implementação do Repositório, quem estiver implementando o serviço só precisa saber para qual função do repositório chamar.
Acho que não vejo a vantagem de escrever toda a sintaxe da expressão na classe de serviço e passar para o repositório. Isso não significa que o código LINQ de fácil envio de mensagens está sendo duplicado em muitos casos?
Por exemplo, em nosso antigo sistema de faturamento, chamamos
InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)
de alguns serviços diferentes (cliente, fatura, conta etc.). Isso parece muito mais limpo do que escrever o seguinte em vários lugares:
rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);
A única desvantagem que vejo ao usar a abordagem específica é que poderíamos terminar com muitas permutações das funções Get *, mas isso ainda parece preferível ao empurrar a lógica da expressão para as classes Service.
o que estou perdendo?
fonte
Respostas:
Esse é um problema tão antigo quanto o próprio padrão do repositório. A recente introdução de LINQs
IQueryable
, uma representação uniforme de uma consulta, causou muita discussão sobre esse mesmo tópico.Eu prefiro repositórios específicos, depois de ter trabalhado muito duro para construir uma estrutura de repositório genérica. Não importa qual mecanismo inteligente eu tentei, sempre acabava com o mesmo problema: um repositório é uma parte do domínio que está sendo modelado e esse domínio não é genérico. Nem toda entidade pode ser excluída, nem toda entidade pode ser adicionada, nem toda entidade possui um repositório. As consultas variam muito; a API do repositório se torna tão exclusiva quanto a própria entidade.
Um padrão que costumo usar é ter interfaces de repositório específicas, mas uma classe base para as implementações. Por exemplo, usando LINQ to SQL, você pode fazer:
Substitua
DataContext
por sua unidade de trabalho de sua escolha. Um exemplo de implementação pode ser:Observe que a API pública do repositório não permite que os usuários sejam excluídos. Além disso, a exposição
IQueryable
é uma outra lata de minhocas - existem tantas opiniões quanto umbigos sobre esse tópico.fonte
Na verdade, eu discordo um pouco do post de Bryan. Acho que ele está certo, que no final tudo é muito único e assim por diante. Mas, ao mesmo tempo, a maior parte disso sai à medida que você projeta, e eu acho que, ao criar um repositório genérico e usá-lo durante o desenvolvimento do meu modelo, posso instalar um aplicativo muito rapidamente, e refatorar para uma maior especificidade, pois precisa fazer isso.
Portanto, em casos como esse, muitas vezes criei um IRepository genérico que possui a pilha CRUD completa, e isso permite que eu rapidamente jogue com a API e deixe que as pessoas joguem com a interface do usuário e faça testes de integração e aceitação do usuário em paralelo. Então, como acho que preciso de consultas específicas no repositório, etc, começo a substituir essa dependência pelo específico, se necessário, e a partir daí. Um impl impl. é fácil de criar e usar (e possivelmente conectar-se a um banco de dados na memória ou a objetos estáticos ou objetos simulados ou o que for).
Dito isto, o que comecei a fazer ultimamente é acabar com o comportamento. Portanto, se você fizer interfaces para IDataFetcher, IDataUpdater, IDataInserter e IDataDeleter (por exemplo), poderá combinar e combinar para definir seus requisitos por meio da interface e depois ter implementações que cuidam de alguns ou de todos eles, e eu posso ainda injeto a implementação de tudo para usar enquanto estou criando o aplicativo.
Paulo
fonte
GetById()
,. Devo usarIRepository<T, TId>
,GetById(object id)
ou fazer suposições e usarGetById(int id)
? Como as chaves compostas funcionariam? Gostaria de saber se uma seleção genérica por ID era uma abstração que valeria a pena. Caso contrário, o que mais os repositórios genéricos seriam forçados a expressar de maneira desajeitada? Essa foi a linha de raciocínio por trás da abstração da implementação , não da interface .Prefiro repositórios específicos derivados de repositórios genéricos (ou lista de repositórios genéricos para especificar o comportamento exato) com assinaturas de método substituíveis.
fonte
Tenha um repositório genérico envolvido por um repositório específico. Dessa forma, você pode controlar a interface pública, mas ainda tem a vantagem da reutilização de código proveniente de um repositório genérico.
fonte
classe pública UserRepository: Repository, IUserRepository
Você não deve injetar IUserRepository para evitar a exposição da interface. Como as pessoas disseram, você pode não precisar da pilha CRUD completa etc.
fonte