Al repository o non al repository


10

Quando ho saputo per la prima volta di Domain Driven Design, sono stato anche introdotto nel repository e nell'unità di schemi di lavoro che una volta sembravano essere il massimo per i ragazzi fantastici che hanno lanciato query SQL come cavemani contro i database. Più approfondivo ho approfondito l'argomento, più ho imparato che non sembrano più necessari a causa di ORM come EF e NHibernate che implementano sia unità di lavoro che repository in un'unica API, chiamata sessione o contesto.

Ora non sono sicuro di cosa fare. Al repository o non al repository. Capisco davvero l'argomentazione secondo cui tali astrazioni che perdono complicano solo le cose, pur non aggiungendo assolutamente nulla che possa semplificare l'accesso ai dati, tuttavia, non è giusto accoppiare ogni possibile aspetto della mia applicazione, ad esempio Entity Framework . Di solito, seguo alcune semplici linee guida:

  1. Il livello di dominio è il cuore del sistema, contenente entità, servizi, repository ...
  2. Il livello dell'infrastruttura fornisce implementazioni di interfacce di dominio di interesse infrastrutturale, ad esempio file, database, protocolli.
  3. Il livello applicazione ospita una radice di composizione che collega le cose e orchestra tutto.

Le mie soluzioni di solito si presentano così:

Domain.Module1
Domain.Module2
    IModule2Repo
    IModule2Service
    Module2
Infrastructure.Persistence
    Repositories
        EntityFrameworkRepositoryBase
MyApp
    Boostrapper
        -> inject EntityFrameworkRepositoryBase into IRepository etc.

Tengo pulito il mio livello di dominio usando un IRepository<'T>che è anche una preoccupazione del dominio, non dipende da qualcos'altro che mi dice come accedere ai dati. Quando ora farei una realizzazione concreta di IModule2Serviceciò che richiede l'accesso ai dati, dovrei iniettare DbContexte, in questo modo, accoppiarlo direttamente al livello dell'infrastruttura. ( Venendo al progetto Visual Studio, questo può finire davvero complicato a causa delle dipendenze circolari! )

Inoltre, quale può essere un'alternativa ai depositari e ai fucktons delle opere ? CQRS? Come si fa ad astrarre un quadro infrastrutturale puro?


1
A parte questo, se stai parlando di "Repository" come descritto in DDD, EF e nHibernate non sono repository. Certo, astraggono parzialmente il meccanismo di accesso ai dati, ma c'è molto di più in un repository di quello .
Eric King,

Risposte:


4

L'ingegneria è tutta una questione di compromessi. E così è lo sviluppo del software. In questo momento, credo solo un'altra opzione, che sarebbe più semplice, è quella di lavorare direttamente con ORM. Ma come hai detto, ciò potrebbe bloccarti in un quadro di persistenza specifico.

Quindi devi chiederti "La complessità aggiuntiva merita il disaccoppiamento del codice dalla persistenza". Ogni volta che sento persone dire che vogliono separare il loro codice dalla persistenza, chiedo "Quante volte nella tua carriera hai cambiato il tuo schema di persistenza?"

I problemi che vedo nei repository sono che rendono più difficili i cambiamenti comuni. Per esempio. aggiungendo un nuovo modo di interrogare un aggregato. E rendono le modifiche non comuni (presumibilmente) più facili. Per esempio. modifica dell'implementazione dei repository.

Poi c'è anche l'argomento di unit test, al quale dico "Se il framework di persistenza non ti consente di deridere un database, sia in memoria che localmente, non vale la pena usare il framework".


1
Il punto del repository è disaccoppiare l'app dalla persistenza e non c'è complessità. E cambio i dettagli di persistenza almeno una volta, perché l'accesso al db è l'ultima cosa che codice, fino a quel punto che sto usando nei repository di memoria. Inoltre, c'è una trappola molto sottile quando si utilizza direttamente un dettaglio di persistenza. I tuoi oggetti business tenderanno a essere progettati per essere compatibili con esso, anziché agnostici. E questo perché un'entità ricca e incapsulata correttamente non può essere ripristinata direttamente da alcun archivio (tranne la memoria) senza (alcune brutte) soluzioni alternative
MikeSW

Informazioni sull'aggiunta di un nuovo modo di interrogare una radice aggregata o qualsiasi entità, ecco perché è apparso CQRS. Personalmente, conservo il repository per scopi di dominio e utilizzo i gestori di query per le query effettive. E quei gestori sono strettamente collegati al db e spesso molto efficienti in quello che fanno.
MikeSW,

3

Sono un repository, anche se mi sono allontanato da schemi di repository generici. Al contrario, allinea i miei repository con la funzione aziendale che svolgono. I repository non mirano a sottrarre l'ORM, in quanto questo non è qualcosa che mi aspetto di cambiare, e allo stesso tempo evito di rendere un repository troppo granulare. (Vale a dire CRUD) Invece i miei repository servono da due a tre scopi chiave:

  • Recupero dei dati
  • Creazione dei dati
  • Dura cancellazione

Per il recupero dei dati, il repository restituisce sempre IQueryable<TEntity>. Per la creazione dei dati restituisce TEntity. Il repository gestisce il mio filtro a livello di base come lo stato "attivo" di autorizzazione per i sistemi che utilizzano schemi di eliminazione graduale e lo stato "corrente" per i sistemi che utilizzano dati storici. La creazione dei dati è responsabile solo per garantire che i riferimenti richiesti siano risolti e associati e che l'entità sia impostata e pronta per l'uso.

L'aggiornamento dei dati è di responsabilità della logica aziendale che lavora con le entità in questione. Questo può includere cose come le regole di validazione. Non provo a incapsularlo in un metodo di repository.

L'eliminazione nella maggior parte dei miei sistemi è soft-delete, quindi rientrerebbe nell'aggiornamento dei dati. (IsActive = false) Nel caso di cancellazioni forzate, questo sarebbe un one-liner nel repository.

Perché repository? Test-capacità. Certo, DbContext può essere deriso, ma è più semplice deridere una classe che ritornaIQueryable<TEntity>. Funzionano bene anche con il modello UoW, personalmente uso il modello DbContextScope di Mehdime per definire l'unità di lavoro al livello desiderato (Ie Controller in MVC) e consentire ai miei controller e alle classi di servizio di supporto di utilizzare i repository senza dover passare riferimenti a UoW / dbContext in giro. L'uso di IQueryable significa che non hai bisogno di molti metodi wrapper nel repository e il tuo codice può ottimizzare il modo in cui i dati verranno consumati. Ad esempio, il repository non ha bisogno di esporre metodi come "Esiste" o "Conta" o provare a racchiudere le entità con altri POCO nei casi in cui si desidera sottoinsiemi di dati. Non è nemmeno necessario gestire le opzioni di caricamento desideroso per i dati correlati che potrebbero essere necessari o meno. Passando IQueryable, il codice chiamante può:

.Any()
.Count()
.Include() // Generally avoided, instead I use .Select()
.Where()
.Select(x => new ViewModel or Anon. Type)
.Skip().Take()
.FirstOrDefault() / .SingleOrDefault() / .ToList()

Molto flessibile e da un PoV di prova il mio repository beffardo deve semplicemente restituire Elenchi di oggetti di entità popolata.

Per quanto riguarda i repository generici, mi sono allontanato da questi per la maggior parte perché quando si finisce con un repository per tabella i controller / servizi finiscono con riferimenti a diversi repository per eseguire un'azione aziendale. Nella maggior parte dei casi solo uno o due di questi repository eseguono effettivamente operazioni di scrittura (a condizione che si stiano utilizzando correttamente le proprietà di navigazione) mentre il resto supporta quelli con Reads. Preferirei avere qualcosa come un deposito ordini in grado di leggere e creare ordini e leggere eventuali ricerche pertinenti ecc. (Oggetti cliente leggeri, prodotti, ecc.) Come riferimento durante la creazione di un ordine, piuttosto che colpire 5 o 6 repository diversi. Potrebbe violare i puristi di DNRY, ma la mia tesi è che lo scopo del repository è quello di servire la creazione di ordini che includa i relativi riferimenti.Repository<Product>per ottenere prodotti dove per la base di un ordine ho solo bisogno di un'entità con una manciata di campi. Il mio OrderRepository potrebbe avere un .GetProducts()metodo di ritorno IQueryable<ProductSummary>che trovo migliore di uno Repository<Product>che finisce per avere diversi metodi "Get" per provare a servire diverse aree delle esigenze dell'applicazione e / o qualche espressione di filtro pass-in complessa.

Opto per un codice più semplice che è facile da seguire, testare e ottimizzare. Può essere maltrattato, ma preferirei avere qualcosa in cui gli abusi siano facili da individuare e correggere piuttosto che provare a "bloccare" il codice in un modo che non può essere abusato, fallire e quindi avere qualcosa che sia un incubo per arrivare effettivamente a fare ciò che il cliente paga per farlo alla fine. :)

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.