Se o Padrão de Repositório é um exagero para ORMs modernas (EF, nHibernate), o que é uma abstração melhor?

12

Recentemente, li muitos argumentos contra o uso do padrão de repositório com ORMs poderosos, como o Entity Framework, pois ele incorpora funcionalidade semelhante a repositório, juntamente com a funcionalidade Unidade de Trabalho.

Outro argumento contra o uso do padrão para uma situação como teste de unidade é que o padrão do repositório é uma abstração com vazamento, pois as implementações mais genéricas utilizam o IQueryable.

Os argumentos contra o uso do padrão de repositório fazem sentido para mim, mas os métodos alternativos de abstração sugeridos são geralmente mais confusos e parecem tão exagerados quanto o problema.

A solução de Jimmy Bogards parece ser uma mistura de afastar as abstrações, mas também introduzir sua própria arquitetura. https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

Outro exemplo de repositórios sendo desnecessariamente .... mas use minha arquitetura! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

Outro ... http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework

Não encontrei uma substituição ou alternativa clara à abordagem de padrão de repositório "excessivamente complexo" que não é mais arquitetada por si mesma.

AnotherDeveloper
fonte
4
O que especificamente você está tentando alcançar? Abstrações devem ter um propósito. Se você estiver escrevendo um aplicativo CRUD, um ORM provavelmente é abstrato o suficiente.
precisa saber é o seguinte
@ JacquesB Estou tentando evitar problemas de impedância relacional de objetos com um modelo de domínio robusto, mas também abstrato dos meus modelos de exibição em uma implementação mvc.
AnotherDeveloper
Reed Cospey tem muitas coisas positivas a dizer sobre o IQuerable aqui: stackoverflow.com/questions/1578778/using-iqueryable-with-linq Isso implica que é melhor na camada de transporte. No que diz respeito à abstração, achei que o uso do padrão Generic Repository funcionava bem quando eu precisava injetar o EntityType, mas ainda queria manter métodos comuns. Por outro lado, eu próprio argumentei no fórum LINDN do MSDN que o EF é um padrão de repositório porque está tudo na memória. Um projeto usou numerosas cláusulas Where como chamadas de método que funcionaram bem.
John Peters
Mas uma área em que examinei, mas rejeitei, foi o negócio da Árvore de Expressões, alguns realmente gostamos, mas ... você precisa estudá-lo muito antes de achar útil.
John Peters
2
devemos voltar para SQL chamando de controladores
Bobek

Respostas:

12

Eu acho que você é repositórios conflitantes e repositórios genéricos.

Um repositório básico apenas interage com seu armazenamento de dados e fornece métodos para retornar os dados

IRepository {
   List<Data> GetDataById(string id);
}

Ele não vaza a camada de dados para o seu código por meio de um IQueryable ou de outras maneiras de passar em consultas aleatórias e fornece uma superfície testável e injetável bem definida de métodos.

Um repositório genérico permite que você passe sua consulta como um ORM

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
    //or
    IQueryable<T> Get<T>();
}

Concordo que não faz muito sentido usar um Repositório Genérico sobre um ORM, que é basicamente apenas outro Repositório Genérico.

A resposta é usar o padrão básico do repositório para ocultar seu ORM

Ewan
fonte
1
Um ORM de um ORM? Eu estava tentando ser engraçado. Por que você precisa abstrair o ORM?
johnny
1
mesma razão que você abstrai qualquer coisa. para evitar a poluição seu código com as classes proprietárias
Ewan
6

A maioria dos argumentos mencionados incorretamente atribui aos recursos do padrão de repositório que ele não possui.

Conceitualmente, um Repositório, conforme definido originalmente no DDD, é apenas uma coleção de objetos que você pode pesquisar ou adicionar. O mecanismo de persistência por trás dele é abstraído; assim, como consumidor, você tem a ilusão de que é uma coleção na memória.

Uma implementação de Repositório que possui abstrações com vazamento (expor, IQueryablespor exemplo) é uma implementação ruim de Repositório.

Uma implementação de Repositório que expõe mais do que apenas operações de coleta (por exemplo, recursos da Unidade de Trabalho) é uma implementação ruim do Repositório.

Existem alternativas ao Repositório para acesso a dados? Sim, mas eles não estão relacionados aos problemas que você levanta na sua pergunta.

guillaume31
fonte
1

Para mim, os repositórios, combinados com o ORM ou outras camadas de persistência do banco de dados, têm estas desvantagens:

  1. Encobrir unidades de trabalho. A UoW precisa ser codificada pelo programador e raramente pode ser implementada como um tipo de mágica em segundo plano, onde o usuário simplesmente faz consultas e modificações, sem definir os limites da UoW e, possivelmente, o ponto de confirmação. Às vezes, as UoW são abandonadas, reduzindo-as em micro UoW (por exemplo, sessões NHibernate) em cada método de acesso ao Repositório.
  2. Encobrir ou, na pior das hipóteses, destruir a ignorância de persistência: métodos como "Load ()", "Get ()", "Save ()" ou "Update ()" sugerem operações imediatas de um único objeto, como se enviassem indivíduos SQL / DML ou como se estivesse trabalhando com arquivos. De fato, por exemplo, os métodos NHibernate, com esses nomes enganosos, geralmente não fazem acesso individual, mas enfileiram para carregamento lento ou inserem / atualizam lotes (Persistence Ignorance). Às vezes, os programadores se perguntam por que não obtêm operações imediatas de banco de dados e quebram à força a ignorância da persistência, diminuindo o desempenho e usando grandes esforços para realmente piorar o sistema (muito!).
  3. Crescimento descontrolado. Um repositório simples pode acumular cada vez mais métodos para atender a necessidades específicas.

Tal como:

public interface ICarsRepository  /* initial */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); // bad, should be for multiple IDs.
    void SaveCar(ICar carToSave); // bad, no individual saves, use UoW commit!
}

public interface ICarsRepository  /* a few years later */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); 
    IList<ICar> GetBlueCars();
    IList<ICar> GetRedYellowGreenCars();
    IList<ICar> GetCarsByColor(Color colorOfCars); // a bit better
    IList<ICar> GetCarsByColor(IEnumerable<Color> colorsOfCars); // better!
    IList<ICar> GetCarsWithPowerBetween(int hpFrom, int hpTo);
    IList<ICar> GetCarsWithPowerKwBetween(int kwFrom, int kwTo);
    IList<ICar> GetCarsBuiltBetween(int yearFrom, int yearTo);
    IList<ICar> GetCarsBuiltBetween(DateTime from, DateTime to); // some also need month and day
    IList<ICar> GetHybridCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetElectricCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetCarsFromManufacturer(IManufacturer carManufacturer); 
    bool HasCarMeanwhileBeenChangedBySomebodyElseInDb(ICar car); // persistence ignorance broken
    void SaveCar(ICar carToSave);
}

4. Objeto Perigo de Deus: você pode ser tentado a criar uma classe divina, cobrindo todo o seu modelo ou camada de acesso a dados. A classe de repositório não conteria apenas métodos Car, mas métodos para todas as entidades.

Na minha opinião, é melhor oferecer pelo menos algumas oportunidades de consulta, para evitar a enorme confusão de muitos métodos de uso único. Não importa se é LINQ, uma linguagem de consulta própria ou mesmo algo extraído diretamente do ORM (OK, tipo de problema de acoplamento ...).

Erik Hart
fonte
4
Para onde vão todos esses métodos se não se usa o Padrão de Repositório?
johnny
1

Se o objetivo da interface do repositório é zombar do banco de dados para fazer um teste unittest (= teste isolado), a melhor abstração é algo fácil de zombar.

É difícil zombar de uma interface de repositório baseada em um resultado IQueryable.

Do ponto de vista do teste de unidade

IRepository {
   List<Data> GetDataById(string id);
}

pode ser ridicularizado facilmente

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
}

pode ser facilmente ridicularizado apenas se o mock ignorar o conteúdo do parâmetro de consulta.

IGenericRepository<T> {
    IQueryable<T> Get<T>(some_parameters);
}

não pode ser facilmente ridicularizado

k3b
fonte
0

Eu não acho que o padrão do repositório seja um exagero, se você usar funções lambda para consultar. Especialmente quando você precisa abstrair o ORM (na minha opinião você sempre deve), então eu não vou me importar com os detalhes de implementação do próprio repositório.

Por exemplo:

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        UserRepository ur = new UserRepository();
        var userWithA = ur.GetBy(u => u.Name.StartsWith("A"));

        Console.WriteLine(userWithA.Name);


        ur.GetAllBy(u => u.Name.StartsWith("M"))
          .ForEach(u => Console.WriteLine(u.Name));


        ur.GetAllBy(u => u.Age > 13)
          .ForEach(u => Console.WriteLine(u.Name));
    }
}

public class UserRepository 
{
    List<User> users = new List<User> { 
        new User{Name="Joe", Age=10},
            new User{Name="Allen", Age=12},
            new User{Name="Martin", Age=14},
            new User{Name="Mary", Age=15},
            new User{Name="Ashton", Age=29}
    };

    public User GetBy(Predicate<User> userPredicate)
    {
        return users.Find(userPredicate);
    }

    public List<User> GetAllBy(Predicate<User> userPredicate)
    {
        return users.FindAll(userPredicate);
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
dhalsim
fonte