Come utilizzare correttamente il pattern del repository?


86

Mi chiedo come dovrei raggruppare i miei repository? Come dagli esempi che ho visto su asp.net mvc e nei miei libri usano fondamentalmente un repository per tabella di database. Ma sembra che molti repository ti portino a dover richiamare molti repository in seguito per derisioni e cose del genere.

Quindi immagino che dovrei raggrupparli. Tuttavia non sono sicuro di come raggrupparli.

In questo momento ho creato un repository di registrazione per gestire tutte le mie cose di registrazione. Tuttavia ci sono tipo 4 tabelle che devo aggiornare e prima avevo 3 repository per farlo.

Ad esempio una delle tabelle è una tabella delle licenze. Quando si registrano, guardo la loro chiave e la controllo per vedere se esiste nel database. Ora cosa succede se devo controllare questa chiave di licenza o qualcos'altro da quel tavolo in un altro punto diverso dalla registrazione?

Un punto potrebbe essere il login (controlla se la chiave non è scaduta).

Allora cosa farei in questa situazione? Riscrivi di nuovo il codice (interrompi DRY)? Prova a unire questi 2 repository insieme e spera che nessuno dei metodi sia necessario in un altro momento (come forse potrei avere un metodo che controlla se userName è usato - forse ne avrò bisogno da qualche altra parte).

Inoltre, se li unissi insieme avrei bisogno di 2 livelli di servizio che vadano nello stesso repository poiché penso che avere tutta la logica per 2 parti diverse di un sito sarebbe lungo e dovrei avere nomi come ValidateLogin (), ValdiateRegistrationForm () , ValdiateLoginRetrievePassword () e così via

O chiama comunque il Repository e hai solo un nome strano in giro?

Sembra solo difficile creare un repository che abbia un nome abbastanza generico in modo da poterlo utilizzare per molti punti della tua applicazione e avere ancora senso e non penso che chiamare un altro repository in un repository sarebbe una buona pratica?


11
+1. Ottima domanda.
griegs

Grazie, mi infastidisce da un po 'di tempo. Poiché trovo che quelli che ho visto nel libro siano troppo semplici, non ti mostrano cosa fare in queste situazioni.
chobo2

Fondamentalmente sto facendo lo stesso di te e sì, se ho una classe linq2sql utilizzata in più di 1 repository e ho bisogno di cambiare la struttura della tabella, rompo DRY. Meno che ideale. Ora lo pianifico un po 'meglio, quindi non ho bisogno di usare una classe linq2sql più di una volta, il che immagino sia una buona separazione delle preoccupazioni, ma prevedo un giorno in cui questo sarà un vero problema per me.
griegs il

Ho fatto una domanda simile (ma non identico) qui: stackoverflow.com/questions/910156/...
Grokys

Sì, ma sembra difficile pianificarlo per tutte le situazioni. Come ho detto, posso unire forse il login e la registrazione in un'autenticazione e avere 2 livelli separati. Questo probabilmente risolverà il mio problema. Ma cosa succede se nella pagina del profilo del mio sito voglio mostrare loro la loro chiave per qualsiasi motivo (forse lascerò che htem la cambi o qualcosa del genere). Ora cosa faccio rompere DRY e scrivere la stessa cosa? Oppure prova a creare un repository che possa in qualche modo adattare tutte e 3 queste tabelle con un buon nome.
chobo2

Risposte:


41

Una cosa che ho sbagliato quando ho giocato con il pattern del repository - proprio come te, pensavo che la tabella si riferisse al repository 1: 1. Quando applichiamo alcune regole dal Domain Driven Design, il problema del raggruppamento dei repository spesso scompare.

Il repository dovrebbe essere per radice aggregata e non per tabella. Significa che - se l'entità non dovesse vivere da sola (cioè - se hai una Registrantche partecipa in particolare Registration) - è solo un'entità, non ha bisogno di un repository, dovrebbe essere aggiornato / creato / recuperato attraverso il repository di root aggregato appartiene.

Ovviamente, in molti casi, questa tecnica di riduzione del numero di repository (in realtà, è più una tecnica per strutturare il modello di dominio) non può essere applicata perché si suppone che ogni entità sia una radice aggregata (che dipende molto dal tuo dominio, Posso fornire solo ipotesi cieche). Nel tuo esempio, Licensesembra essere una radice aggregata perché devi essere in grado di controllarli senza contesto di Registrationentità.

Ma questo non ci limita ai repository a cascata (il Registrationrepository può fare riferimento al Licenserepository se necessario). Ciò non ci limita a fare riferimento al Licenserepository (preferibile - tramite IoC) direttamente Registrationdall'oggetto.

Cerca solo di non guidare il tuo progetto attraverso complicazioni fornite dalle tecnologie o fraintendimenti su qualcosa. Raggruppare i repository ServiceXsolo perché non vuoi costruire 2 repository non è una buona idea.

Sarebbe molto meglio dargli un nome proprio, ad RegistrationServicees

Ma i servizi dovrebbero essere evitati in generale: spesso sono causa che porta a un modello di dominio anemico .

EDIT:
inizia a utilizzare IoC. Allevia veramente il dolore di iniettare dipendenze.
Invece di scrivere:

var registrationService = new RegistrationService(new RegistrationRepository(),  
      new LicenseRepository(), new GodOnlyKnowsWhatElseThatServiceNeeds());

potrai scrivere:

var registrationService = IoC.Resolve<IRegistrationService>();

Ps Sarebbe meglio usare il cosiddetto localizzatore di servizi comuni ma questo è solo un esempio.


17
Hahaha ... Sto dando consigli per usare il localizzatore di servizi. È sempre una gioia vedere quanto sono stato stupido in passato.
Arnis Lapsa

1
@Arnis Sembra che le tue opinioni siano cambiate - Mi interessa, come risponderesti a questa domanda in modo diverso ora?
ngm

10
@ngm sono cambiate molte cose da quando ho risposto. Sono ancora d'accordo sul fatto che la radice aggregata dovrebbe disegnare i confini transazionali (essere salvata nel suo insieme), ma sono molto meno ottimista sull'astrazione della persistenza usando il modello di repository. Ultimamente, sto usando ORM direttamente, perché cose come la gestione del caricamento avido / pigro sono troppo imbarazzanti. È molto più vantaggioso concentrarsi sullo sviluppo di un ricco modello di dominio invece di concentrarsi sull'astrazione della persistenza.
Arnis Lapsa

2
@Developer no, non esattamente. dovrebbero ancora essere ignoranti per la persistenza. recuperi la radice aggregata dall'esterno e chiami il metodo su di essa che fa il lavoro. il mio modello di dominio non ha riferimenti, solo quelli standard .net framework. per ottenere ciò, è necessario disporre di un modello di dominio ricco e di strumenti sufficientemente intelligenti (NHibernate fa il trucco).
Arnis Lapsa

1
Anzi. Il recupero di più di un ggregate spesso significa che è possibile utilizzare PK o indici. Molto più velocemente che sfruttare le relazioni che EF genera. Più piccoli sono gli aggregati radice, più facile sarà ottenere prestazioni su di essi.
jgauffin

5

Una cosa che ho iniziato a fare per risolvere questo problema è sviluppare effettivamente servizi che racchiudono N repository. Si spera che i tuoi framework DI o IoC possano aiutarti a renderlo più semplice.

public class ServiceImpl {
    public ServiceImpl(IRepo1 repo1, IRepo2 repo2...) { }
}

Ha senso? Inoltre, capisco che parlare di servizi in questo maniero possa o meno essere conforme ai principi DDD, lo faccio solo perché sembra funzionare.


1
No, non ha molto senso. Al momento non uso framework DI o IoC perché ne ho abbastanza per così com'è.
chobo2

1
Se puoi istanziare i tuoi repository con solo new (), potresti provare questo ... public ServiceImpl (): this (new Repo1, new Repo2 ...) {} come costruttore aggiuntivo nel servizio.
neouser99

Iniezione di dipendenza? Lo faccio già, ma non sono ancora sicuro di quale sia il tuo codice e cosa risolva.
chobo2,

Sto specificatamente andando dopo la fusione dei repository. Quale codice non ha senso? Se stai usando DI, il codice nella risposta funzionerà nel senso che il tuo framework DI inietterà quei servizi IRepo, nel codice dei commenti è fondamentalmente solo un piccolo lavoro per fare DI (fondamentalmente il tuo costruttore senza parametri 'inietta' quelle dipendenze nel tuo ServiceImpl).
neouser99

Uso già DI in modo da poter eseguire un test unitario meglio. Il mio problema è che se crei i tuoi livelli di servizio e repository con nomi dettagliati. Quindi, se hai mai bisogno di usarli da qualche altra parte, sembrerà strano chiamare come il livello di RegistrationService che chiama il RegRepo in qualche altra classe come il ProfileClass. Quindi non vedo dal tuo esempio cosa stai facendo completamente. Ad esempio, se inizi ad avere troppi repository nello stesso livello di servizio, avrai una logica di business e una logica di convalida molto diverse. Dal momento che nel livello di servizio di solito inserisci la logica di convalida. Così tanti ho bisogno di più ...
chobo2

2

Quello che sto facendo è avere una classe base astratta definita come segue:

public abstract class ReadOnlyRepository<T,V>
{
     V Find(T lookupKey);
}

public abstract class InsertRepository<T>
{
     void Add(T entityToSave);
}

public abstract class UpdateRepository<T,V>
{
     V Update(T entityToUpdate);
}

public abstract class DeleteRepository<T>
{
     void Delete(T entityToDelete);
}

Puoi quindi derivare il tuo repository dalla classe base astratta ed estendere il tuo singolo repository fintanto che gli argomenti generici differiscono per esempio;

public class RegistrationRepository: ReadOnlyRepository<int, IRegistrationItem>,
                                     ReadOnlyRepository<string, IRegistrationItem> 

eccetera....

Ho bisogno dei repository separati perché abbiamo restrizioni su alcuni dei nostri repository e questo ci offre la massima flessibilità. Spero che sia di aiuto.


Quindi stai cercando di creare un repository generico per gestire tutto questo?
chobo2

Quindi, cosa significa effettivamente il metodo Update. Ti piace questo aggiornamento V e passa in un T entitytoUpdate ma non c'è alcun codice che lo aggiorni effettivamente o c'è?
chobo2,

Sì .. Ci sarà codice nel metodo di aggiornamento perché scriverete una classe che discende dal repository generico. Il codice di implementazione può essere Linq2SQL o ADO.NET o qualunque cosa tu abbia scelto come tecnologia di implementazione dell'accesso ai dati
Michael Mann,

2

Ho questo come classe di repository e sì, estendo nel repository table / area ma a volte devo rompere DRY.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MvcRepository
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected System.Data.Linq.DataContext _dataContextFactory;

        public IQueryable<T> All()
        {
            return GetTable.AsQueryable();
        }

        public IQueryable<T> FindAll(Func<T, bool> exp)
        {
            return GetTable.Where<T>(exp).AsQueryable();
        }

        public T Single(Func<T, bool> exp)
        {
            return GetTable.Single(exp);
        }

        public virtual void MarkForDeletion(T entity)
        {
            _dataContextFactory.GetTable<T>().DeleteOnSubmit(entity);
        }

        public virtual T CreateInstance()
        {
            T entity = Activator.CreateInstance<T>();
            GetTable.InsertOnSubmit(entity);
            return entity;
        }

        public void SaveAll()
        {
            _dataContextFactory.SubmitChanges();
        }

        public Repository(System.Data.Linq.DataContext dataContextFactory)
        {
            _dataContextFactory = dataContextFactory;
        }

        public System.Data.Linq.Table<T> GetTable
        {
            get { return _dataContextFactory.GetTable<T>(); }
        }

    }
}

MODIFICARE

public class AdminRepository<T> : Repository<T> where T: class
{
    static AdminDataContext dc = new AdminDataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MY_ConnectionString"].ConnectionString);

    public AdminRepository()
        : base( dc )
    {
    }

Ho anche un datacontext che è stato creato utilizzando la classe Linq2SQL.dbml.

Quindi ora ho un repository standard che implementa chiamate standard come All e Find e nel mio AdminRepository ho chiamate specifiche.

Non risponde alla domanda di DRY anche se non credo.


A cosa serve "Repository"? e come CreateInstance? Non sono sicuro di cosa stia facendo tutto quello che hai.
chobo2

È generico come qualsiasi cosa. Fondamentalmente devi avere un repository specifico per la tua (area). Controlla la modifica sopra per il mio AdminRepository.
Griegs il

1

Di seguito è riportato un esempio di un'implementazione generica del repository utilizzando FluentNHibernate. È in grado di mantenere qualsiasi classe per cui hai scritto un mappatore. È anche in grado di generare il tuo database in base alle classi mapper.


1

Il modello del repository è un modello di progettazione errato. Lavoro con molti vecchi progetti .Net e questo pattern tipicamente causa errori di "Transazioni distribuite", "Rollback parziale" e "Connection Pool Exhausted" che potrebbero essere evitati. Il problema è che il pattern cerca di gestire internamente le connessioni e le transazioni, ma queste dovrebbero essere gestite a livello del controller. Anche EntityFramework astrae già molta della logica. Suggerirei di utilizzare il modello di servizio invece di riutilizzare il codice condiviso.


0

Ti suggerisco di guardare Sharp Architecture . Suggeriscono di utilizzare un repository per entità. Lo sto usando attualmente nel mio progetto e sono molto soddisfatto dei risultati.


Darei un +1 qui, ma ha indicato che i contenitori DI o IoC non sono proprio un'opzione (ammesso che non siano gli unici vantaggi di Sharp Arch). Immagino che ci sia un po 'di codice esistente su cui sta lavorando.
neouser99

Cosa è considerato un'entità? È l'intero database? o è una tabella di database? I contenitori DI o IoC non sono un'opzione in questo momento poiché semplicemente non voglio imparare questo in cima alle altre 10 cose che sto imparando allo stesso tempo. sono qualcosa che esaminerò nella mia prossima revisione del mio sito o nel mio prossimo progetto. Anche se non sono sicuro se sarà questo un rapido sguardo al sito e sembra che tu usi nhirbrate e io sto usando da linq a sql in questo momento.
chobo2 il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.