Soll ich das Repository im Domänenobjekt verwenden oder das Domänenobjekt zurück in die Serviceschicht verschieben?

11

Ich komme aus einer Transaktionsskriptwelt und beginne gerade, mir DDD anzuschauen. Ich bin mir nicht sicher, wie ich ein DDD-Design mit Datenbankpersistenz richtig integrieren kann. Das habe ich:

Eine Serviceklasse namens OrganisationService, deren Schnittstelle Methoden zum Abrufen und Speichern von Instanzen von Organisationsdomänenobjekten enthält. Organisation ist eine aggregierte Wurzel und hat andere damit verbundene Daten: Mitglieder und Lizenzen. Der erste DBContext der EF6-Datenbank wird im OrganisationService verwendet, um OrganisationDB-Entitäten und die zugehörigen MemberDB- und LicenseDB-Entitäten abzurufen. Diese werden alle in ihre Domänenobjektklassenäquivalente umgewandelt, wenn sie vom OrganisationService abgerufen und in das Organisationsdomänenobjekt geladen werden. Dieses Objekt sieht so aus:

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }
}

Ich verwende das Repository-Muster nicht im OrganisationService ... Ich verwende EF selbst als Repository, da EF6 das Repository anscheinend weitgehend überflüssig gemacht hat.

Zu diesem Zeitpunkt im Entwurf ist das Organisationsdomänenobjekt anämisch: Es sieht aus wie die EF POCO-Organisationsklasse. Die OrganisationService-Klasse ähnelt stark einem Repository!

Jetzt muss ich anfangen, Logik hinzuzufügen. Diese Logik umfasst das Verwalten von Lizenzen und Mitgliedern einer Organisation. In den Tagen des Transaktionsskripts würde ich dem OrganisationService Methoden zur Verarbeitung dieser Vorgänge hinzufügen und ein Repository aufrufen, um mit der Datenbank zu interagieren. Bei DDD sollte diese Logik jedoch im Organisationsdomänenobjekt selbst gekapselt sein ...

Hier bin ich mir nicht sicher, was ich tun soll: Ich muss diese Daten als Teil der Logik in der Datenbank speichern. Bedeutet dies, dass ich dazu DbContext im Organisationsdomänenobjekt verwenden sollte? Ist die Verwendung des Repositorys / EF innerhalb des Domänenobjekts eine schlechte Praxis? Wenn ja, wohin gehört diese Beharrlichkeit?

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }

    public void AddLicensesToOrganisation(IList<License> licensesToAdd)
    {
        // Do validation/other stuff/

        // And now Save to the DB???
        using(var context = new EFContext())
        {
            context.Licenses.Add(...
        }

        // Add to the Licenses collection in Memory
        Licenses.AddRange(licensesToAdd);
    }
}

Sollte ich stattdessen nur das Organisationsdomänenobjekt im Speicher mutieren und es dann zur Persistenz zurück an den OrganisationService senden? Dann muss ich nachverfolgen, was sich tatsächlich am Objekt geändert hat (was EF mit seinen eigenen POCOs macht! Ich habe das Gefühl, dass EF nicht nur ein Repository-Ersatz ist, sondern auch die Domänenschicht sein könnte!).

Jede Anleitung hier wird geschätzt.

MrLane
quelle

Antworten:

2

Sie können beide Ansätze wählen und es gut funktionieren lassen - es gibt natürlich Vor- und Nachteile.

Entity Framework ist definitiv dazu gedacht , Ihre Domain-Entitäten zu durchdringen. Es funktioniert gut, wenn Ihre Domänenentitäten und Datenentitäten dieselben Klassen sind. Es ist viel schöner, wenn Sie sich auf EF verlassen können, um die Änderungen für Sie zu verfolgen, und einfach anrufen, context.SaveChanges()wenn Sie mit Ihrer Transaktionsarbeit fertig sind. Dies bedeutet auch, dass Ihre Validierungsattribute nicht zweimal festgelegt werden müssen, einmal in Ihren Domänenmodellen und einmal in Ihren persistierten Entitäten - Dinge wie [Required]oder [StringLength(x)]können in Ihrer Geschäftslogik überprüft werden , sodass Sie ungültige Datenzustände abfangen können, bevor Sie dies versuchen Führen Sie eine DB-Transaktion durch und erhalten Sie eineEntityValidationException. Schließlich ist das Codieren schnell - Sie müssen keine Zuordnungsebene oder kein Repository schreiben, sondern können stattdessen direkt mit dem EF-Kontext arbeiten. Es ist bereits ein Repository und eine Arbeitseinheit, sodass zusätzliche Abstraktionsebenen nichts bewirken.

Ein Nachteil beim Kombinieren Ihrer Domain und persistierter Entitäten besteht darin, dass Sie eine Reihe von [NotMapped]Attributen erhalten, die über Ihre Eigenschaften verteilt sind. Häufig möchten Sie domänenspezifische Eigenschaften, die entweder nur Filter für persistente Daten sind oder später in Ihrer Geschäftslogik festgelegt und nicht wieder in Ihrer Datenbank gespeichert werden. Manchmal möchten Sie Ihre Daten in Ihren Domänenmodellen auf eine Weise ausdrücken, die mit einer Datenbank nicht sehr gut funktioniert. Wenn Sie beispielsweise Aufzählungen verwenden, ordnet Entity diese einer intSpalte zu. Möglicherweise möchten Sie sie jedoch zuordnen Sie sind für Menschen lesbar string, sodass Sie bei der Untersuchung der Datenbank keine Suche durchführen müssen. Dann erhalten Sie eine stringEigenschaft, die zugeordnet ist, eineenumEigenschaft, die es nicht ist (aber die Zeichenfolge und die Zuordnungen zur Aufzählung erhält), und eine API, die beide verfügbar macht! Wenn Sie komplexe Typen (Tabellen) mappedkontextübergreifend kombinieren möchten, erhalten Sie möglicherweise eine OtherTableId und eine nicht zugeordnete ComplexTypeEigenschaft, die beide in Ihrer Klasse als öffentlich verfügbar sind. Dies kann für jemanden verwirrend sein, der mit dem Projekt nicht vertraut ist und Ihre Domain-Modelle unnötig aufbläht.

Je komplexer meine Geschäftslogik / Domain ist, desto restriktiver oder umständlicher ist es für mich, meine Domain und persistente Entitäten zu kombinieren. Für Projekte mit kurzen Fristen oder die keine komplexe Geschäftsschicht ausdrücken, halte ich die Verwendung von EF-Entitäten für beide Zwecke für angemessen, und es ist nicht erforderlich, Ihre Domain von Ihrer Persistenz zu abstrahieren. Für Projekte, die eine maximale Erweiterungsfreundlichkeit erfordern oder die eine sehr komplizierte Logik ausdrücken müssen, ist es meiner Meinung nach besser, die beiden zu trennen und sich mit der zusätzlichen Komplexität der Persistenz zu befassen.

Ein Trick, um die Probleme der manuellen Verfolgung Ihrer Entitätsänderungen zu vermeiden, besteht darin, die entsprechende persistierte Entitäts-ID in Ihrem Domänenmodell zu speichern. Dies kann automatisch von Ihrer Mapping-Ebene ausgefüllt werden. Wenn Sie dann eine Änderung an EF beibehalten müssen, rufen Sie die relevante persistente Entität ab, bevor Sie eine Zuordnung vornehmen. Wenn Sie die Änderungen dann zuordnen, erkennt EF sie automatisch und Sie können anrufen, context.SaveChanges()ohne sie von Hand verfolgen zu müssen.

public class OrganisationService
{
    public void PersistLicenses(IList<DomainLicenses> licenses) 
    {
        using (var context = new EFContext()) 
        {
            foreach (DomainLicense license in licenses) 
            {
                var persistedLicense = context.Licenses.Find(pl => pl.Id == license.PersistedId);
                MappingService.Map(persistedLicense, license); //Right-left mapping
                context.Update(persistedLicense);
            }
            context.SaveChanges();
        }
    }
}
NWard
quelle
Gibt es ein Problem bei der Kombination verschiedener Ansätze mit unterschiedlicher Datenkomplexität? Wenn Sie etwas haben, das der Datenbank gut zugeordnet ist, verwenden Sie EF direkt. Wenn Sie etwas haben, das nicht funktioniert, führen Sie eine Zuordnung / Abstraktion ein, bevor Sie EF verwenden.
Euphoric
@Euphoric Die Unterschiede zwischen Domain und DB können im Mapping behandelt werden. Ich kann mir keinen Anwendungsfall vorstellen, bei dem das zweimalige Hinzufügen der Domäne (nicht persistent und persistent) und das manuelle Verfolgen von Änderungen weniger Arbeit erfordert als das Hinzufügen von Zuordnungen für Sonderfälle. Kannst du mich auf einen verweisen? (Ich nehme an, mit NHibernate, vielleicht ist EF eingeschränkter?)
Firo
Sehr hilfreiche, praktische und informative Antwort. Als Antwort markieren. Vielen Dank!
MrLane
3

Ich kenne EF6 nicht, aber andere ORM behandeln dies transparent, sodass Sie die Lizenzen nicht explizit zum Kontext hinzufügen müssen. Sie werden beim Speichern von Änderungen erkannt. Die Methode liegt also bei

public void AddLicenses(IEnumerable<License> licensesToAdd)
{
    // Do validation/other stuff/
    // Add to the Licenses collection in Memory
    Licenses.AddRange(licensesToAdd);
}

und der Code darum herum

var someOrg = Load(orgId);

someOrg.AddLicenses(someLicences);

SaveChanges();
Firo
quelle
Meine Domain-Objekte sind nicht die Entitäten. Wenn Sie sie also zur Lizenzsammlung hinzufügen, bei der es sich lediglich um eine Liste handelt, wird dies nicht gekürzt. Die Frage betrifft jedoch nicht so sehr EF, sondern den Ort, an dem die eigentliche Persistenz stattfindet. Alle Persistenz in EF muss innerhalb eines Kontextbereichs erfolgen. Die Frage ist, wo gehört das hin?
MrLane
2
Domänenentitäten und persistierte Entitäten können / sollten identisch sein, andernfalls führen Sie eine weitere Abstraktionsebene ein, die in 99% der Fälle keinen Wert hinzufügt und die Änderungsverfolgung verloren geht.
Firo