Se il pattern del repository è eccessivo per i moderni ORM (EF, ibernazione), qual è una migliore astrazione?

11

Recentemente ho letto molti argomenti contro l'utilizzo del pattern di repository con potenti ORM come Entity Framework in quanto incorpora funzionalità simili a repository, insieme a funzionalità di unità di lavoro.

Un altro argomento contro l'utilizzo del modello per una situazione come la verifica di unità è che il modello di repository è un'astrazione che perde perché le implementazioni più generiche sfruttano IQueryable.

Gli argomenti contro l'uso del pattern del repository hanno senso per me, ma i metodi alternativi di astrazione suggeriti sono spesso più confusi e appaiono altrettanto eccentrici del problema.

La soluzione di Jimmy Bogards sembra essere un mix di soffiare via le astrazioni, ma anche di introdurre la propria architettura. link

Un altro esempio di repository inutilmente .... ma usa la mia architettura! link

Un altro ... link

Non ho trovato una chiara sostituzione o un'alternativa all'approccio del modello di repository "eccessivamente complesso" che non è più un architetto.

    
posta AnotherDeveloper 18.03.2016 - 21:57
fonte

5 risposte

11

Penso che tu stia confondendo repository e repository generici.

Un repository di base interfaccia semplicemente l'archivio dati e fornisce metodi per restituire i dati

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

Non perde il livello dati nel tuo codice tramite un IQueryable o altri modi per trasmettere query casuali e fornisce una superficie di metodi testabile e iniettabile ben definita.

Un repository generico consente di trasferire la query in modo molto simile a un ORM

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

Sono d'accordo che non c'è molto senso usare un Repository generico su un ORM che è fondamentalmente solo un altro Repository generico.

La risposta è usare il modello base del repository per nascondere il tuo ORM

    
risposta data 19.03.2016 - 17:12
fonte
5

La maggior parte degli argomenti che hai citato erroneamente attribuiscono alle caratteristiche del modello di repository che non ha.

Concettualmente, un repository come originariamente definito in DDD è solo una raccolta di oggetti che è possibile cercare o aggiungere. Il meccanismo di persistenza dietro di esso è astratto, così come un consumatore si ottiene l'illusione che si tratti di una raccolta in memoria.

Un'implementazione del repository che ha delle astrazioni che perdono (esponendo IQueryables per esempio) è una scarsa implementazione del repository.

Un'implementazione di un repository che espone oltre le sole operazioni di raccolta (ad esempio, le funzionalità di Unit of Work) è una scarsa implementazione del repository.

Ci sono alternative al deposito per l'accesso ai dati? Sì, ma non sono correlati ai problemi che hai sollevato nella tua domanda.

    
risposta data 25.03.2016 - 11:26
fonte
1

Per me, gli archivi, combinati con ORM o altri livelli di persistenza DB, hanno questi svantaggi:

  1. Coprire le unità di lavoro. UoW deve essere codificato dal programmatore e raramente può essere implementato come una sorta di magia in background, dove l'utente fa semplicemente query e modifiche, senza definire i limiti UoW e, eventualmente, il punto di commit. A volte, l'UoW viene abbandonato riducendoli in micro UoW (ad esempio sessioni di NHibernate) in ciascun metodo di accesso al repository.
  2. Coprire o, nel peggiore dei casi, distruggere Persistenza Ignoranza: metodi come "Load ()", "Get ()", "Save ()" o "Update ()" suggeriscono operazioni immediate, a singolo oggetto, come se si invia un singolo SQL / DML o se si lavora con i file. Infatti, ad esempio, i metodi di NHibernate, con questi nomi fuorvianti, di solito non rendono l'accesso individuale, ma accodano per il carico pigro o il batch di inserimento / aggiornamento (Persistenza Ignoranza). A volte, i programmatori si chiedono perché non ottengono operazioni DB immediate e violano forzatamente l'ignoranza della persistenza, uccidendo così le prestazioni e usando i maggiori sforzi per rendere il sistema (molto!) Peggio.
  3. Crescita incontrollata. Un repository semplice potrebbe accumulare sempre più metodi per soddisfare esigenze specifiche.

Ad esempio:

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. Oggetto di pericolo di Dio: potresti essere tentato di creare una classe di Dio, che copra tutto il tuo modello o livello di accesso ai dati. La classe repository non solo contiene i metodi Car, ma i metodi per tutte le entità.

A mio parere, è meglio offrire almeno alcune opportunità di query, per evitare l'immensa confusione di molti metodi a scopo singolo. Non importa se si tratta di LINQ, un proprio linguaggio di query o anche qualcosa preso direttamente dall'ORM (OK, tipo di problema di accoppiamento ...).

    
risposta data 07.04.2016 - 20:13
fonte
1

Se lo scopo dell'interfaccia del repository è deridere il database per un unittest (= test in isolamento) la migliore astrazione è qualcosa di facile da prendere in giro.

È difficile simulare un'interfaccia di repository basata su un risultato IQueryable.

Da un punto di vista dei test unitari

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

può essere facilmente deriso

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

può essere deriso facilmente solo se il mock ignora il contenuto del parametro di query.

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

non può essere facilmente deriso

    
risposta data 17.10.2017 - 11:10
fonte
0

Non penso che il pattern di repository sia eccessivo, se usi le funzioni lambda per l'interrogazione. Soprattutto quando devi astrarre l'ORM (secondo me dovrebbe sempre) quindi non mi interesseranno i dettagli di implementazione del repository stesso.

Ad esempio:

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; }
}
    
risposta data 19.03.2016 - 18:25
fonte