Si el patrón de repositorio es excesivo para los ORM modernos (EF, nHibernate), ¿cuál es una mejor abstracción?

12

Recientemente he leído muchos argumentos en contra del uso del patrón de repositorio con poderosos ORM como Entity Framework, ya que incorpora una funcionalidad de repositorio, junto con la funcionalidad de la Unidad de Trabajo también.

Otro argumento en contra del uso del patrón para una situación como las pruebas unitarias es que el patrón del repositorio es una abstracción permeable ya que las implementaciones más genéricas aprovechan IQueryable.

Los argumentos en contra del uso del patrón de repositorio tienen sentido para mí, pero los métodos alternativos de abstracción sugeridos son a menudo más confusos y parecen tan exagerados como el problema.

La solución de Jimmy Bogards parece ser una mezcla de volar las abstracciones, pero también presentar su propia arquitectura. https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

Otro ejemplo de repositorios innecesariamente ... ¡pero use mi arquitectura! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

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

No he encontrado un reemplazo claro o una alternativa al enfoque de patrón de repositorio "demasiado complejo" que no sea más arquitectónico en sí mismo.

Otro desarrollador
fuente
44
¿Qué estás tratando de lograr específicamente? Las abstracciones deben tener un propósito. Si está escribiendo una aplicación CRUD, un ORM probablemente sea lo suficientemente abstracto.
JacquesB
@JacquesB Estoy tratando de evitar problemas de impedancia relacional de objetos con un modelo de dominio robusto, pero también abstraigo eso de mis modelos de vista en una implementación de mvc.
AnotherDeveloper
Reed Cospey tiene muchas cosas positivas que decir sobre IQuerable aquí: stackoverflow.com/questions/1578778/using-iqueryable-with-linq Esto implica que es mejor en la capa de transporte. En cuanto a la abstracción, encontré que el uso del patrón de repositorio genérico funciona bien cuando necesitaba inyectar el EntityType pero aún quería mantener métodos comunes. Por otro lado, yo mismo he argumentado en el foro MSDN LINQ que EF es un patrón de repositorio porque está todo en la memoria. Un proyecto utilizó numerosas cláusulas Where como llamadas a métodos que funcionaron bien.
John Peters
Pero un área que analicé pero rechacé fue el negocio Expression Tree, a algunos realmente les gusta, pero ... hay que estudiarlo mucho antes de encontrarlo útil.
John Peters
2
deberíamos volver a llamar a SQL desde los controladores
bobek

Respuestas:

12

Creo que está combinando repositorios y repositorios genéricos.

Un repositorio básico solo interconecta su almacén de datos y proporciona métodos para devolver los datos

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

No filtra la capa de datos en su código a través de IQueryable u otras formas de pasar consultas aleatorias y proporciona una superficie de métodos inyectable y comprobable bien definida.

Un repositorio genérico le permite pasar su consulta como un ORM

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

Estoy de acuerdo en que no tiene mucho sentido usar un repositorio genérico encima de un ORM, que es básicamente otro repositorio genérico.

La respuesta es usar el patrón de repositorio básico para ocultar su ORM

Ewan
fuente
1
¿Un ORM de un ORM? Estaba tratando de ser graciosa. ¿Por qué necesitas abstraer el ORM?
johnny
1
La misma razón por la que abstraes algo. para evitar contaminar su código con clases propietarias
Ewan
6

La mayoría de los argumentos que menciona erróneamente se atribuyen a las características del patrón de Repositorio que no tiene.

Conceptualmente, un Repositorio como se definió originalmente en DDD es solo una colección de objetos en los que puede buscar o agregar. El mecanismo de persistencia detrás de esto se abstrae, por lo que como consumidor tienes la ilusión de que es una colección en memoria.

Una implementación de repositorio que tiene abstracciones con fugas (exponer, IQueryablespor ejemplo) es una implementación de repositorio deficiente.

Una implementación de repositorio que expone más que solo operaciones de recolección (por ejemplo, características de la Unidad de trabajo) es una implementación de repositorio deficiente.

¿Existen alternativas al repositorio para el acceso a datos? Sí, pero no están relacionados con los problemas que plantea en su pregunta.

guillaume31
fuente
1
Esto está en la línea de cómo respondí una pregunta similar en StackOverflow .
Eric King
1

Para mí, los repositorios, combinados con ORM u otras capas de persistencia DB, tienen estas desventajas:

  1. Encubrimiento de unidades de trabajo. El programador debe codificar UoW ​​y rara vez se puede implementar como una especie de magia en segundo plano, donde el usuario simplemente realiza consultas y modificaciones, sin definir los límites de UoW y posiblemente el punto de confirmación. A veces, los UoW se abandonan reduciéndolos a micro UoW (por ejemplo, sesiones de NHibernate) en cada método de acceso al Repositorio.
  2. Enmascarar o, en el peor de los casos, destruir la ignorancia de persistencia: métodos como "Load ()", "Get ()", "Save ()" o "Update ()" sugieren operaciones inmediatas de un solo objeto, como si se enviara un objeto individual SQL / DML, o como si estuviera trabajando con archivos. De hecho, por ejemplo, los métodos de NHibernate, con estos nombres engañosos, generalmente no hacen acceso individual, sino que ponen en cola la carga diferida o el lote de inserción / actualización (Ignorancia de persistencia). A veces, los programadores se preguntan por qué no obtienen operaciones inmediatas de base de datos y rompen por la fuerza la ignorancia de persistencia, lo que mata el rendimiento y utiliza grandes esfuerzos para empeorar el sistema (¡mucho!).
  3. Crecimiento descontrolado. Un repositorio simple podría acumular más y más métodos para satisfacer necesidades específicas.

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 Peligro de Dios: es posible que tengas la tentación de crear una clase de dios que cubra todo tu modelo o capa de acceso a datos. La clase de repositorio no solo contendría métodos Car, sino también métodos para todas las entidades.

En mi opinión, es mejor ofrecer al menos algunas oportunidades de consulta, para evitar el gran lío de muchos métodos de propósito único. No importa si es LINQ, un lenguaje de consulta propio o incluso algo tomado directamente del ORM (OK, tipo de problema de acoplamiento ...).

Erik Hart
fuente
44
¿A dónde van todos estos métodos si uno no usa el Patrón de repositorio?
johnny
1

Si el propósito de la interfaz del repositorio es simular la base de datos para una prueba unitaria (= prueba aislada), la mejor abstracción es algo que es fácil de burlar.

Es difícil simular una interfaz de repositorio basada en un resultado IQueryable.

Desde el punto de vista de las pruebas unitarias

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

se puede burlar fácilmente

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

se puede burlar fácilmente solo si el simulacro ignora el contenido del parámetro de consulta.

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

no puede ser burlado fácilmente

k3b
fuente
0

No creo que el patrón de repositorio sea excesivo, si usa funciones lambda para realizar consultas. Especialmente cuando tiene que abstraer el ORM (en mi opinión, siempre debería hacerlo), entonces no me importarán los detalles de implementación del repositorio en sí.

Por ejemplo:

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
fuente