Eine Situation, in die ich einigermaßen oft gerate, ist eine, in der meine Modelle entweder beginnen:
- Wachsen Sie mit unzähligen Methoden zu Monstern heran
ODER
- Ermöglichen es Ihnen, ihnen SQL-Teile zu übergeben, sodass sie flexibel genug sind, um nicht eine Million verschiedener Methoden zu erfordern
Angenommen, wir haben ein "Widget" -Modell. Wir beginnen mit einigen grundlegenden Methoden:
- get ($ id)
- Einfügen ($ record)
- update ($ id, $ record)
- löschen ($ id)
- getList () // Liste der Widgets abrufen
Das ist alles in Ordnung und gut, aber dann brauchen wir eine Berichterstattung:
- listCreatedBetween ($ start_date, $ end_date)
- listPurchasedBetween ($ start_date, $ end_date)
- listOfPending ()
Und dann wird die Berichterstattung immer komplexer:
- listPendingCreatedBetween ($ start_date, $ end_date)
- listForCustomer ($ customer_id)
- listPendingCreatedBetweenForCustomer ($ customer_id, $ start_date, $ end_date)
Sie können sehen, wo dies zunimmt ... irgendwann haben wir so viele spezifische Abfrageanforderungen, dass ich entweder Tonnen und Tonnen von Methoden implementieren muss, oder eine Art "Abfrage" -Objekt, das ich an eine einzelne -> Abfrage (Abfrage) übergeben kann $ query) Methode ...
... oder beißen Sie einfach in die Kugel und machen Sie so etwas:
- list = MyModel-> query ("start_date> X AND end_date <Y AND pending = 1 AND customer_id = Z")
Es hat einen gewissen Reiz, nur eine Methode wie diese zu haben, anstatt 50 Millionen andere spezifischere Methoden ... aber es fühlt sich manchmal "falsch" an, einen Stapel dessen, was im Grunde SQL ist, in den Controller zu stopfen.
Gibt es einen "richtigen" Weg, mit solchen Situationen umzugehen? Scheint es akzeptabel, solche Abfragen in eine generische -> query () -Methode zu packen?
Gibt es bessere Strategien?
quelle
Antworten:
Martin Fowlers Patterns of Enterprise Application Architecture beschreibt eine Reihe von ORM-bezogenen Mustern, einschließlich der Verwendung des Query Object, was ich vorschlagen würde.
Mit Abfrageobjekten können Sie dem Prinzip der Einzelverantwortung folgen, indem Sie die Logik für jede Abfrage in individuell verwaltete und verwaltete Strategieobjekte aufteilen. Entweder kann Ihr Controller die Verwendung direkt verwalten oder an einen sekundären Controller oder ein Hilfsobjekt delegieren.
Wirst du viele davon haben? Bestimmt. Können einige zu generischen Abfragen zusammengefasst werden? Wieder ja.
Können Sie die Abhängigkeitsinjektion verwenden, um die Objekte aus Metadaten zu erstellen? Das ist, was die meisten ORM-Tools tun.
quelle
Es gibt keinen richtigen Weg, dies zu tun. Viele Menschen verwenden ORMs, um die Komplexität zu abstrahieren. Einige der fortgeschritteneren ORMs übersetzen Code-Ausdrücke in komplizierte SQL-Anweisungen. ORMs haben auch ihre Nachteile, jedoch überwiegen bei vielen Anwendungen die Vorteile die Kosten.
Wenn Sie nicht mit einem umfangreichen Dataset arbeiten, ist es am einfachsten, die gesamte Tabelle in den Arbeitsspeicher auszuwählen und im Code zu filtern.
Für interne Berichtsanwendungen ist dieser Ansatz wahrscheinlich in Ordnung. Wenn das Dataset wirklich groß ist, benötigen Sie viele benutzerdefinierte Methoden sowie entsprechende Indizes für Ihre Tabelle.
quelle
In einigen ORMs können Sie komplexe Abfragen ausgehend von grundlegenden Methoden erstellen. Zum Beispiel
ist eine vollkommen gültige Abfrage im Django ORM .
Die Idee ist, dass Sie einen Abfrage-Generator haben (in diesem Fall
Purchase.objects
), dessen interner Status Informationen zu einer Abfrage darstellt. Methoden wieget
,filter
,exclude
,order_by
gültig sind und geben eine neue Query Builder mit einem aktualisierten Status. Diese Objekte implementieren eine iterierbare Schnittstelle, sodass beim Durchlaufen der Objekte die Abfrage ausgeführt wird und Sie die Ergebnisse der bisher erstellten Abfrage erhalten. Obwohl dieses Beispiel aus Django stammt, sehen Sie dieselbe Struktur in vielen anderen ORMs.quelle
Es gibt einen dritten Ansatz.
Ihr spezielles Beispiel weist ein exponentielles Wachstum der Anzahl der erforderlichen Methoden auf, wenn die Anzahl der erforderlichen Features zunimmt: Wir möchten, dass erweiterte Abfragen angeboten und alle Abfragefunktionen kombiniert werden können. Wenn wir dazu Methoden hinzufügen, haben wir eine Methode für a Basisabfrage, zwei, wenn wir ein optionales Feature hinzufügen, vier, wenn wir zwei hinzufügen, acht, wenn wir drei hinzufügen, 2 ^ n, wenn wir n Features hinzufügen.
Das ist offensichtlich über drei oder vier Features hinaus nicht zu erreichen, und es riecht nach einer Menge eng verwandter Codes, die zwischen den Methoden fast kopiert werden.
Sie können dies vermeiden, indem Sie ein Datenobjekt hinzufügen, das die Parameter enthält, und eine einzige Methode verwenden, die die Abfrage auf der Grundlage der angegebenen (oder nicht angegebenen) Parameter erstellt. In diesem Fall ist das Hinzufügen einer neuen Funktion, z. B. eines Datumsbereichs, so einfach wie das Hinzufügen von Setters und Getters für den Datumsbereich zu Ihrem Datenobjekt und anschließendes Hinzufügen eines Codebits, in dem die parametrisierte Abfrage erstellt wird:
... und wo die Parameter zur Abfrage hinzugefügt werden:
Dieser Ansatz ermöglicht ein lineares Codewachstum, wenn Features hinzugefügt werden, ohne dass beliebige, nicht parametrisierte Abfragen zulässig sind.
quelle
Meiner Meinung nach besteht der allgemeine Konsens darin, in Ihren Modellen in MVC so weit wie möglich auf Daten zuzugreifen. Ein weiteres Konstruktionsprinzip besteht darin, einige Ihrer allgemeineren Abfragen (die nicht direkt mit Ihrem Modell zusammenhängen) auf eine höhere, abstraktere Ebene zu verschieben, auf der Sie zulassen können, dass sie auch von anderen Modellen verwendet werden. (In RoR haben wir so etwas wie Framework) Es gibt noch eine andere Sache, die Sie berücksichtigen müssen und die die Wartbarkeit Ihres Codes ist. Wenn Ihr Projekt wächst und Sie über Datenzugriff in Controllern verfügen, wird es immer schwieriger, diese aufzuspüren. (Dieses Problem tritt derzeit in einem großen Projekt auf.) Modelle bieten, obwohl sie mit Methoden überladen sind, einen zentralen Ansprechpartner für alle Controller könnte am Ende von den Tabellen quering. (Dies kann auch zu einer Wiederverwendung von Code führen, was wiederum von Vorteil ist.)
quelle
Ihre Service-Layer-Schnittstelle verfügt möglicherweise über viele Methoden, der Datenbankaufruf kann jedoch nur über eine Methode erfolgen.
Eine Datenbank hat 4 Hauptoperationen
Eine andere optionale Methode kann darin bestehen, eine Datenbankoperation auszuführen, die nicht unter die grundlegenden DB-Operationen fällt. Nennen wir das Ausführen.
Einfügen und Aktualisieren können in einem Vorgang zusammengefasst werden, der als Speichern bezeichnet wird.
Viele Ihrer Methoden sind Abfragen. So können Sie eine generische Schnittstelle erstellen, die die meisten unmittelbaren Anforderungen erfüllt. Hier ist ein Beispiel für eine generische Schnittstelle:
Das Datenübertragungsobjekt ist generisch und enthält alle Ihre Filter, Parameter, Sortierungen usw. Die Datenschicht ist für das Parsen und Extrahieren dieser Daten und das Einrichten der Operation für die Datenbank über gespeicherte Prozeduren, parametrisierte SQL, Linq usw. verantwortlich. Daher wird SQL nicht zwischen Schichten übergeben. Dies ist in der Regel das, was ein ORM tut. Sie können jedoch Ihre eigene Zeichnung erstellen und eine eigene Abbildung erstellen.
In Ihrem Fall haben Sie also Widgets. Widgets würden die IPOCO-Schnittstelle implementieren.
Also, in Ihrem Service-Layer-Modell hätte
getList().
Benötigt eine Mapping-Ebene, in die transformiert
getList
werden kannund umgekehrt. Wie andere bereits erwähnt haben, geschieht dies manchmal über ein ORM, aber letztendlich erhalten Sie eine Menge Code vom Typ Boilerplate, insbesondere wenn Sie Hunderte von Tabellen haben. Der ORM erstellt auf magische Weise parametrisiertes SQL und führt dieses für die Datenbank aus. Wenn Sie Ihre eigenen Dateien rollen, werden zusätzlich in der Datenebene selbst Mapper benötigt, um SP, Linq usw. einzurichten (im Grunde genommen der SQL-Code, der zur Datenbank geht).
Wie bereits erwähnt, ist das DTO ein aus Kompositionen bestehendes Objekt. Vielleicht ist eines der darin enthaltenen Objekte ein Objekt namens QueryParameters. Dies wären alle Parameter für die Abfrage, die von der Abfrage eingerichtet und verwendet würden. Ein weiteres Objekt wäre eine Liste der zurückgegebenen Objekte aus Abfragen, Aktualisierungen, ext. Das ist die Nutzlast. In diesem Fall wäre die Nutzlast eine Liste von Widgets.
Die grundlegende Strategie lautet also:
In Ihrem Fall könnte das Modell viele Methoden haben, aber Sie möchten, dass der Datenbankaufruf generisch ist. Sie haben immer noch viel Boilerplate-Mapping-Code (insbesondere mit SPs) oder magischen ORM-Code, der das parametrisierte SQL dynamisch für Sie erstellt.
quelle