Já percorri muitos caminhos e criei muitas implementações de repositórios em diferentes projetos e ... Joguei a toalha e desisti, aqui está o porquê.
Codificação para a exceção
Você codifica para 1% de chance de seu banco de dados mudar de uma tecnologia para outra? Se você está pensando sobre o estado futuro de sua empresa e diz que sim, essa é uma possibilidade, então a) eles devem ter muito dinheiro para fazer uma migração para outra tecnologia de banco de dados ou b) você está escolhendo uma tecnologia de banco de dados para se divertir ou c ) algo deu terrivelmente errado com a primeira tecnologia que você decidiu usar.
Por que jogar fora a rica sintaxe LINQ?
LINQ e EF foram desenvolvidos para que você pudesse fazer coisas legais com ele para ler e percorrer gráficos de objetos. Criar e manter um repositório que possa lhe dar a mesma flexibilidade para fazer isso é uma tarefa monstruosa. Em minha experiência, sempre que criei um repositório, SEMPRE tive vazamento de lógica de negócios na camada do repositório para tornar as consultas mais eficientes e / ou reduzir o número de acessos ao banco de dados.
Não quero criar um método para cada permutação de uma consulta que tenho que escrever. Também posso escrever procedimentos armazenados. Eu não quero GetOrder
, GetOrderWithOrderItem
, GetOrderWithOrderItemWithOrderActivity
, GetOrderByUserId
, e assim por diante ... Eu só quero chegar a entidade principal e transversal e incluir o gráfico do objeto como eu por favor.
A maioria dos exemplos de repositórios são besteiras
A menos que você esteja desenvolvendo algo REALMENTE básico, como um blog ou algo, suas consultas nunca serão tão simples quanto 90% dos exemplos que você encontra na Internet em torno do padrão de repositório. Eu não posso enfatizar isso o suficiente! Isso é algo que é preciso rastejar na lama para descobrir. Sempre haverá aquela consulta que quebrará seu repositório / solução perfeitamente pensado que você criou, e não é até esse ponto em que você se questiona e começa a dívida / erosão técnica.
Não me teste de unidade mano
Mas e quanto ao teste de unidade se eu não tiver um repositório? Como vou zombar? Simples, você não precisa. Vamos olhar de ambos os ângulos:
Sem repositório - você pode simular o DbContext
usando um IDbContext
ou alguns outros truques, mas então você está realmente testando a unidade LINQ to Objects e não LINQ to Entities porque a consulta é determinada em tempo de execução ... OK, isso não é bom! Portanto, agora cabe ao teste de integração cobrir isso.
Com repositório - agora você pode simular seus repositórios e testar a (s) camada (s) intermediária (s). Ótimo, certo? Bem, não realmente ... Nos casos acima em que você precisa vazar lógica na camada do repositório para tornar as consultas mais eficientes e / ou menos acessos ao banco de dados, como seus testes de unidade podem cobrir isso? Agora está na camada de repo e você não quer testar, IQueryable<T>
certo? Também vamos ser honestos, seus testes de unidade não vão cobrir as consultas que têm uma .Where()
cláusula de 20 linhas e.Include()
é um monte de relacionamentos e acessa o banco de dados novamente para fazer todas essas outras coisas, blá, blá, blá de qualquer maneira, porque a consulta é gerada em tempo de execução. Além disso, como você criou um repositório para manter a persistência das camadas superiores ignorante, se agora você deseja alterar a tecnologia de seu banco de dados, desculpe seus testes de unidade definitivamente não vão garantir os mesmos resultados em tempo de execução, voltando aos testes de integração. Então, todo o ponto do repositório parece estranho ..
2 centavos
Já perdemos muita funcionalidade e sintaxe ao usar EF em vez de procedimentos armazenados simples (inserções em massa, exclusões em massa, CTEs, etc.), mas também codifico em C #, então não preciso digitar binário. Usamos EF para termos a possibilidade de usar diferentes provedores e trabalhar com gráficos de objetos de uma forma bem relacionada entre muitas coisas. Certas abstrações são úteis e outras não.
Coding for the exception
: Usar repositórios não significa mudar o mecanismo de banco de dados. É separar o negócio da persistência.DbSet
é o repositório eDbContext
é a Unidade de Trabalho . Por que implementar o padrão de repositório quando o ORM já faz isso por nós! Para testar, basta alterar o provedor paraInMemory
. E faça seus testes! Está bem documentado no MSDN.O padrão do repositório é uma abstração . Seu objetivo é reduzir a complexidade e tornar o resto do código persistente e ignorante. Como bônus, ele permite que você escreva testes de unidade em vez de testes de integração .
O problema é que muitos desenvolvedores não conseguem entender o propósito dos padrões e criam repositórios que vazam informações específicas de persistência até o chamador (normalmente por exposição
IQueryable<T>
). Ao fazer isso, eles não obtêm nenhum benefício ao usar o OR / M diretamente.Atualize para abordar outra resposta
Codificação para a exceção
Usar repositórios não significa ser capaz de mudar a tecnologia de persistência (ou seja, mudar o banco de dados ou usar um serviço da web, etc.). Trata-se de separar a lógica de negócios da persistência para reduzir a complexidade e o acoplamento.
Testes de unidade vs testes de integração
Você não escreve testes de unidade para repositórios. período.
Mas, ao introduzir repositórios (ou qualquer outra camada de abstração entre persistência e negócios), você pode escrever testes de unidade para a lógica de negócios. ou seja, você não precisa se preocupar com a falha de seus testes devido a um banco de dados configurado incorretamente.
Quanto às consultas. Se você usa o LINQ, também precisa ter certeza de que suas consultas funcionam, assim como você tem que fazer com os repositórios. e isso é feito usando testes de integração.
A diferença é que, se você não misturou seu negócio com instruções LINQ, pode ter 100% de certeza de que é o código de persistência que está falhando e não outra coisa.
Se você analisar seus testes, também verá que eles são muito mais limpos se você não tiver interesses mistos (ou seja, LINQ + lógica de negócios)
Exemplos de repositório
A maioria dos exemplos são besteiras. isso é muito verdade. No entanto, se você pesquisar qualquer padrão de design no Google, encontrará muitos exemplos de baixa qualidade. Isso não é motivo para evitar o uso de um padrão.
Construir uma implementação de repositório correta é muito fácil. Na verdade, você só precisa seguir uma única regra:
Não adicione nada na classe do repositório até o momento em que você precisar
Muitos programadores são preguiçosos e tentam fazer um repositório genérico e usar uma classe base com vários métodos de que podem precisar. YAGNI. Você escreve a classe de repositório uma vez e a mantém enquanto o aplicativo durar (pode levar anos). Por que foder tudo por ser preguiçoso. Mantenha-o limpo, sem qualquer herança de classe base. Isso o tornará muito mais fácil de ler e manter.
(A afirmação acima é uma diretriz e não uma lei. Uma classe base pode muito bem ser motivada. Pense antes de adicioná-la, para que você a adicione pelos motivos certos)
Coisas antigas
Conclusão:
Se você não se importa em ter instruções LINQ em seu código de negócios nem se preocupa com testes de unidade, não vejo razão para não usar o Entity Framework diretamente.
Atualizar
Eu fiz um blog sobre o padrão de repositório e o que "abstração" realmente significa: http://blog.gauffin.org/2013/01/repository-pattern-done-right/
Atualização 2
Novamente: uma entidade com mais de 20 campos foi modelada incorretamente. É uma entidade DEUS. Divida-o.
Não estou argumentando que
IQueryable
não foi feito para questionar. Estou dizendo que não é certo para uma camada de abstração como o padrão de Repositório, uma vez que vaza. Não existe um provedor LINQ To Sql 100% completo (como EF).Todos eles têm itens específicos de implementação, como como usar o carregamento antecipado / lento ou como fazer instruções SQL "IN". Expondo
IQueryable
no repositório força o usuário a saber todas essas coisas. Portanto, toda a tentativa de abstrair a fonte de dados é um fracasso completo. Você apenas adiciona complexidade sem obter nenhum benefício em usar o OR / M diretamente.Implemente o padrão de Repositório corretamente ou simplesmente não o use.
(Se você realmente deseja lidar com grandes entidades, pode combinar o padrão Repositório com o padrão Especificação . Isso fornece uma abstração completa que também pode ser testada.)
fonte
IMO, tanto a
Repository
abstração quanto aUnitOfWork
abstração têm um lugar muito valioso em qualquer desenvolvimento significativo. As pessoas discutirão sobre os detalhes de implementação, mas assim como existem muitas maneiras de esfolar um gato, existem muitas maneiras de implementar uma abstração.Sua pergunta é especificamente para usar ou não usar e por quê.
Como você sem dúvida percebeu, você já tem esses dois padrões integrados ao Entity Framework,
DbContext
é oUnitOfWork
eDbSet
é oRepository
. Geralmente, você não precisa fazer o teste de unidade dos própriosUnitOfWork
ouRepository
, pois eles estão simplesmente facilitando entre suas classes e as implementações de acesso a dados subjacentes. O que você vai precisar fazer, repetidamente, é simular essas duas abstrações ao testar a lógica de seus serviços.Você pode simular, falsificar ou o que quer que seja com bibliotecas externas adicionando camadas de dependências de código (que você não controla) entre a lógica que está fazendo o teste e a lógica que está sendo testada.
Portanto, um ponto secundário é que ter sua própria abstração
UnitOfWork
eRepository
lhe dá o máximo de controle e flexibilidade ao simular seus testes de unidade.Tudo bem, mas para mim, o verdadeiro poder dessas abstrações é que elas fornecem uma maneira simples de aplicar técnicas de Programação Orientada a Aspectos e aderir aos princípios SOLID .
Então você tem o seu
IRepository
:E sua implementação:
Nada fora do comum até agora, mas agora queremos adicionar um pouco de registro - fácil com um decorador de registro .
Tudo pronto e sem alterações em nosso código existente . Existem inúmeras outras questões transversais que podemos adicionar, como tratamento de exceções, armazenamento em cache de dados, validação de dados ou qualquer outra coisa e, em todo o nosso processo de design e construção, a coisa mais valiosa que temos que nos permite adicionar recursos simples sem alterar nenhum de nosso código existente é a nossa
IRepository
abstração .Agora, muitas vezes eu vi esta pergunta no StackOverflow - “como você faz o Entity Framework funcionar em um ambiente multilocatário?”.
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Se você tem uma
Repository
abstração, a resposta é “é fácil adicionar um decorador”IMO, você deve sempre colocar uma abstração simples sobre qualquer componente de terceiros que será referenciado em mais de um punhado de lugares. Sob essa perspectiva, um ORM é o candidato perfeito, pois é referenciado em grande parte do nosso código.
A resposta que normalmente vem à mente quando alguém diz "por que eu deveria ter uma abstração (por exemplo
Repository
) sobre esta ou aquela biblioteca de terceiros" é "por que você não faria?"Os decoradores PS são extremamente simples de aplicar usando um recipiente IoC, como SimpleInjector .
fonte
Em primeiro lugar, como sugerido por alguma resposta, o próprio EF é um padrão de repositório, não há necessidade de criar outra abstração apenas para nomeá-lo como repositório.
Repositório de simulação para testes de unidade, realmente precisamos dele?
Deixamos EF se comunicar para testar o banco de dados em testes de unidade para testar nossa lógica de negócios diretamente no banco de dados de teste SQL. Não vejo nenhum benefício em simular qualquer padrão de repositório. O que há de realmente errado em fazer testes de unidade no banco de dados de teste? Como são operações em massa não são possíveis e acabamos escrevendo SQL bruto. SQLite na memória é o candidato perfeito para fazer testes de unidade em banco de dados real.
Abstração desnecessária
Você quer criar um repositório apenas para que no futuro você possa facilmente substituir EF por NHbibernate etc ou qualquer outra coisa? Parece um ótimo plano, mas é realmente econômico?
Linq mata testes de unidade?
Eu gostaria de ver alguns exemplos de como ele pode matar.
Injeção de dependência, IoC
Uau, essas palavras são ótimas, claro que parecem ótimas na teoria, mas às vezes você tem que escolher entre um ótimo design e uma ótima solução. Nós usamos tudo isso e acabamos jogando tudo no lixo e escolhendo uma abordagem diferente. Tamanho x velocidade (tamanho do código e velocidade de desenvolvimento) são muito importantes na vida real. Os usuários precisam de flexibilidade, eles não se importam se seu código é ótimo em design em termos de DI ou IoC.
A menos que você esteja criando o Visual Studio
Todos esses excelentes designs são necessários se você estiver construindo um programa complexo como o Visual Studio ou Eclipse, que será desenvolvido por muitas pessoas e precisa ser altamente personalizável. Todos os grandes padrões de desenvolvimento entraram em cena após anos de desenvolvimento pelos quais esses IDEs passaram, e eles evoluíram em um local onde todos esses grandes padrões de design são tão importantes. Mas se você estiver fazendo uma folha de pagamento simples baseada na web ou um aplicativo de negócios simples, é melhor que você evolua em seu desenvolvimento com o tempo, em vez de gastar tempo construindo-o para milhões de usuários, onde será implantado apenas para centenas de usuários.
Repositório como visualização filtrada - ISecureRepository
Por outro lado, o repositório deve ser uma visão filtrada do EF que protege o acesso aos dados aplicando o preenchimento necessário com base no usuário / função atual.
Mas fazer isso complica o repositório ainda mais, pois ele acaba em uma enorme base de código para manter. As pessoas acabam criando diferentes repositórios para diferentes tipos de usuários ou combinações de tipos de entidades. Além disso, também acabamos com muitos DTOs.
A resposta a seguir é um exemplo de implementação do Repositório Filtrado sem criar um conjunto completo de classes e métodos. Pode não responder a uma pergunta diretamente, mas pode ser útil para derivar uma.
Isenção de responsabilidade: eu sou o autor do SDK REST da entidade
http://entityrestsdk.codeplex.com
Tendo isso em mente, desenvolvemos um SDK que cria um repositório de visualização filtrada com base no SecurityContext que contém filtros para operações CRUD. E apenas dois tipos de regras simplificam quaisquer operações complexas. O primeiro é o acesso à entidade e o outro é a regra de leitura / gravação para a propriedade.
A vantagem é que você não reescreve lógica de negócios ou repositórios para diferentes tipos de usuários, apenas bloqueia ou concede acesso a eles.
Essas regras LINQ são avaliadas em relação ao banco de dados no método SaveChanges para cada operação e essas regras atuam como firewall na frente do banco de dados.
fonte
Há muito debate sobre qual método é o correto, então eu vejo como ambos são aceitáveis, então eu uso sempre qual eu gosto mais (que não é repositório, UoW).
No EF UoW é implementado via DbContext e os DbSets são repositórios.
Quanto a como trabalhar com a camada de dados, trabalho diretamente no objeto DbContext, para consultas complexas farei métodos de extensão para a consulta que podem ser reutilizados.
Eu acredito que Ayende também tem alguns posts sobre como abstrair as operações de CUD é ruim.
Eu sempre faço uma interface e tenho meu contexto herdado dela, então posso usar um contêiner IoC para DI.
fonte
O que mais se aplica ao EF não é um Padrão de Repositório. É um padrão Facade (abstraindo as chamadas aos métodos EF em versões mais simples e fáceis de usar).
EF é quem aplica o Padrão de Repositório (e também o padrão de Unidade de Trabalho). Ou seja, EF é aquele que abstrai a camada de acesso a dados de forma que o usuário não tenha ideia de que está lidando com SQLServer.
E, por falar nisso, a maioria dos "repositórios" sobre o EF nem mesmo são bons Facades, pois eles meramente mapeiam, de forma bastante direta, para métodos únicos no EF, até o ponto de ter as mesmas assinaturas.
As duas razões, então, para aplicar este padrão "Repositório" sobre o EF é permitir testes mais fáceis e estabelecer um subconjunto de chamadas "enlatadas" para ele. Não é ruim em si, mas claramente não é um Repositório.
fonte
Linq é hoje um 'Repositório'.
ISession + Linq já é o repositório, e você não precisa de
GetXByY
métodos nemQueryData(Query q)
generalização. Sendo um pouco paranóico com o uso de DAL, ainda prefiro a interface de repositório. (Do ponto de vista da sustentabilidade, ainda precisamos ter alguma fachada sobre interfaces de acesso a dados específicas).Aqui está o repositório que usamos - ele nos desvincula do uso direto do nhibernate, mas fornece interface linq (como acesso ISession em casos excepcionais, que estão sujeitos a refatoração eventualmente).
fonte
O Repositório (ou como alguém quiser chamá-lo) neste momento, para mim, trata principalmente de abstrair a camada de persistência.
Eu o uso junto com objetos de consulta, portanto, não tenho um acoplamento a nenhuma tecnologia específica em meus aplicativos. E também facilita muito os testes.
Então, eu tendo a ter
Possivelmente adicione métodos assíncronos com retornos de chamada como delegados. O repo é fácil de implementar genericamente , portanto, não posso tocar em uma linha da implementação de aplicativo para aplicativo. Bem, isso é verdade pelo menos quando uso NH, eu fiz isso também com EF, mas me fez odiar EF. 4. A conversa é o início de uma transação. Muito legal se algumas classes compartilham a instância do repositório. Além disso, para NH, um repo em minha implementação é igual a uma sessão que é aberta na primeira solicitação.
Em seguida, os objetos de consulta
Para o configure utilizo no NH apenas para passar no ISession. Em EF não faz sentido mais ou menos.
Um exemplo de consulta seria .. (NH)
Para fazer uma consulta EF, você teria que ter o contexto na base abstrata, não na sessão. Mas é claro que o ifc seria o mesmo.
Desta forma, as próprias consultas são encapsuladas e facilmente testáveis. E o melhor de tudo, meu código depende apenas de interfaces. Tudo muito limpo. Objetos de domínio (negócios) são apenas isso, por exemplo, não há mistura de responsabilidades como ao usar o padrão de registro ativo que é dificilmente testável e mistura código de acesso a dados (consulta) no objeto de domínio e, ao fazer isso, está misturando preocupações (objeto que busca em si ??). Todos ainda estão livres para criar POCOs para transferência de dados.
Em suma, muita reutilização e simplicidade de código são fornecidas com essa abordagem, sem que eu possa imaginar nada. Alguma ideia?
E muito obrigado ao Ayende por suas ótimas postagens e contínua dedicação. São as ideias dele aqui (objeto de consulta), não minhas.
fonte
Para mim, é uma decisão simples, com relativamente poucos fatores. Os fatores são:
Portanto, se meu aplicativo não pode justificar o nº 2, domínio separado e modelos de dados, geralmente não vou me preocupar com o nº 5
fonte