wake-up-neo.net

Repositorys zu aggregierten Roots reduzieren

Ich habe derzeit für fast jede Tabelle in der Datenbank ein Repository und möchte mich weiter an DDD ausrichten, indem ich sie nur auf aggregierte Wurzeln reduziere.

Nehmen wir an, ich habe die folgenden Tabellen, User und Phone. Jeder Benutzer verfügt möglicherweise über ein oder mehrere Telefone. Ohne den Begriff der Gesamtwurzel könnte ich so etwas tun:

//assuming I have the userId in session for example and I want to update a phone number
List<Phone> phones = PhoneRepository.GetPhoneNumberByUserId(userId);
phones[0].Number = “911”;
PhoneRepository.Update(phones[0]);

Das Konzept der Aggregatwurzeln ist auf dem Papier leichter zu verstehen als in der Praxis. Ich werde niemals Telefonnummern haben, die keinem Benutzer gehören. Wäre es also sinnvoll, das PhoneRepository abzuschaffen und telefonbezogene Methoden in das UserRepository zu integrieren? Angenommen, die Antwort lautet "Ja". Ich werde das vorherige Codebeispiel neu schreiben.

Darf ich im UserRepository eine Methode haben, die Telefonnummern zurückgibt? Oder sollte immer ein Verweis auf einen Benutzer zurückgegeben werden und dann die Beziehung durch den Benutzer durchlaufen werden, um zu den Telefonnummern zu gelangen:

List<Phone> phones = UserRepository.GetPhoneNumbers(userId);
// Or
User user = UserRepository.GetUserWithPhoneNumbers(userId); //this method will join to Phone

Unabhängig davon, wie ich die Telefone erwerbe, vorausgesetzt, ich habe eines davon modifiziert. Wie gehe ich vor, um sie zu aktualisieren? Mein begrenztes Verständnis ist, dass Objekte unter der Wurzel durch die Wurzel aktualisiert werden sollten. Obwohl dies mit Entity Framework perfekt funktionieren wird, scheint dies äußerst unbeschreiblich zu sein, da ich beim Lesen des Codes keine Ahnung habe, was ich eigentlich aktualisiere, obwohl Entity Framework geänderte Objekte innerhalb des Diagramms verfolgt.

UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);

Angenommen, ich habe mehrere Nachschlagetabellen, die nicht wirklich an irgendetwas gebunden sind, wie CountryCodes, ColorsCodes, SomethingElseCodes. Ich könnte sie verwenden, um Dropdowns zu bevölkern oder aus irgendeinem anderen Grund. Sind diese eigenständigen Repositories? Können sie zu einer Art logischen Gruppierung/Repository wie CodesRepository kombiniert werden? Oder ist das gegen Best Practices.

82
e36M3

In Ihrem Repository dürfen Sie eine beliebige Methode haben :) In beiden Fällen, die Sie erwähnen, ist es sinnvoll, den Benutzer mit der Telefonliste zurückzusenden. Normalerweise wird das Benutzerobjekt nicht vollständig mit allen Unterinformationen (beispielsweise allen Adressen und Telefonnummern) gefüllt, und es gibt möglicherweise unterschiedliche Methoden, um das Benutzerobjekt mit unterschiedlichen Informationen zu füllen. Dies wird als Lazy Loading bezeichnet.

User GetUserDetailsWithPhones()
{
    // Populate User along with Phones
}

Bei der Aktualisierung wird in diesem Fall der Benutzer aktualisiert, nicht die Telefonnummer selbst. Das Speichermodell speichert die Telefone möglicherweise in einer anderen Tabelle. Auf diese Weise denken Sie, dass nur die Telefone aktualisiert werden. Dies ist jedoch nicht der Fall, wenn Sie aus DDD-Perspektive denken. Was die Lesbarkeit angeht, so ist zwar die Zeile

UserRepository.Update(user)

alleine vermittelt nicht, was aktualisiert wird, der Code oben macht deutlich, was aktualisiert wird. Außerdem ist es höchstwahrscheinlich Teil eines Front-End-Methodenaufrufs, der die Aktualisierungen deutlich machen kann.

Für die Nachschlagetabellen und eigentlich sogar sonst ist es sinnvoll, über GenericRepository zu verfügen und dieses zu verwenden. Das benutzerdefinierte Repository kann vom GenericRepository erben.

public class UserRepository : GenericRepository<User>
{
    IEnumerable<User> GetUserByCustomCriteria()
    {
    }

    User GetUserDetailsWithPhones()
    {
        // Populate User along with Phones
    }

    User GetUserDetailsWithAllSubInfo()
    {
        // Populate User along with all sub information e.g. phones, addresses etc.
    }
}

Suchen Sie nach Generic Repository Entity Framework und Sie würden viele Nice-Implementierungen durchführen. Verwenden Sie eine davon oder schreiben Sie Ihre eigene.

11
amit_g

Ihr Beispiel für das Aggregate-Root-Repository ist perfekt. Dh jede Entität, die nicht vernünftigerweise ohne Abhängigkeit von einem anderen existieren kann, sollte kein eigenes Repository (in Ihrem Fall Phone) haben. Ohne diese Überlegung können Sie sich schnell mit einer Explosion von Repositorys in einer 1-1-Zuordnung zu Db-Tabellen befassen.

Sie sollten sich eher mit dem Unit of Work-Muster für Datenänderungen als mit den Repositorys selbst beschäftigen, da sie meiner Meinung nach Verwirrung verursachen, wenn es darum geht, Änderungen an der Datenbank zu speichern. In einer EF-Lösung ist die Unit of Work im Wesentlichen ein Schnittstellen-Wrapper für Ihren EF-Kontext.

In Bezug auf Ihr Repository für Suchdaten erstellen wir einfach ein ReferenceDataRepository, das für Daten verantwortlich ist, die nicht speziell zu einer Domäneneinheit gehören (Länder, Farben usw.).

9
Darren Lewis

Wenn das Telefon ohne Benutzer keinen Sinn ergibt, handelt es sich um eine Entität (wenn Sie sich um ihre Identität kümmern) oder um ein Wertobjekt. Es sollte immer durch den Benutzer geändert und gemeinsam abgerufen/aktualisiert werden.

Stellen Sie sich Aggregatwurzeln als Kontext-Definierer vor. Sie zeichnen lokale Kontexte, befinden sich jedoch selbst im globalen Kontext (Ihre Anwendung).

Wenn Sie dem domänengesteuerten Design folgen, sollten Repositorys 1: 1 pro Aggregatstamm sein.
Keine Ausreden.

Ich wette, das sind Probleme, mit denen Sie konfrontiert sind:

  • technische Schwierigkeiten - Ungleichheit der Objektrelevanz. Sie haben Schwierigkeiten, ganze Objektdiagramme mit Leichtigkeit zu erhalten, und das Entity-Framework kann nicht helfen.
  • das Domänenmodell ist datenzentriert (im Gegensatz zu verhaltenszentrisch). Aus diesem Grund verlieren Sie das Wissen über die Objekthierarchie (zuvor erwähnte Kontexte), und auf magische Weise wird alles zu einer Gesamtwurzel.

Ich bin nicht sicher, wie das erste Problem behoben werden soll, aber ich habe festgestellt, dass das Reparieren des zweiten Problems zuerst gut genug ist. Um zu verstehen, was ich mit verhaltenszentrisch meine, geben Sie dieses Papier einen Versuch.

P.s. Das Repository zu aggregieren, um root zu aggregieren, macht keinen Sinn.
Pp. "CodeRepositories" vermeiden. Das führt zu datenzentrisch -> prozeduralem Code.
Pp.p.s Arbeitseinheit vermeiden. Aggregierte Roots sollten Transaktionsgrenzen definieren.

5
Arnis Lapsa

Dies ist eine alte Frage, aber es lohnt sich, eine einfache Lösung zu veröffentlichen.

  1. EF Context bietet Ihnen bereits sowohl Arbeitseinheiten (verfolgt Änderungen) als auch Repositorys (In-Memory-Verweise auf Daten aus der Datenbank). Eine weitere Abstraktion ist nicht obligatorisch.
  2. Entfernen Sie das DBSet aus Ihrer Kontextklasse, da Phone kein Aggregatstamm ist.
  3. Verwenden Sie stattdessen die Navigationseigenschaft "Telefone" für Benutzer.

statische void updateNumber (int userId, Zeichenfolge oldNumber, Zeichenfolge newNumber)

static void updateNumber(int userId, string oldNumber, string newNumber)
    {
        using (MyContext uow = new MyContext()) // Unit of Work
        {
            DbSet<User> repo = uow.Users; // Repository
            User user = repo.Find(userId); 
            Phone oldPhone = user.Phones.Where(x => x.Number.Trim() == oldNumber).SingleOrDefault();
            oldPhone.Number = newNumber;
            uow.SaveChanges();
        }

    }
3
Chalky

Wenn eine Phone-Entität nur zusammen mit einem aggregierten Root-Benutzer sinnvoll ist, wäre es meiner Meinung nach auch sinnvoll, dass der Vorgang zum Hinzufügen eines neuen Phone-Datensatzes durch eine bestimmte Methode (DDD-Verhalten) in der Verantwortung des Benutzerdomänenobjekts liegt Es ist aus mehreren Gründen durchaus sinnvoll, der unmittelbare Grund ist, dass wir prüfen sollten, ob das Benutzerobjekt vorhanden ist, da die Phone-Entität davon abhängt und möglicherweise eine Transaktionssperre beibehalten wird, während weitere Validierungsprüfungen durchgeführt werden, um sicherzustellen, dass kein anderer Prozess das Root-Aggregat zuvor gelöscht hat Die Validierung der Operation ist abgeschlossen. In anderen Fällen mit anderen Arten von Stammaggregaten möchten Sie möglicherweise einen Wert zusammenfassen oder berechnen und in den Spalteneigenschaften des Stammaggregats beibehalten, um die Verarbeitung durch andere Vorgänge später effizienter zu gestalten. Obwohl ich vorschlage, dass das Benutzerdomänenobjekt über eine Methode zum Hinzufügen des Telefons verfügt, bedeutet dies nicht, dass es über die Existenz der Datenbank oder EF wissen sollte. Eine der großen Funktionen von EM und Hibernate besteht darin, dass sie Änderungen an der Entität verfolgen können Klassen transparent und das bedeutet auch das Hinzufügen neuer verwandter Entitäten anhand ihrer Navigationssammlungseigenschaften. 

Wenn Sie Methoden verwenden möchten, die alle Telefone abrufen, unabhängig von den Benutzern, deren Eigentümer sie sind, könnten Sie trotzdem über das Benutzer-Repository darüber nachdenken. Sie benötigen nur eine Methode, die alle Benutzer als IQueryable zurückgibt. Dann können Sie sie zuordnen, um alle Telefone des Benutzers zu erhalten und verfeinern zu können Frage damit ab. In diesem Fall benötigen Sie nicht einmal ein PhoneRepository. Außerdem würde ich lieber eine Klasse mit Erweiterungsmethode für IQueryable verwenden, die ich nicht nur von einer Repository-Klasse verwenden kann, wenn Abfragen hinter Methoden abstrahiert werden sollen.

Nur eine Einschränkung für das Löschen von Phone-Entitäten, indem nur das Domänenobjekt und kein Phone-Repository verwendet wird, müssen Sie sicherstellen, dass die UserId Teil des Phone-Primärschlüssels ist. Mit anderen Worten: Der Primärschlüssel eines Phone-Datensatzes ist ein zusammengesetzter Schlüssel bestehend aus UserId und einer anderen Eigenschaft (ich empfehle eine automatisch generierte Identität) in der Entität Phone. Dies ist intuitiv sinnvoll, da der Telefondatensatz dem Benutzerdatensatz "gehört" und seine Entfernung aus der Sammlung der Benutzernavigation der vollständigen Entfernung aus der Datenbank entspricht.

0
Kaveh Hadjari