Wie verwende ich das Repository-Muster richtig?

86

Ich frage mich, wie ich meine Repositorys gruppieren soll. Wie aus den Beispielen, die ich auf dem asp.net mvc und in meinen Büchern gesehen habe, wird grundsätzlich ein Repository pro Datenbanktabelle verwendet. Aber das scheint eine Menge Repositories zu sein, die dazu führen, dass Sie später viele Repositories zum Verspotten und so weiter aufrufen müssen.

Also denke ich, ich sollte sie gruppieren. Ich bin mir jedoch nicht sicher, wie ich sie gruppieren soll.

Im Moment habe ich ein Registrierungs-Repository erstellt, um alle meine Registrierungsaufgaben zu erledigen. Es gibt jedoch ungefähr 4 Tabellen, die ich aktualisieren muss und bevor ich 3 Repositorys hatte, um dies zu tun.

Eine der Tabellen ist beispielsweise eine Lizenztabelle. Wenn sie sich registrieren, schaue ich auf ihren Schlüssel und überprüfe ihn, ob er in der Datenbank vorhanden ist. Was passiert nun, wenn ich diesen Lizenzschlüssel oder etwas anderes aus dieser Tabelle an einer anderen Stelle als der Registrierung überprüfen muss?

Ein Punkt könnte die Anmeldung sein (prüfen Sie, ob der Schlüssel nicht abgelaufen ist).

Was würde ich in dieser Situation tun? Code erneut umschreiben (DRY brechen)? Versuchen Sie, diese beiden Repositorys zusammenzufassen, und hoffen Sie, dass keine der Methoden zu einem anderen Zeitpunkt benötigt wird (z. B. könnte ich eine Methode haben, die überprüft, ob Benutzername verwendet wird - vielleicht brauche ich das woanders).

Auch wenn ich sie zusammenführe, würde ich entweder 2 Service-Layer benötigen, die in dasselbe Repository gehen, da ich denke, dass die gesamte Logik für 2 verschiedene Teile einer Site lang wäre und ich Namen wie ValidateLogin (), ValdiateRegistrationForm () haben müsste. , ValdiateLoginRetrievePassword () und etc.

Oder rufen Sie das Repository trotzdem an und haben Sie nur einen seltsam klingenden Namen?

Es scheint nur schwierig zu sein, ein Repository zu erstellen, dessen Name allgemein genug ist, damit Sie es für viele Stellen Ihrer Anwendung verwenden können und dennoch sinnvoll sind. Ich denke nicht, dass das Aufrufen eines anderen Repositorys in einem Repository eine gute Vorgehensweise wäre.

chobo2
quelle
10
+1. Gute Frage.
Griegs
Danke, es nervt mich schon seit einiger Zeit. Da ich finde, dass die, die ich in dem Buch gesehen habe, zu einfach sind, zeigen sie Ihnen nicht, was Sie in diesen Situationen tun sollen.
Chobo2
Ich mache im Grunde das Gleiche wie Sie und ja, wenn ich eine linq2sql-Klasse habe, die in mehr als einem Repository verwendet wird, und ich die Tabellenstruktur ändern muss, unterbreche ich DRY. Weniger als ideal. Ich plane es jetzt ein wenig besser, damit ich eine linq2sql-Klasse nicht mehr als einmal verwenden muss, was meiner Meinung nach eine gute Trennung der Bedenken darstellt, aber ich sehe einen Tag voraus, an dem dies ein echtes Problem für mich sein wird.
Griegs
Ich habe hier eine ähnliche Frage gestellt (aber nicht identisch): stackoverflow.com/questions/910156/…
Grokys
Ja, aber es scheint schwierig, es für alle Situationen zu planen. Wie gesagt, ich kann vielleicht die Anmeldung und Registrierung zu einer Authentifizierung zusammenführen und habe 2 separate Ebenen. Das wird wahrscheinlich mein Problem lösen. Aber was passiert, wenn ich ihnen auf der Profilseite meiner Website ihren Schlüssel aus irgendeinem Grund zeigen möchte (vielleicht lasse ich sie ihn ändern oder so). Was mache ich jetzt, um DRY zu brechen und dasselbe zu schreiben? Oder versuchen Sie, ein Repository zu erstellen, in das alle drei Tabellen mit einem guten Namen passen.
Chobo2

Antworten:

41

Eine Sache, die ich falsch gemacht habe, als ich mit dem Repository-Muster herumgespielt habe - genau wie Sie dachte ich, dass sich die Tabelle auf das Repository 1: 1 bezieht. Wenn wir einige Regeln aus Domain Driven Design anwenden, verschwindet das Problem der Gruppierung von Repositorys häufig.

Das Repository sollte sich im Aggregatstamm und nicht in der Tabelle befinden. Dies bedeutet, dass - wenn eine Entität nicht alleine leben sollte (dh wenn Sie eine Person haben, die Registrantinsbesondere teilnimmt Registration) - es sich nur um eine Entität handelt, kein Repository benötigt, sondern über das Repository des aggregierten Stamms aktualisiert / erstellt / abgerufen werden sollte gehört.

Natürlich kann in vielen Fällen diese Technik zum Reduzieren der Anzahl von Repositorys (eigentlich - es ist eher eine Technik zum Strukturieren Ihres Domänenmodells) nicht angewendet werden, da jede Entität eine aggregierte Wurzel sein soll (die stark von Ihrer Domäne abhängt). Ich kann nur blinde Vermutungen anstellen. In Ihrem Beispiel - Licensescheint eine aggregierte Wurzel zu sein, da Sie sie ohne RegistrationEntitätskontext überprüfen müssen müssen .

Dies beschränkt uns jedoch nicht auf kaskadierte Repositorys (das RegistrationRepository darf Licensebei Bedarf auf das Repository verweisen ). Dies beschränkt uns nicht darauf, das LicenseRepository (vorzugsweise über IoC) direkt vom RegistrationObjekt aus zu referenzieren .

Versuchen Sie einfach, Ihr Design nicht durch Komplikationen durch Technologien oder Missverständnisse voranzutreiben. Das Gruppieren von Repositorys, ServiceXnur weil Sie keine zwei Repositorys erstellen möchten, ist keine gute Idee.

Viel besser wäre es, ihm einen richtigen Namen zu geben - RegistrationServicedh

Dienste sollten jedoch generell vermieden werden - sie sind häufig Ursachen, die zu einem anämischen Domänenmodell führen .

BEARBEITEN:
Beginnen Sie mit der Verwendung von IoC. Es lindert wirklich den Schmerz, Abhängigkeiten zu injizieren.
Anstatt zu schreiben:

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

Sie können schreiben:

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

Ps Wäre besser, den sogenannten Common Service Locator zu verwenden, aber das ist nur ein Beispiel.

Arnis Lapsa
quelle
17
Hahaha ... Ich gebe Ratschläge zur Verwendung von Service Locator. Ich freue mich immer zu sehen, wie dumm ich in der Vergangenheit war.
Arnis Lapsa
1
@Arnis Es hört sich so an, als hätten sich Ihre Meinungen geändert - ich bin interessiert, wie würden Sie diese Frage jetzt anders beantworten?
ngm
10
@ngm viel hat sich geändert, seit ich darauf geantwortet habe. Ich stimme immer noch zu, dass die aggregierte Wurzel Transaktionsgrenzen ziehen sollte (als Ganzes gespeichert werden soll), aber ich bin viel weniger optimistisch, die Persistenz mithilfe des Repository-Musters zu abstrahieren. In letzter Zeit verwende ich ORM nur direkt, weil Dinge wie eifriges / faules Lademanagement zu umständlich sind. Es ist viel vorteilhafter, sich auf die Entwicklung eines Rich-Domain-Modells zu konzentrieren, als sich auf die Abstraktion der Persistenz zu konzentrieren.
Arnis Lapsa
2
@ Entwickler nein, nicht genau. Sie sollten immer noch hartnäckig sein. Sie rufen das aggregierte Stammverzeichnis von außen ab und rufen die Methode auf, die den Job ausführt. Mein Domain-Modell hat keine Referenzen, nur Standard-.net-Framework-Referenzen. Um dies zu erreichen, müssen Sie über ein umfangreiches Domänenmodell und Tools verfügen, die intelligent genug sind (NHibernate erledigt den Trick).
Arnis Lapsa
1
Andererseits. Wenn Sie mehr als ein Aggregat abrufen, können Sie häufig die PK oder die Indizes verwenden. Viel schneller als die von EF generierten Beziehungen zu nutzen. Je kleiner die Root-Aggregate sind, desto einfacher ist die Leistung.
Jgauffin
5

Eine Sache, die ich begonnen habe, um dies zu beheben, ist die Entwicklung von Diensten, die N Repositorys umschließen. Hoffentlich können Ihre DI- oder IoC-Frameworks dazu beitragen, dies zu vereinfachen.

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

Ist das sinnvoll? Ich verstehe auch, dass das Sprechen von Diensten in diesem Herrenhaus möglicherweise tatsächlich den DDD-Grundsätzen entspricht oder nicht. Ich mache es einfach, weil es zu funktionieren scheint.

neouser99
quelle
1
Nein, das macht nicht viel Sinn. Ich verwende derzeit keine DI- oder IoC-Frameworks, da ich so wie es ist genug auf meinem Teller habe.
Chobo2
1
Wenn Sie Ihre Repos mit nur einem neuen () instanziieren können, können Sie dies versuchen ... public ServiceImpl (): this (neues Repo1, neues Repo2 ...) {} als zusätzlichen Konstruktor im Service.
neouser99
Abhängigkeitsspritze? Ich mache das schon, bin mir aber immer noch nicht sicher, was dein Code ist und was er löst.
Chobo2
Ich gehe speziell nach dem Zusammenführen von Repositories. Welcher Code macht keinen Sinn? Wenn Sie DI verwenden, funktioniert der Code in der Antwort in dem Sinne, dass Ihr DI-Framework diese IRepo-Dienste einfügt. Im Kommentarcode ist es im Grunde nur eine kleine Umgehung, DI auszuführen (im Grunde genommen injiziert Ihr Konstruktor ohne Parameter ". diese Abhängigkeiten in Ihr ServiceImpl).
neouser99
Ich benutze DI bereits, damit ich Unit-Tests besser durchführen kann. Mein Problem ist, dass, wenn Sie Ihre Service-Schichten und Repos mit detaillierten Namen erstellen. Wenn Sie sie dann irgendwo anders verwenden müssen, sieht es seltsam aus, wenn Sie den RegistrationService-Layer aufrufen, der den RegRepo in einer anderen Klasse aufruft, beispielsweise die ProfileClass. Ich sehe also nicht von Ihnen als Beispiel, was Sie voll und ganz tun. Wenn Sie anfangen, zu viele Repos in derselben Service-Schicht zu haben, werden Sie so viel unterschiedliche Geschäftslogik und Validierungslogik haben. Da in der Service-Schicht in der Regel Validierungslogik. So viele brauche ich mehr ...
chobo2
2

Ich habe eine abstrakte Basisklasse, die wie folgt definiert ist:

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);
}

Sie können dann Ihr Repository von der abstrakten Basisklasse ableiten und Ihr einzelnes Repository erweitern, solange sich die generischen Argumente beispielsweise unterscheiden.

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

etc....

Ich benötige die separaten Repositorys, da wir für einige unserer Repositorys Einschränkungen haben und dies uns maximale Flexibilität gibt. Hoffe das hilft.

Michael Mann
quelle
Sie versuchen also, ein generisches Repository zu erstellen, um all dies zu bewältigen?
Chobo2
Also, was geht eigentlich in die Update-Methode. Wie Sie dieses V-Update mögen und es in einem T entitytoUpdate übergeben wird, aber es gibt keinen Code, der es tatsächlich aktualisiert, oder gibt es?
Chobo2
Ja. Die Aktualisierungsmethode enthält Code, da Sie eine Klasse schreiben, die vom generischen Repository abstammt. Der Implementierungscode kann Linq2SQL oder ADO.NET sein oder was auch immer Sie als Implementierungstechnologie für den Datenzugriff ausgewählt haben
Michael Mann,
2

Ich habe dies als meine Repository-Klasse und ja, ich erweitere das Tabellen- / Bereichs-Repository, aber manchmal muss ich DRY trotzdem brechen.

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>(); }
        }

    }
}

BEARBEITEN

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 )
    {
    }

Ich habe auch einen Datenkontext, der mit der Klasse Linq2SQL.dbml erstellt wurde.

Jetzt habe ich ein Standard-Repository, das Standardaufrufe wie All and Find implementiert, und in meinem AdminRepository habe ich bestimmte Aufrufe.

Beantwortet die Frage von DRY nicht, obwohl ich nicht denke.

Griegs
quelle
Wofür "Repository"? und wie CreateInstance? Ich bin mir nicht sicher, was alles, was du hast, tut.
Chobo2
Es ist generisch wie alles. Grundsätzlich benötigen Sie ein bestimmtes Repository für Ihren (Bereich). Überprüfen Sie die Bearbeitung oben für mein AdminRepository.
Griegs
1

Hier ist ein Beispiel für eine generische Repository-Implementierung mit FluentNHibernate. Es kann jede Klasse beibehalten, für die Sie einen Mapper geschrieben haben. Es ist sogar in der Lage, Ihre Datenbank basierend auf den Mapper-Klassen zu generieren.

James Jones
quelle
1

Das Repository-Muster ist ein schlechtes Entwurfsmuster. Ich arbeite mit vielen alten .Net-Projekten und dieses Muster verursacht normalerweise "Distributed Transactions" -, "Partial Rollback" - und "Connection Pool Exhausted" -Fehler, die vermieden werden könnten. Das Problem ist, dass das Muster versucht, Verbindungen und Transaktionen intern zu verarbeiten, diese sollten jedoch auf der Controller-Ebene behandelt werden. Auch EntityFramework abstrahiert bereits einen Großteil der Logik. Ich würde vorschlagen, stattdessen das Dienstmuster zu verwenden, um gemeinsam genutzten Code wiederzuverwenden.

ColacX
quelle
0

Ich empfehle Ihnen, sich Sharp Architecture anzuschauen . Sie schlagen vor, ein Repository pro Entität zu verwenden. Ich verwende es derzeit in meinem Projekt und bin sehr zufrieden mit den Ergebnissen.

Schlau
quelle
Ich würde hier eine +1 geben, aber er hat angegeben, dass DI- oder IoC-Container keine Option sind (vorausgesetzt, dies sind nicht die einzigen Vorteile von Sharp Arch). Ich vermute, es gibt einige zu viele vorhandene Codes, an denen er arbeitet.
neouser99
Was ist eine Einheit? Ist das die gesamte Datenbank? oder ist das eine Datenbanktabelle? DI- oder IoC-Container sind derzeit keine Option, da ich nicht nur die anderen 10 Dinge lernen möchte, die ich gleichzeitig lerne. Sie sind etwas, das ich in meiner nächsten Überarbeitung meiner Website oder meines nächsten Projekts untersuchen werde. Auch wenn ich nicht sicher bin, ob dies ein kurzer Blick auf die Website sein wird und es scheint, dass Sie nhirbrate verwenden sollen, verwende ich derzeit linq to sql.
Chobo2