Ich dachte, ich würde eine Pause einlegen, um meine eigene Frage zu beantworten. Was folgt, ist nur eine Möglichkeit, die Probleme 1-3 in meiner ursprünglichen Frage zu lösen.
Haftungsausschluss: Ich verwende möglicherweise nicht immer die richtigen Begriffe, wenn ich Muster oder Techniken beschreibe. Das tut mir leid.
Die Ziele:
- Erstellen Sie ein vollständiges Beispiel eines Basis-Controllers zum Anzeigen und Bearbeiten
Users
.
- Der gesamte Code muss vollständig testbar und verspottbar sein.
- Der Controller sollte keine Ahnung haben, wo die Daten gespeichert sind (was bedeutet, dass sie geändert werden können).
- Beispiel zum Anzeigen einer SQL-Implementierung (am häufigsten).
- Für maximale Leistung sollten Controller nur die Daten empfangen, die sie benötigen - keine zusätzlichen Felder.
- Die Implementierung sollte eine Art Datenmapper nutzen, um die Entwicklung zu vereinfachen.
- Die Implementierung sollte in der Lage sein, komplexe Datensuchen durchzuführen.
Die Lösung
Ich teile meine permanente Speicherinteraktion (Datenbankinteraktion) in zwei Kategorien auf: R (Lesen) und CUD (Erstellen, Aktualisieren, Löschen). Ich habe die Erfahrung gemacht, dass das Lesen wirklich dazu führt, dass eine Anwendung langsamer wird. Und während die Datenmanipulation (CUD) tatsächlich langsamer ist, kommt sie viel seltener vor und ist daher viel weniger besorgniserregend.
CUD (Erstellen, Aktualisieren, Löschen) ist einfach. Dies beinhaltet die Arbeit mit tatsächlichen Modellen , die dann Repositories
zur Persistenz an meine übergeben werden . Beachten Sie, dass meine Repositorys weiterhin eine Lesemethode bereitstellen, jedoch nur zur Objekterstellung und nicht zur Anzeige. Dazu später mehr.
R (Lesen) ist nicht so einfach. Keine Modelle hier, nur Wertobjekte . Verwenden Sie Arrays, wenn Sie dies bevorzugen . Diese Objekte können ein einzelnes Modell oder eine Mischung aus vielen Modellen darstellen, eigentlich alles. Diese sind für sich genommen nicht sehr interessant, aber wie sie erzeugt werden, ist. Ich benutze was ich rufe Query Objects
.
Der Code:
Benutzermodell
Beginnen wir einfach mit unserem grundlegenden Benutzermodell. Beachten Sie, dass es überhaupt keine ORM-Erweiterung oder Datenbankmaterial gibt. Nur purer Model-Ruhm. Fügen Sie Ihre Getter, Setter, Validierungen, was auch immer hinzu.
class User
{
public $id;
public $first_name;
public $last_name;
public $gender;
public $email;
public $password;
}
Repository-Schnittstelle
Bevor ich mein Benutzer-Repository erstelle, möchte ich meine Repository-Schnittstelle erstellen. Dies definiert den "Vertrag", dem Repositorys folgen müssen, um von meinem Controller verwendet zu werden. Denken Sie daran, mein Controller weiß nicht, wo die Daten tatsächlich gespeichert sind.
Beachten Sie, dass meine Repositorys nur alle diese drei Methoden enthalten. Die save()
Methode ist sowohl für das Erstellen als auch für das Aktualisieren von Benutzern verantwortlich, je nachdem, ob für das Benutzerobjekt eine ID festgelegt wurde oder nicht.
interface UserRepositoryInterface
{
public function find($id);
public function save(User $user);
public function remove(User $user);
}
SQL Repository-Implementierung
Nun erstelle ich meine Implementierung der Schnittstelle. Wie bereits erwähnt, sollte mein Beispiel eine SQL-Datenbank sein. Beachten Sie die Verwendung eines Daten-Mappers , um zu verhindern, dass sich wiederholende SQL-Abfragen geschrieben werden müssen.
class SQLUserRepository implements UserRepositoryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function find($id)
{
// Find a record with the id = $id
// from the 'users' table
// and return it as a User object
return $this->db->find($id, 'users', 'User');
}
public function save(User $user)
{
// Insert or update the $user
// in the 'users' table
$this->db->save($user, 'users');
}
public function remove(User $user)
{
// Remove the $user
// from the 'users' table
$this->db->remove($user, 'users');
}
}
Objektoberfläche abfragen
Jetzt, da CUD (Erstellen, Aktualisieren, Löschen) von unserem Repository erledigt wird, können wir uns auf das R (Lesen) konzentrieren. Abfrageobjekte sind einfach eine Kapselung einer Art von Datensuchlogik. Sie sind keine Abfrage-Builder. Indem wir es wie unser Repository abstrahieren, können wir seine Implementierung ändern und es einfacher testen. Ein Beispiel für ein Abfrageobjekt kann ein AllUsersQuery
oder AllActiveUsersQuery
oder sogar sein MostCommonUserFirstNames
.
Sie denken vielleicht: "Kann ich nicht einfach Methoden für diese Abfragen in meinen Repositorys erstellen?" Ja, aber hier ist, warum ich das nicht mache:
- Meine Repositorys sind für die Arbeit mit Modellobjekten gedacht. Warum sollte ich in einer realen App jemals das
password
Feld abrufen müssen, wenn ich alle meine Benutzer auflisten möchte ?
- Repositorys sind häufig modellspezifisch, Abfragen umfassen jedoch häufig mehr als ein Modell. In welches Repository stellen Sie Ihre Methode?
- Dies hält meine Repositorys sehr einfach - keine aufgeblähte Klasse von Methoden.
- Alle Abfragen sind jetzt in eigenen Klassen organisiert.
- Zu diesem Zeitpunkt existieren wirklich Repositorys, um meine Datenbankebene zu abstrahieren.
In meinem Beispiel erstelle ich ein Abfrageobjekt, um nach "AllUsers" zu suchen. Hier ist die Schnittstelle:
interface AllUsersQueryInterface
{
public function fetch($fields);
}
Implementierung des Abfrageobjekts
Hier können wir wieder einen Data Mapper verwenden, um die Entwicklung zu beschleunigen. Beachten Sie, dass ich eine Änderung am zurückgegebenen Datensatz zulasse - die Felder. Dies ist ungefähr so weit, wie ich mit der Manipulation der ausgeführten Abfrage gehen möchte. Denken Sie daran, dass meine Abfrageobjekte keine Abfrageersteller sind. Sie führen einfach eine bestimmte Abfrage durch. Da ich jedoch weiß, dass ich diese wahrscheinlich in verschiedenen Situationen häufig verwenden werde, gebe ich mir die Möglichkeit, die Felder anzugeben. Ich möchte niemals Felder zurückgeben, die ich nicht brauche!
class AllUsersQuery implements AllUsersQueryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function fetch($fields)
{
return $this->db->select($fields)->from('users')->orderBy('last_name, first_name')->rows();
}
}
Bevor ich zum Controller übergehe, möchte ich ein weiteres Beispiel zeigen, um zu veranschaulichen, wie leistungsfähig dies ist. Vielleicht habe ich eine Berichts-Engine und muss einen Bericht für erstellen AllOverdueAccounts
. Dies kann mit meinem Daten-Mapper schwierig sein, und ich möchte SQL
in dieser Situation möglicherweise einige aktuelle Informationen schreiben . Kein Problem, so könnte dieses Abfrageobjekt aussehen:
class AllOverdueAccountsQuery implements AllOverdueAccountsQueryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function fetch()
{
return $this->db->query($this->sql())->rows();
}
public function sql()
{
return "SELECT...";
}
}
Dadurch bleibt meine gesamte Logik für diesen Bericht in einer Klasse und es ist einfach zu testen. Ich kann es nach Herzenslust verspotten oder sogar eine ganz andere Implementierung verwenden.
Der Controller
Nun der lustige Teil - alle Teile zusammenbringen. Beachten Sie, dass ich die Abhängigkeitsinjektion verwende. Normalerweise werden Abhängigkeiten in den Konstruktor eingefügt, aber ich ziehe es tatsächlich vor, sie direkt in meine Controller-Methoden (Routen) einzufügen. Dies minimiert das Objektdiagramm des Controllers und ich finde es tatsächlich besser lesbar. Hinweis: Wenn Ihnen dieser Ansatz nicht gefällt, verwenden Sie einfach die traditionelle Konstruktormethode.
class UsersController
{
public function index(AllUsersQueryInterface $query)
{
// Fetch user data
$users = $query->fetch(['first_name', 'last_name', 'email']);
// Return view
return Response::view('all_users.php', ['users' => $users]);
}
public function add()
{
return Response::view('add_user.php');
}
public function insert(UserRepositoryInterface $repository)
{
// Create new user model
$user = new User;
$user->first_name = $_POST['first_name'];
$user->last_name = $_POST['last_name'];
$user->gender = $_POST['gender'];
$user->email = $_POST['email'];
// Save the new user
$repository->save($user);
// Return the id
return Response::json(['id' => $user->id]);
}
public function view(SpecificUserQueryInterface $query, $id)
{
// Load user data
if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
return Response::notFound();
}
// Return view
return Response::view('view_user.php', ['user' => $user]);
}
public function edit(SpecificUserQueryInterface $query, $id)
{
// Load user data
if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
return Response::notFound();
}
// Return view
return Response::view('edit_user.php', ['user' => $user]);
}
public function update(UserRepositoryInterface $repository)
{
// Load user model
if (!$user = $repository->find($id)) {
return Response::notFound();
}
// Update the user
$user->first_name = $_POST['first_name'];
$user->last_name = $_POST['last_name'];
$user->gender = $_POST['gender'];
$user->email = $_POST['email'];
// Save the user
$repository->save($user);
// Return success
return true;
}
public function delete(UserRepositoryInterface $repository)
{
// Load user model
if (!$user = $repository->find($id)) {
return Response::notFound();
}
// Delete the user
$repository->delete($user);
// Return success
return true;
}
}
Abschließende Gedanken:
Die wichtigsten Dinge, die hier zu beachten sind, sind, dass ich beim Ändern (Erstellen, Aktualisieren oder Löschen) von Entitäten mit realen Modellobjekten arbeite und die Persistenz über meine Repositorys durchführe.
Beim Anzeigen (Auswählen von Daten und Senden an die Ansichten) arbeite ich jedoch nicht mit Modellobjekten, sondern mit einfachen alten Wertobjekten. Ich wähle nur die Felder aus, die ich benötige, und sie sind so konzipiert, dass ich meine Daten-Suchleistung maximieren kann.
Meine Repositorys bleiben sehr sauber, und stattdessen ist dieses "Durcheinander" in meinen Modellabfragen organisiert.
Ich verwende einen Daten-Mapper, um bei der Entwicklung zu helfen, da es einfach lächerlich ist, sich wiederholendes SQL für allgemeine Aufgaben zu schreiben. Sie können jedoch bei Bedarf unbedingt SQL schreiben (komplizierte Abfragen, Berichterstellung usw.). Und wenn Sie dies tun, ist es schön in einer richtig benannten Klasse versteckt.
Ich würde gerne Ihre Meinung zu meinem Ansatz hören!
Update Juli 2015:
Ich wurde in den Kommentaren gefragt, wo ich mit all dem gelandet bin. Naja, eigentlich gar nicht so weit weg. Ehrlich gesagt mag ich Repositories immer noch nicht wirklich. Ich finde sie übertrieben für grundlegende Suchvorgänge (insbesondere wenn Sie bereits ein ORM verwenden) und chaotisch, wenn Sie mit komplizierteren Abfragen arbeiten.
Ich arbeite im Allgemeinen mit einem ORM im ActiveRecord-Stil, daher verweise ich meistens nur direkt auf diese Modelle in meiner Anwendung. In Situationen, in denen ich komplexere Abfragen habe, verwende ich Abfrageobjekte, um diese wiederverwendbarer zu machen. Ich sollte auch beachten, dass ich meine Modelle immer in meine Methoden einfüge, damit sie in meinen Tests leichter verspottet werden können.
new Query\ComplexUserLookup($username, $anotherCondition)
. Oder tun Sie dies über Setter-Methoden$query->setUsername($username);
. Sie können dies wirklich entwerfen, aber es ist für Ihre spezielle Anwendung sinnvoll, und ich denke, Abfrageobjekte lassen hier viel Flexibilität.Nach meiner Erfahrung finden Sie hier einige Antworten auf Ihre Fragen:
F: Wie gehen wir damit um, dass wir nicht benötigte Felder zurückbringen?
A: Meiner Erfahrung nach läuft dies wirklich darauf hinaus, sich mit vollständigen Entitäten im Vergleich zu Ad-hoc-Abfragen zu befassen.
Eine vollständige Entität ist so etwas wie ein
User
Objekt. Es hat Eigenschaften und Methoden usw. Es ist ein erstklassiger Bürger in Ihrer Codebasis.Eine Ad-hoc-Abfrage gibt einige Daten zurück, aber darüber hinaus wissen wir nichts. Wenn die Daten in der Anwendung weitergegeben werden, erfolgt dies ohne Kontext. Ist es ein
User
? AUser
mit einigenOrder
Informationen im Anhang? Wir wissen es nicht wirklich.Ich arbeite lieber mit vollständigen Entitäten.
Sie haben Recht, dass Sie häufig Daten zurückbringen, die Sie nicht verwenden, aber Sie können dies auf verschiedene Arten angehen:
User
für das Back-End und möglicherweise eineUserSmall
für AJAX-Anrufe haben. Man könnte 10 Eigenschaften haben und man hat 3 Eigenschaften.Die Nachteile der Arbeit mit Ad-hoc-Abfragen:
User
schreiben Sie beispielsweiseselect *
für viele Anrufe im Wesentlichen dasselbe . Ein Anruf erhält 8 von 10 Feldern, einer 5 von 10, einer 7 von 10. Warum nicht alle durch einen Anruf ersetzen, der 10 von 10 erhält? Der Grund dafür ist, dass es Mord ist, neu zu faktorisieren / zu testen / zu verspotten.User
so langsam?" Am Ende werden einmalige Abfragen aufgespürt, sodass Fehlerkorrekturen in der Regel klein und lokalisiert sind.F: Ich werde zu viele Methoden in meinem Repository haben.
A: Ich habe keinen anderen Weg gesehen, als Anrufe zu konsolidieren. Die Methodenaufrufe in Ihrem Repository werden wirklich Funktionen in Ihrer Anwendung zugeordnet. Je mehr Funktionen, desto mehr datenspezifische Anrufe. Sie können Funktionen zurücksetzen und versuchen, ähnliche Anrufe zu einem zusammenzuführen.
Die Komplexität am Ende des Tages muss irgendwo existieren. Mit einem Repository-Muster haben wir es in die Repository-Oberfläche verschoben, anstatt möglicherweise eine Reihe gespeicherter Prozeduren zu erstellen.
Manchmal muss ich mir sagen: "Nun, es musste irgendwo geben! Es gibt keine Silberkugeln."
quelle
SELECT *
, sondern nur die Felder auswählt, die Sie benötigen. Siehe zum Beispiel diese Frage . Was all die Ad-Hock-Anfragen betrifft, von denen Sie sprechen, verstehe ich mit Sicherheit, woher Sie kommen. Ich habe gerade eine sehr große App, die viele davon hat. Das war mein "Nun, es musste irgendwo geben!" Moment entschied ich mich für maximale Leistung. Jetzt beschäftige ich mich jedoch mit VIELEN verschiedenen Abfragen.reads
häufig Leistungsprobleme auftreten, können Sie für diese einen benutzerdefinierten Abfrageansatz verwenden, der sich nicht in echte Geschäftsobjekte umsetzt. Dann wird fürcreate
,update
unddelete
verwenden Sie ein ORM, die mit ganzen Objekten arbeitet. Irgendwelche Gedanken zu diesem Ansatz?Ich benutze folgende Schnittstellen:
Repository
- Lädt, fügt ein, aktualisiert und löscht EntitätenSelector
- findet Entitäten basierend auf Filtern in einem RepositoryFilter
- kapselt die FilterlogikMein
Repository
ist datenbankunabhängig; Tatsächlich gibt es keine Persistenz an. Es kann alles Mögliche sein: SQL-Datenbank, XML-Datei, Remote-Service, ein Außerirdischer aus dem Weltraum usw. Für Suchfunktionen können dieRepository
KonstrukteSelector
gefiltertLIMIT
, sortiert, sortiert und gezählt werden. Am Ende holt der Selektor einen oder mehrereEntities
aus der Persistenz.Hier ist ein Beispielcode:
Dann eine Implementierung:
Die Idee ist, dass die generischen
Selector
Verwendungen,Filter
aber die ImplementierungSqlSelector
verwendetSqlFilter
; dasSqlSelectorFilterAdapter
passt ein generikumFilter
an einen konkreten anSqlFilter
.Der Client-Code erstellt
Filter
Objekte (die generische Filter sind), aber in der konkreten Implementierung des Selektors werden diese Filter in SQL-Filter transformiert.Andere Selektorimplementierungen
InMemorySelector
wandeln sich vonFilter
zurInMemoryFilter
Verwendung ihrer spezifischen umInMemorySelectorFilterAdapter
; Daher wird jede Selektorimplementierung mit einem eigenen Filteradapter geliefert.Mit dieser Strategie kümmert sich mein Client-Code (in der Bussines-Ebene) nicht um ein bestimmtes Repository oder eine bestimmte Selector-Implementierung.
PS Dies ist eine Vereinfachung meines echten Codes
quelle
Ich werde ein wenig hinzufügen, da ich gerade versuche, all dies selbst zu erfassen.
# 1 und 2
Dies ist ein perfekter Ort für Ihr ORM, um das schwere Heben zu erledigen. Wenn Sie ein Modell verwenden, das eine Art ORM implementiert, können Sie einfach seine Methoden verwenden, um diese Dinge zu erledigen. Erstellen Sie Ihre eigenen orderBy-Funktionen, die bei Bedarf die Eloquent-Methoden implementieren. Verwenden von Eloquent zum Beispiel:
Was Sie zu suchen scheinen, ist ein ORM. Kein Grund, warum Ihr Repository nicht auf einem basieren kann. Dies würde eine eloquente Benutzererweiterung erfordern, aber ich persönlich sehe das nicht als Problem.
Wenn Sie jedoch ein ORM vermeiden möchten, müssten Sie "Ihr eigenes rollen", um das zu bekommen, wonach Sie suchen.
#3
Schnittstellen sollten keine festen Anforderungen sein. Etwas kann eine Schnittstelle implementieren und ergänzen. Was es nicht tun kann, ist, eine erforderliche Funktion dieser Schnittstelle nicht zu implementieren. Sie können auch Schnittstellen wie Klassen erweitern, um die Dinge trocken zu halten.
Das heißt, ich fange gerade erst an zu verstehen, aber diese Erkenntnisse haben mir geholfen.
quelle
Ich kann nur kommentieren, wie wir (in meinem Unternehmen) damit umgehen. Zuallererst ist Leistung für uns kein allzu großes Problem, aber sauberer / korrekter Code ist es.
Zunächst definieren wir Modelle wie
UserModel
ein Modell, das mithilfe eines ORMUserEntity
Objekte erstellt. Wenn aUserEntity
aus einem Modell geladen wird, werden alle Felder geladen. Für Felder, die auf fremde Entitäten verweisen, verwenden wir das entsprechende fremde Modell, um die jeweiligen Entitäten zu erstellen. Für diese Entitäten werden die Daten bei Bedarf geladen. Jetzt könnte Ihre erste Reaktion sein ... ??? ... !!! Lassen Sie mich ein Beispiel geben:In unserem Fall
$db
handelt es sich um ein ORM, das Entitäten laden kann. Das Modell weist das ORM an, eine Reihe von Entitäten eines bestimmten Typs zu laden. Das ORM enthält eine Zuordnung und verwendet diese, um alle Felder für diese Entität in die Entität einzufügen. Für fremde Felder werden jedoch nur die IDs dieser Objekte geladen. In diesem FallOrderModel
erstellt dasOrderEntity
s nur die IDs der referenzierten Bestellungen. Wenn die EntitätPersistentEntity::getField
von derOrderEntity
Entität aufgerufen wird , weist sie ihr Modell an, alle Felder in dasOrderEntity
s zu laden . AlleOrderEntity
s, die einer UserEntity zugeordnet sind, werden als eine Ergebnismenge behandelt und sofort geladen.Die Magie hier ist, dass unser Modell und ORM alle Daten in die Entitäten einfügen und dass Entitäten lediglich Wrapper-Funktionen für die von
getField
bereitgestellte generische Methode bereitstellenPersistentEntity
. Zusammenfassend laden wir immer alle Felder, aber Felder, die auf eine fremde Entität verweisen, werden bei Bedarf geladen. Das Laden einer Reihe von Feldern ist kein wirkliches Leistungsproblem. Das Laden aller möglichen ausländischen Unternehmen wäre jedoch ein RIESIGER Leistungsabfall.Nun zum Laden einer bestimmten Gruppe von Benutzern, basierend auf einer where-Klausel. Wir bieten ein objektorientiertes Paket von Klassen, mit denen Sie einfache Ausdrücke angeben können, die zusammengeklebt werden können. Im Beispielcode habe ich es benannt
GetOptions
. Es ist ein Wrapper für alle möglichen Optionen für eine ausgewählte Abfrage. Es enthält eine Sammlung von where-Klauseln, eine group by-Klausel und alles andere. Unsere where-Klauseln sind ziemlich kompliziert, aber Sie könnten natürlich leicht eine einfachere Version erstellen.Eine einfachste Version dieses Systems wäre, den WHERE-Teil der Abfrage als Zeichenfolge direkt an das Modell zu übergeben.
Es tut mir leid für diese ziemlich komplizierte Antwort. Ich habe versucht, unseren Rahmen so schnell und klar wie möglich zusammenzufassen. Wenn Sie weitere Fragen haben, können Sie diese gerne stellen und ich werde meine Antwort aktualisieren.
BEARBEITEN: Wenn Sie einige Felder wirklich nicht sofort laden möchten, können Sie in Ihrer ORM-Zuordnung eine Option zum verzögerten Laden angeben. Da alle Felder schließlich über die
getField
Methode geladen werden , können Sie einige Felder in letzter Minute laden, wenn diese Methode aufgerufen wird. Dies ist kein sehr großes Problem in PHP, aber ich würde es nicht für andere Systeme empfehlen.quelle
Dies sind einige verschiedene Lösungen, die ich gesehen habe. Jeder von ihnen hat Vor- und Nachteile, aber Sie müssen selbst entscheiden.
Problem Nr. 1: Zu viele Felder
Dies ist ein wichtiger Aspekt, insbesondere wenn Sie nur Index-Scans berücksichtigen . Ich sehe zwei Lösungen, um mit diesem Problem umzugehen. Sie können Ihre Funktionen aktualisieren, um einen optionalen Array-Parameter aufzunehmen, der eine Liste der zurückzugebenden Spalten enthält. Wenn dieser Parameter leer ist, geben Sie alle Spalten in der Abfrage zurück. Das kann etwas komisch sein; Basierend auf dem Parameter können Sie ein Objekt oder ein Array abrufen. Sie können auch alle Ihre Funktionen duplizieren, sodass Sie zwei unterschiedliche Funktionen haben, die dieselbe Abfrage ausführen. Eine gibt jedoch ein Array von Spalten und die andere ein Objekt zurück.
Problem Nr. 2: Zu viele Methoden
Ich habe vor einem Jahr kurz mit Propel ORM gearbeitet und dies basiert auf dem, woran ich mich aus dieser Erfahrung erinnern kann. Propel hat die Möglichkeit, seine Klassenstruktur basierend auf dem vorhandenen Datenbankschema zu generieren. Es werden zwei Objekte für jede Tabelle erstellt. Das erste Objekt ist eine lange Liste von Zugriffsfunktionen, die denen ähneln, die Sie derzeit aufgelistet haben.
findByAttribute($attribute_value)
. Das nächste Objekt erbt von diesem ersten Objekt. Sie können dieses untergeordnete Objekt aktualisieren, um Ihre komplexeren Getter-Funktionen einzubauen.Eine andere Lösung wäre die
__call()
Zuordnung nicht definierter Funktionen zu etwas Umsetzbarem. Ihre__call
Methode wäre in der Lage, findById und findByName in verschiedene Abfragen zu analysieren.Ich hoffe das hilft zumindest einiges was.
quelle
Ich empfehle https://packagist.org/packages/prettus/l5-repository als Anbieter, um Repositories / Criterias usw. in Laravel5: D zu implementieren
quelle
Ich stimme @ ryan1234 zu, dass Sie vollständige Objekte innerhalb des Codes weitergeben und generische Abfragemethoden verwenden sollten, um diese Objekte abzurufen.
Für die externe / Endpunkt-Verwendung mag ich die GraphQL-Methode sehr.
quelle
Mein Bauch sagt mir, dass dies möglicherweise eine Schnittstelle erfordert, die neben generischen Methoden auch abfrageoptimierte Methoden implementiert. Leistungsempfindliche Abfragen sollten zielgerichtete Methoden haben, während seltene oder leichte Abfragen von einem generischen Handler bearbeitet werden, was möglicherweise die Kosten des Controllers verursacht, der etwas mehr Jonglieren ausführt.
Die generischen Methoden würden die Implementierung einer Abfrage ermöglichen und somit verhindern, dass Änderungen während einer Übergangszeit unterbrochen werden. Mit den gezielten Methoden können Sie einen Anruf optimieren, wenn dies sinnvoll ist, und er kann auf mehrere Dienstanbieter angewendet werden.
Dieser Ansatz ähnelt Hardware-Implementierungen, die bestimmte optimierte Aufgaben ausführen, während Software-Implementierungen die leichte Arbeit oder die flexible Implementierung erledigen.
quelle
Ich denke graphQL ist in einem solchen Fall ein guter Kandidat, um eine umfangreiche Abfragesprache bereitzustellen, ohne die Komplexität von Datenrepositorys zu erhöhen.
Es gibt jedoch eine andere Lösung, wenn Sie sich vorerst nicht für graphQL entscheiden möchten. Mit einem DTO bei dem ein Objekt zum Übertragen der Daten zwischen Prozessen verwendet wird, in diesem Fall zwischen dem Dienst / Controller und dem Repository.
Eine elegante Antwort ist bereits oben angegeben, ich werde jedoch versuchen, ein anderes Beispiel zu nennen, das meiner Meinung nach einfacher ist und als Ausgangspunkt für ein neues Projekt dienen könnte.
Wie im Code gezeigt, würden wir nur 4 Methoden für CRUD-Operationen benötigen. das
find
Methode wird zum Auflisten und Lesen durch Übergeben eines Objektarguments verwendet. Backend-Services können das definierte Abfrageobjekt basierend auf einer URL-Abfragezeichenfolge oder basierend auf bestimmten Parametern erstellen.Das Abfrageobjekt (
SomeQueryDto
) kann bei Bedarf auch eine bestimmte Schnittstelle implementieren. und kann später leicht erweitert werden, ohne die Komplexität zu erhöhen.Anwendungsbeispiel:
quelle