Arbeitseinheit + Repository + Serviceschicht mit Abhängigkeitsinjektion

8

Ich entwerfe eine Webanwendung und einen Windows-Dienst und möchte die Arbeitseinheit + Repository-Schicht in Verbindung mit einer Serviceschicht verwenden. Ich habe Probleme damit, alles zusammen zu stellen, damit die Client-Apps die Transaktion steuern Daten mit der Arbeitseinheit.

Die Arbeitseinheit enthält eine Sammlung aller in der Transaktion registrierten Repositorys sowie Commit- und Rollback-Vorgänge

%Vor%

Das generische Repository verfügt über Operationen, die auf der Datenschicht für ein bestimmtes Modell (Tabelle) ausgeführt werden

%Vor%

Die konkrete Implementierung der Arbeitseinheit verwendet das Entitätsframework unter der Haube (DbContext), um die Änderungen in der Datenbank zu speichern, und eine neue Instanz der DbContext-Klasse wird pro Arbeitseinheit erstellt.

%Vor%

Die Repositorys in der Arbeitseinheit werden beim Zugriff erstellt, wenn sie in der aktuellen Arbeitseinheitseinheit nicht vorhanden sind. Das Repository verwendet den DbContext als Konstruktorparameter, damit er effektiv in der aktuellen Arbeitseinheit funktionieren kann.

%Vor%

Ich habe auch eine Serviceklasse, die Business-Workflow-Logik kapselt und ihre Abhängigkeiten im Konstruktor aufnimmt.

%Vor%

Die Webanwendung ist eine ASP.NET MVC-App, der Controller erhält seine Abhängigkeiten auch im Konstruktor. In diesem Fall wird die Arbeitseinheit und Serviceklasse injiziert. Die Aktion führt eine vom Dienst bereitgestellte Operation aus, z. B. das Erstellen eines Datensatzes im Repository und das Speichern einer Datei auf einem Dateiserver mit einem DocumentStorageService. Anschließend wird die Arbeitseinheit in der Controller-Aktion festgeschrieben.

%Vor%

In der Webanwendung verwende ich den Unity DI-Container, um allen Aufrufern die gleiche Instanz der Arbeitseinheit pro http-Anforderung zu injizieren, sodass die Controller-Klasse eine neue Instanz erhält und dann die Serviceklasse, die die Arbeitseinheit verwendet die gleiche Instanz wie der Controller. Auf diese Weise fügt der Dienst dem Repository einige Datensätze hinzu, die in einer Arbeitseinheit registriert sind und vom Client-Code im Controller festgeschrieben werden können.

Eine Frage bezüglich des oben beschriebenen Codes und der Architektur. Wie kann ich die Abhängigkeit der Arbeitseinheit in den Serviceklassen beseitigen? Idealerweise sollte die Serviceklasse keine Instanz der Arbeitseinheit haben, da ich nicht möchte, dass der Service die Transaktion festschreibt. Ich möchte nur, dass der Service einen Verweis auf das Repository hat, mit dem er arbeiten muss. und lassen Sie den Controller (Client-Code) die Operation festschreiben, wenn es passt.

In der Windows-Dienstanwendung möchte ich in der Lage sein, eine Reihe von Datensätzen mit einer einzigen Arbeitseinheit zu erhalten, sagen alle Datensätze im Status "Ausstehend". Dann würde ich gerne alle diese Datensätze durchforsten und die Datenbank abfragen, um jeden einzelnen zu erhalten und dann den Status jedes einzelnen während jeder Schleife zu überprüfen, da sich der Status von der Zeit, zu der ich abgefragt habe, zu der Zeit geändert hat, zu der ich arbeiten möchte ein einziger. Das Problem, das ich gerade habe, ist, dass meine derzeitige Architektur es mir nicht erlaubt, mehrere Arbeitseinheiten für die gleiche Instanz des Dienstes zu haben.

%Vor%

Die obige Jobklasse übernimmt einen Dienst im Konstruktor als Abhängigkeit und wird wiederum von Unity aufgelöst. Die Service-Instanz, die aufgelöst und injiziert wird, hängt von einer Arbeitseinheit ab. Ich würde gerne zwei get Operationen auf der Serviceklasse durchführen, aber da ich unter der gleichen Instanz der Arbeitseinheit arbeite, kann ich das nicht erreichen.

Haben Sie für alle von Ihnen, Gurus da draußen, irgendwelche Vorschläge, wie ich meine Anwendung, Arbeitseinheit + Repository + Serviceklassen umgestalten kann, um die oben genannten Ziele zu erreichen?

Ich wollte die Arbeitseinheits- + Repository-Muster verwenden, um Testbarkeit für meine Serviceklassen zu ermöglichen, aber ich bin offen für andere Designmuster, die meinen Code gleichzeitig wartbar und testbar machen und gleichzeitig die Trennung von Bedenken ermöglichen. p>

Update 1 Hinzufügen der DataContext-Klasse, die von EFs DbContext stammt, wo ich meine EF DbSets und Konfigurationen deklariert habe.

%Vor%     
Guillermo Gomez 20.09.2014, 22:26
quelle

2 Antworten

15

Wenn Ihr Zweck für die Verwendung von Unit of Work (UoW) für die Testbarkeit war, haben Sie den falschen Weg eingeschlagen. Einheit der Arbeit tut nichts zur Testbarkeit. Der Hauptzweck besteht darin, atomare Transaktionen für unterschiedliche Datenquellen bereitzustellen, einer Datenschicht UOW-Funktionalität zur Verfügung zu stellen, die diese nicht bereits bereitstellt, oder eine vorhandene UoW so zu verpacken, dass sie leichter ersetzbar ist ... etwas, das Sie Durch die Verwendung des generischen Repositories (das bindet es eng an Entity Framework an), wird es ungültig gemacht.

Ich schlage vor, dass Sie die Einheit der Arbeit vollständig loswerden. Entity Framework ist bereits ein UoW. Selbst Microsoft hat seine Meinung geändert und empfiehlt UOW nicht mehr mit EF.

Wenn Sie also UoW loswerden, können Sie Ihre EF-Anfragen einfach mit Repositories umschließen. Ich schlage nicht vor, ein generisches Repository zu verwenden, da dies Ihre Datenschichtimplementierung über Ihren gesamten Code (etwas, das Ihre UoW bereits ausgeführt hat), sondern konkrete Repository-Repositories (diese können intern generische Repositories verwenden, wenn Sie möchten, aber das generische Repository) sollte nicht außerhalb Ihres Repositorys lecken.)

Dies bedeutet, dass Ihre Serviceebene das spezifische konkrete Repository benötigt, das sie benötigt. Zum Beispiel IPortfolioRepository . Dann haben Sie eine Klasse PortfolioRepository , die von IPortfolioRepository erbt, die Ihre EF DbContext als Parameter nimmt, der von Ihrem Depnvency Injection (DI) -Framework injiziert wird. Wenn Sie Ihren DI-Container so konfigurieren, dass er Ihren EF-Kontext auf einer "PerRequest" -Basis darstellt, können Sie dieselbe Instanz an alle Ihre Repositorys übergeben. Sie können eine Commit -Methode für Ihr Repository verwenden, die SavesChanges aufruft, aber es speichert Änderungen an allen Änderungen, nicht nur an diesem Repository.

Was die Testbarkeit betrifft, haben Sie zwei Möglichkeiten. Sie können entweder die konkreten Repositories überspielen oder Sie können die eingebauten Spottfunktionen von EF6 verwenden.

    
Erik Funkenbusch 21.09.2014 00:21
quelle
2

Ich habe selbst dieses Höllenloch durchgemacht und hier ist was Ich habe getan:

  • Ziehe die UOW komplett ab. EFs DBContext ist im Grunde eine UOW. Es hat keinen Sinn, das Rad neu zu erfinden.

    Pro MSN :

      

    DbContext-Klasse

         

    Stellt eine Kombination der Arbeitseinheits- und Repository-Muster dar   und ermöglicht es Ihnen, eine Datenbank abzufragen und Änderungen zusammenzufassen   wird dann als eine Einheit in den Speicher zurückgeschrieben.

  • Service-Ebene + Repo-Ebene schien eine gute Wahl zu sein. Repos sind jedoch immer eine undichte Abstraktion, insbesondere wenn DBContext DbSet den Repositories entspricht.

  • Wenn dann der Bedarf für einen Windows-Dienst entsteht, werden die Dinge jetzt mit einer anderen Ebene weiter verdüstert. Wirf Async- oder Hintergrundverarbeitung in die Mischung und die Dinge beginnen schnell zu lecken.

Wenn Sie meine 2 Cent fragen, würde ich sagen, gehen Sie mit der Service-Schicht + EF, eine Verpackung Geschäftslogik, die andere Verpackung UOW / Repository-Muster.

Alternativ, und speziell für Windows-Dienste, finde ich, dass das zu einem Kommandoabfrage basierend auf funktioniert besser. Es hilft nicht nur bei der Testbarkeit, es hilft auch bei asynchronen Aufgaben, bei denen ich mich nicht darum kümmern muss, den DBContext auch nach Beendigung der Anfrage am Leben zu erhalten (DBContext ist jetzt mit dem Befehlshandler verbunden und bleibt so lange aktiv, wie der Async-Befehl bleibt) lebendig).

Wenn Sie kürzlich all diese Fakten über das UOW / Repository-Muster verdaut haben, dann wird sicherlich auch das Lesen über das Kommando-Abfrage-Muster Ihren Verstand verletzen. Ich bin diesen Weg gegangen, aber vertraue mir, es ist die Zeit wert, zumindest hineinzuschauen und es zu versuchen.

Diese Beiträge können helfen:

Wenn Sie mutig genug sind (nach der Verdauung durch CQRS), dann schauen Sie sich MediatR an, die den Mediator implementiert Muster (das im Grunde Befehls-Abfrage mit Benachrichtigungen umschließt) und ermöglicht, über Pub-Sub zu arbeiten. Das Pub-Sub-Modell passt gut in die Windows Service- und Services-Ebene.

    
Mrchief 21.09.2014 07:28
quelle