Als ich zum ersten Mal etwas über Domain Driven Design erfuhr, wurde ich auch in das Repository und die Arbeitseinheit eingeführt, die einst für die coolen Kids, die SQL-Abfragen wie Cavemans gegen Datenbanken stellten, erstklassig zu sein schienen. Je tiefer ich mich mit diesem Thema befasste , desto mehr erfuhr ich, dass sie aufgrund von ORMs wie EF und NHibernate , die sowohl Arbeitseinheiten als auch Repositorys in einer API implementieren, die als Sitzung oder Kontext bezeichnet wird, nicht mehr erforderlich zu sein scheinen .
Jetzt bin ich mir nicht sicher, was ich tun soll. Zum Repository oder nicht zum Repository. Ich verstehe das Argument wirklich, dass solche undichten Abstraktionen die Dinge nur übermäßig komplizieren und absolut nichts hinzufügen, was den Datenzugriff vereinfachen könnte. Es fühlt sich jedoch nicht richtig an, jeden möglichen Aspekt meiner Anwendung mit z . B. Entity Framework zu koppeln . Normalerweise folge ich ein paar einfachen Richtlinien:
- Die Domänenschicht ist das Herzstück des Systems und enthält Entitäten, Dienste, Repositorys ...
- Die Infrastrukturschicht bietet Implementierungen von Domänenschnittstellen eines infrastrukturellen Problems, z. B. Datei, Datenbank, Protokolle.
- Die Anwendungsschicht enthält einen Kompositionsstamm, der die Dinge verkabelt und alles orchestriert.
Meine Lösungen sehen normalerweise so aus:
Domain.Module1
Domain.Module2
IModule2Repo
IModule2Service
Module2
Infrastructure.Persistence
Repositories
EntityFrameworkRepositoryBase
MyApp
Boostrapper
-> inject EntityFrameworkRepositoryBase into IRepository etc.
Ich halte meine Domain-Schicht sauber, indem ich eine verwende, IRepository<'T>
die auch ein Domain-Problem darstellt, das nicht von irgendetwas anderem abhängt, das mir sagt, wie ich auf Daten zugreifen soll. Wenn ich jetzt eine konkrete Implementierung vornehmen würde, für IModule2Service
die Datenzugriff erforderlich ist, müsste ich sie injizieren DbContext
und dadurch direkt an die Infrastrukturschicht koppeln. ( Wenn Sie zum Visual Studio-Projekt kommen, kann dies aufgrund zirkulärer Abhängigkeiten sehr schwierig werden! )
Zusätzlich kann Was eine Alternative sein Hinterlegungsstellen und fucktons von Werken ? CQRS? Wie abstrahiert man einen reinen infrastrukturellen Rahmen?
Antworten:
Beim Engineering geht es um Kompromisse. Und ebenso die Softwareentwicklung. Im Moment glaube ich, dass nur eine andere Option, die einfacher wäre, darin besteht, direkt mit ORM zu arbeiten. Aber wie Sie sagten, könnte dies Sie an ein bestimmtes Persistenz-Framework binden.
Sie müssen sich also fragen: "Ist die zusätzliche Komplexität die Entkopplung Ihres Codes von der Persistenz wert?" Jedes Mal, wenn ich Leute sagen höre, sie möchten ihren Code von der Persistenz entkoppeln, frage ich: "Wie oft in Ihrer Karriere haben Sie Ihren Persistenzrahmen geändert?"
Die Probleme, die ich bei Repositorys sehe, sind, dass sie allgemeine Änderungen erschweren. Z.B. Hinzufügen einer neuen Methode zum Abfragen eines Aggregats. Und sie machen ungewöhnliche Änderungen (angeblich) einfacher. Z.B. Änderung der Implementierung von Repositorys.
Dann gibt es noch das Unit-Testing-Argument, zu dem ich sage: "Wenn das Persistenz-Framework es Ihnen nicht erlaubt, eine Datenbank entweder im Speicher oder lokal zu verspotten, lohnt es sich überhaupt nicht, das Framework zu verwenden."
quelle
Ich bin Pro-Repository, obwohl ich mich von generischen Repository-Mustern entfernt habe. Stattdessen richte ich meine Repositorys an der Geschäftsfunktion aus, die sie erfüllen. Die Repositorys zielen nicht darauf ab, das ORM zu abstrahieren, da ich nicht erwarte, dass dies geändert wird, und gleichzeitig vermeide ich, ein Repository zu detailliert zu gestalten. (Dh CRUD) Stattdessen dienen meine Repositories zwei bis drei Hauptzwecken:
Beim Abrufen von Daten wird das Repository immer zurückgegeben
IQueryable<TEntity>
. Für die Datenerstellung wird TEntity zurückgegeben. Das Repository verwaltet meine Filterung auf Basisebene, z. B. den Status "Autorisierung" aktiv "für Systeme, die Soft-Delete-Muster verwenden, und den Status" aktuell "für Systeme, die historische Daten verwenden. Die Datenerstellung ist nur dafür verantwortlich, dass die erforderlichen Referenzen aufgelöst und verknüpft werden und dass die Entität eingerichtet und einsatzbereit ist.Die Datenaktualisierung liegt in der Verantwortung der Geschäftslogik, die mit den betreffenden Entitäten zusammenarbeitet. Dies kann beispielsweise Validierungsregeln umfassen. Ich versuche nicht, das in eine Repository-Methode zu kapseln.
Das Löschen in den meisten meiner Systeme erfolgt durch sanftes Löschen, sodass es unter die Datenaktualisierung fällt. (IsActive = false) Bei Hard-Deletes wäre dies ein Einzeiler im Repository.
Warum Repositories? Testfähigkeit. Sicher, DbContext kann verspottet werden, aber es ist einfacher, eine zurückgegebene Klasse zu verspotten
IQueryable<TEntity>
. Sie spielen auch gut mit dem UoW-Muster. Ich persönlich verwende das DbContextScope-Muster von Mehdime, um die Arbeitseinheit auf der gewünschten Ebene festzulegen (z. B. Controller in MVC) und meine Controller und Helfer-Serviceklassen die Repositorys verwenden zu lassen, ohne Verweise darauf übergeben zu müssen der UoW / dbContext herum. Die Verwendung von IQueryable bedeutet, dass Sie nicht viele Wrapper-Methoden im Repository benötigen und Ihr Code die Verwendung der Daten optimieren kann. Beispielsweise muss das Repository keine Methoden wie "Exists" oder "Count" verfügbar machen oder versuchen, Entitäten mit anderen POCOs zu verpacken, wenn Sie Teilmengen von Daten wünschen. Sie müssen nicht einmal eifrige Ladeoptionen für verwandte Daten verarbeiten, die Sie möglicherweise benötigen oder nicht. Durch Übergeben von IQueryable kann der aufrufende Code:Sehr flexibel und von einem Test-PoV aus muss mein verspottetes Repository lediglich Listen mit aufgefüllten Entitätsobjekten zurückgeben.
Bei generischen Repositorys habe ich mich größtenteils von diesen entfernt, da Ihre Controller / Services, wenn Sie ein Repository pro Tabelle haben, Verweise auf mehrere Repositorys enthalten, um eine Geschäftsaktion auszuführen. In den meisten Fällen führen nur ein oder zwei dieser Repositorys tatsächlich Schreibvorgänge aus (vorausgesetzt, Sie verwenden die Navigationseigenschaften ordnungsgemäß), während der Rest diejenigen mit Lesevorgängen unterstützt. Ich hätte lieber so etwas wie ein OrdersRepository, das in der Lage ist, Bestellungen zu lesen und zu erstellen und relevante Lookups usw. (leichte Kundenobjekte, Produkte usw.) als Referenz beim Erstellen einer Bestellung zu lesen, als 5 oder 6 verschiedene Repositorys zu treffen. Es kann gegen DNRY-Puristen verstoßen, aber mein Argument dafür ist, dass das Repository dazu dient, Aufträge zu erstellen, die die zugehörigen Referenzen enthalten.
Repository<Product>
Um Produkte zu erhalten, bei denen ich für eine Auftragsbasis nur eine Entität mit einer Handvoll Feldern benötige. In meinem OrderRepository wird möglicherweise eine.GetProducts()
Methode zurückgegeben,IQueryable<ProductSummary>
die meiner Meinung nach besser ist als eineRepository<Product>
, die mehrere "Get" -Methoden enthält, um zu versuchen, verschiedene Bereiche der Anwendungsanforderungen zu bedienen, und / oder einen komplexen Pass-In-Filterausdruck.Ich entscheide mich für einfacheren Code, der einfach zu befolgen, zu testen und abzustimmen ist. Es kann auf schlechte Weise missbraucht werden, aber ich hätte lieber etwas, bei dem Missbrauch leicht zu erkennen und zu korrigieren ist, als zu versuchen, den Code auf eine Weise zu "sperren", die nicht missbraucht werden kann, daran zu scheitern und dann etwas zu haben, das ist Ein Albtraum, um tatsächlich das zu tun, was der Kunde bezahlt, um es am Ende tun zu lassen. :) :)
quelle